calibre
Author: Kovid Goyal
Date: 2024-04-19T03:11:43+00:00
Description: Comprehensive documentation for calibre

Navigatie

- modules
- volgende |
- Start »
- calibre Gebruikershandleiding

calibre Gebruikershandleiding
calibre is een e-boekbibliotheek manager. Het kan e-boeken bekijken, converteren en catalogiseren in de meeste grote e-boekformaten. Het kan ook praten met veel e-boek readers. Het kan naar internet gaan en metadata voor uw boeken ophalen. Het kan kranten downloaden en omzetten in e-boeken voor gemakkelijke lezen. Het is cross platform, draait op Linux, Windows en MacOS.

U bent net begonnen met het gebruik van calibre. Hoe moet het verder? Voor calibre iets kan doen met uw e-boeken, moet het ze leren kennen. Sleep een aantal e-boeken in calibre, of klik op de “boeken toevoegen” knop en blader voor de e-boeken die u wil gebruiken. Eenmaal de boeken zijn toegevoegd, verschijnen ze in het hoofdscherm zoals hieronder:

Zodra u de lijst met boeken hebt bewonderd die u zojuist hebt toegevoegd, wilt u er waarschijnlijk een lezen. Om dat te doen, moet u het boek converteren naar een formaat dat uw reader begrijpt. Bij het eerste gebruik van Calibre wordt de: guilabel: Welkom wizard gestart en wordt calibre voor uw reader ingesteld. Conversie is een eitje. Selecteer gewoon het boek dat u wilt converteren en klik op de knop “Boeken converteren”. Negeer voor nu alle opties en klik op “OK”. Het kleine pictogram in de rechterbenedenhoek begint te draaien. Als het klaar is met draaien, is uw geconverteerde boek klaar. Klik op de knop “Bekijken” om het boek te lezen.

Als u een boek op uw reader wil lezen, sluit de reader aan op de pc en wacht tot calibre hem detecteert (10-20 seconden). Klik vervolgens op de knop “Verzenden naar toestel”. Zodra het pictogram stopt met draaien, afkoppelen en lezen! Als u het boek niet geconverteerd heeft bij de vorige stap, zal calibre het boek converteren naar het formaat dat uw reader begrijpt.

Voor meer geavanceerd gebruik, zou u moeten lezen over De Grafische Gebruikersinterface. Voor nog krachtiger functies, leer Opdrachtpromt. Tevens zal u de lijst Veel gestelde vragen nuttig vinden.

Als u meer vragen hebt, of calibre wilt bespreken met ander gebruikers of hulp vragen met specifieke dingen, er zijn forums en andere hulpbronnen beschikbaar [https://calibre-ebook.com/nl/help].

Paragrafen

- De Grafische Gebruikersinterface
- Uw favoriete nieuws website toevoegen
- De e-boek viewer
- E-boek conversie
- E-boeken bewerken
- De calibre Content server
- E-books vergelijken
- Metadata van e-boeken aanpassen
- Veel gestelde vragen
- Lesboeken
- Het calibre:// URL schema
- calibre aanpassen
- Opdrachtpromt

- Opzetten van Calibre in een development omgeving

- Digital Rights Management (DRM)

- Woordenlijst

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- De Grafische Gebruikersinterface

De Grafische Gebruikersinterface

De Grafische Gebruikersinterface (GUI) biedt toegang tot alle bibliotheekbeheer en e-bookformaat conversie functies. De basis werkstroom voor gebruik van calibre is eerst boeken toevoegen aan de bibliotheek van uw harde schijf. calibre probeert automatisch de metadata te lezen uit de boeken en toe te voegen aan de interne database. Als ze in de database zitten, kunt u er verschillende: ref: actions op uitvoeren, zoals conversie van één formaat naar een ander, overbrengen naar de e-reader, bekijken op uw pc en bewerken van de metadata. Dat laatste omvat o.a. wijzigen van de omslag, omschrijving en labels. Merk op dat calibre kopieën maakt van de bestanden die u toevoegt. Uw originele bestanden blijven onaangetast.

De weergave is verdeeld in verschillende secties:

- Acties

- Voorkeuren

- Catalogi

- Zoeken & sorteren

- De zoek interface

- Zoekopdrachten opslaan

- Searching the full text of all books

- Virtuele bibliotheken

- Temporarily marking books

- Raadt metadata op basis van bestandsnamen

- Boekgegevens

- Labelbrowser

- Omslagenrooster

- Omslagbrowser

- Adding notes for authors, series, etc.

- Snel-weergave

- Taken

- Toetsenbord sneltoetsen

Acties

De acties werkbalk biedt handige snelkoppelingen naar veelgebruikte acties. Als u met de rechtermuisknop op de knoppen klikt, kunt u variaties op de standaardactie uitvoeren. Houd er rekening mee dat de acties werkbalk er enigszins anders uit zal zien, afhankelijk van of u een e-boek reader op uw computer hebt aangesloten.

- Boeken toevoegen

- Metadata bewerken

- Boeken converteren

- Tonen

- Naar apparaat sturen

- Nieuws ophalen

- Bibliotheek

- Apparaat

- Op schijf opslaan

- Verbinden/delen

- Boeken verwijderen

Boeken toevoegen

The Boeken toevoegen actie heeft zeven verschillende opties bij rechter muisklik op de knop.

1. Boeken toevoegen uit een map: Opent een bestandskiezer en laat u opgeven welke boeken uit een map toe te voegen. Deze actie is contextgevoelig d.w.z. het hangt ervan af welke catalogus u heeft geselecteerd. Indien u de Bibliotheek heeft geselecteerd, worden de boeken aan de bibliotheek toegevoegd. Heeft u de e-reader geselecteerd worden de boeken geüpload naar het toestel enz.

2. Boeken toevoegen uit mappen en submappen: Laat u een map kiezen. De map en al z’n submappen worden recursief doorzocht en gevonden e-boeken worden toegevoegd aan de bibliotheek. U kan kiezen tussen calibre alle bestanden in een map aan één enkel boek record of meerdere records laten toevoegen. calibre gaat ervan uit dat elke map een enkel boel bevat. Alle e-boekbestanden in een map worden verondersteld hetzelfde boek te zijn in verschillende formaten. Deze actie is het omgekeerde van Opslaan op schijf , d.w.z. u kunt Opslaan op schijf, de boeken verwijderen en opnieuw toevoegen in één boek per map modus zonder informatie te verliezen behalve de datum (ervan uitgaande dat u geen instellingen heeft gewijzigd voor de Opslaan op schijf actie).

3. Meerdere boeken toevoegen uit archief (ZIP/RAR): Laat u meerdere e-boeken toevoegen opgeslagen in de geselecteerde ZIP of RAR bestanden. Een handige optie die vermijdt eerst de ZIP archieven uit te pakken en dan de boeken toe te voegen met een van de twee opties boven.

4. Voeg leeg boek toe (Boek record zonder formaat) Laat u toe een blanco boek record te maken. Kan gebruikt worden om later manueel de informatie in te vullen van een boek dat u nog niet in uw collectie heeft.

5. Toevoegen van ISBN: Toestaan om boeken toe te voegen door gebruik van ISBN-nummers.

6. Voeg bestanden toe aan geselecteerde boekrecords: Laat u bestanden van een bestaand boek in uw bibliotheek toevoegen of aanpassen.

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 Adding extra data files to a book for details.

8. Voeg leeg bestand toe aan geselecteerde boek records*: Laat toevoegen van leeg bestand van gespecificeerde formaat aan geselecteerde boek records toe.

De actie Boeken toevoegen kan de metadata lezen van vele e-boekformaten. Daarnaast probeert het de metadata te raden uit de bestandsnaam. Zie de sectie Raadt metadata op basis van bestandsnamen om te leren hoe dit te configureren.

Om een bijkomend formaat toe te voegen aan een bestaand boek kan u drie dingen doen:

1. Sleep het bestand en zet het neer op het Boek details paneel rechts in het hoofdvenster

2. Klik rechts op het Boeken toevoegen en kies Voeg bestanden toe aan geselecteerde boeken.

3. Klok op de:guilabel:Boeken toevoegen knop rechtsboven in de Metadata bewerken dialoog, toegang via de :ref:``edit_meta_information` actie.

Metadata bewerken

De Metadata bewerken actie heeft vier verschillende opties die zichtbaar worden door rechts te klikken op de knop.

1. Metadata individueel bewerken: Laat u metadata van boeken één voor één bewerken met de optie om metadata, inclusief omslagen, op te halen van internet. Laat u ook bepaalde e-boekformaten van een boek toevoegen of verwijderen.

2. Metadata bewerken in bulk: Laat u veel gebruikte velden van metadata voor grote hoeveelheden boeken tegelijk bewerken. Heeft effect op alle boeken geselecteerd in de Bibliotheek weergave.

3. Metadata en omslagen downloaden: Downloadt metadata en omslag (indien beschikbaar) voor de geselecteerde boeken.

4. Boekrecords samenvoegen: Laat u metadata en formaten van twee of meer boekrecords samenvoegen. U kunt kiezen om de records die niet eerst werden aangeklikt te verwijderen of te behouden.

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

Voor meer details, zie Metadata van e-boeken aanpassen.

Boeken converteren

E-boeken kunnen geconverteerd worden van een aantal formaten in elk formaatdat uw e-reader bevalt. Vele e-boeken te koop zijn beschermd door Digital Rights Management (DRM) technologie. calibre zal deze e-boeken niet converteren. Het is gemakkelijk om het DRM te verwijderen van vele formaten, maar omdat dat mogelijk illegaal is, moet u zelf het gereedschap vinden om uw boeken te bevrijden en daarna calibre gebruiken om ze te converteren.

Meestal is een conversie een kwestie van één muisklik. Als u meer wilt weten over het conversieproces, ga naar E-boek conversie.

De Convert books actie heeft drie verschillende opties die zichtbaar worden door rechts te klikken op de knop.

1. Individueel converteren: Laat u de conversieopties instellen om de conversie van elk geselecteerd e-boek aan te passen.

2. Bulk converteren: Laat u eenmalig keuzes opgeven om een aantal e-boeken tegelijk te converteren.

3. Catalogus maken van de boeken in uw calibre bibliotheek: Laat u een volledige lijst genereren van de boeken in uw bibliotheek, inclusief alle metadata, in meerdere formaten zoals XML, CSV, BIBTeX, EPUB en MOBI. Deze catalogus bevat alle boeken momenteel getoond in de bibliotheekweergave. Dit laat u toe de zoekfunctie te gebruiken om het aantal boeken dat gecatalogiseerd wordt te beperken. Als u daarnaast meerdere boeken selecteert met behulp van de muis, worden alleen deze boeken toegevoegd aan de catalogus. Indien u de catalogus genereert in een e-boek formaat zoals EPUB, MOBI of AZW3 zal de volgende keer als u verbindt met uw e-boekreader de catalogus automatisch verzonden worden naar het toestel. Voor meer informatie over hoe een catalogus werkt, lees Creëren van AZW3 • EPUB • MOBI-catalogi.

Tonen

De Tonen actie toont het boek in een e-boekreader. calibre heeft een ingebouwde reader voor veel e-boekformaten. Voor andere formaten gebruikt het de standaard applicatie van het besturingssysteem. U kunt instellen welke formaten met de interne reader moeten worden geopend via Voorkeuren → Interface → Gedrag. Als een boek meerdere formaten heeft, kunt u een bepaald formaat weergeven met een rechterklik op de knop.

Naar apparaat sturen

De Naar apparaat sturen actie heeft acht verschillende opties die zichtbaar worden door rechts te drukken op de knop.

1. Naar hoofdgeheugen: De geselecteerde boeken worden overgedragen naar het hoofdgeheugen van de e-reader.

2. Naar kaart (A): De geselecteerde boeken worden overgedragen naar de geheugenkaart (A) van de e-reader.

3. Naar kaart (B): De geselecteerde boeken worden overgedragen naar de geheugenkaart (B) van de e-reader.

4. Bepaald formaat naar: De geselecteerde boeken worden overgedragen naar de geselecteerde locatie van het apparaat en in het formaat dat u opgeeft.

5. Apparaat uitwerpen: Verbreekt verbinding tussen Calibre en het apparaat.

6. Standaard naar apparaat actie instellen: Hier kunt u welke van de opties 1 t/m 5 hierboven of 7 hieronder de standaard actie wordt wanneer u op de hoofdknop klikt.

7. Verstuur en verwijder uit bibliotheek: De geselecteerde boeken worden overgedragen naar de geselecteerde locatie op het apparaat en verwijderd uit de Bibliotheek.

8. Aantekeningen ophalen (experimenteel): Draagt aantekeningen die u heeft gemaakt bij een e-boek op uw toestel over naar de commentaren-metadata van het boek in de calibre bibliotheek.

U kunt bestandsnaam en mapstructuur van bestanden verstuurd naar het apparaat bepalen door een sjabloon in te stellen in Voorkeuren → Import/Export → Boeken versturen naar toestellen. Lees meer De calibre sjabloontaal.

Nieuws ophalen

De Nieuws ophalen actie downloadt nieuws van diverse websites en converteert dit in een e-boek dat gelezen kan worden op uw e-reader. Normaal wordt het nieuw aangemaakte e-boek toegevoegd aan uw bibliotheek, maar als een e-reader verbonden is als de download stopt, wordt het nieuws automatisch geüpload naar de e-reader.

De Nieuws ophalen actie gebruikt simpele recepten (10-15 regels code) voor elke nieuwssite. Om te leren hoe u een recept kunt maken voor uw eigen nieuwsbron, zie Uw favoriete nieuws website toevoegen.

De Nieuws ophalen actie heeft drie opties, toegankelijk via rechts klikken op de knop.

1. Schema nieuws download: Met deze optie kunt u de download inplannen voor uw geselecteerde nieuwsbronnen van een lijst van honderden die beschikbaar zijn. Voor elke nieuwsbron kunt u een schema instellen en is flexibel zodat u specifieke weekdagen kunt selecteren of het aantal dagen tussen downloads.

2. Een aangepaste nieuwsbron toevoegen: Laat u een eenvoudig recept creëren om nieuws te downloaden van een speciale nieuwssite die u wilt gebruiken. Het recept creëren kan zo simpel zijn als een RSS nieuws-feed URL specificeren, of u kunt meer normatief zijn door Python gebaseerde code te creëren voor de taak. Voor meer informatie, kijk op Uw favoriete nieuws website toevoegen.

3. Alle ingeplande nieuwsbronnen downloaden: Hiermee zal Calibre onmiddellijk beginnen met het downloaden van alle nieuwsbronnen die u heeft ingepland.

Bibliotheek

Met de Bibliotheek actie kunt u een Bibliotheek aanmaken, schakelen tussen, hernoemen of verwijderen. U maar zo veel bibliotheken aanmaken als u wilt. U kunt bijvoorbeeld een fictie-bibliotheek aanmaken, eentje voor non-fictie, eentje voor vreemde talen, een projecten bibliotheek of elke structuur die u wenst. Bibliotheken zijn de hoogste organisatorische structuren binnen Calibre. Elke bibliotheek heeft zijn eigen boeken, labels, categorieën en geheugenlocaties.

1. Bibliotheek schakelen/aanmaken…: Met deze optie kunt u: a) verbinding opnemen met een bestaande bibliotheek op een andere locatie, b) een lege bibliotheek aanmaken op een nieuwe locatie of c) de huidige bibliotheek verplaatsen naar een nieuw te specificeren locatie.

2. Snel schakelen: Met deze optie kunt u schakelen tussen bibliotheken die aangemaakt zijn binnen Calibre.

3. Bibliotheek hernoemen: Hiermee kunt u een Bibliotheek een andere andere naam toekennen.

4. Neem een willekeurig boek: Kiest een willekeurig boek voor u uit de bibliotheek

5. Bibliotheek verwijderen: Laat u toe een bibliotheek te deregistreren uit calibre.

6. Export/import van alle calibre data: Laat u toe ofwel calibre data te exporteren voor migratie naar een nieuwe pc ofwel vroeger geëxporteerde data te importeren.

7. <library name>: Acties 7, 8 enz… kunt u meteen wisselen tussen meerdere bibliotheken die u heeft gemaakt of naar gelinkt. Deze lijst bevat enkel de 5 meest gefrequenteerde bibliotheken. Voor de hele lijst, gebruik het Snelle Wissel menu.

8. Bibliotheek onderhouden: Met deze optie kunt u de huidige bibliotheek controleren op gegevens-consistentie en de gegevensbank van de huidige bibliotheek herstellen van back-ups.

Notitie

Metadata over uw e-boeken, bv. titel, auteur en labels, zit in een enkel bestand in uw calibre bibliotheekmap, metadata.db genaamd. Als dit bestand beschadigd wordt (gebeurt zeer zelden) kunt u de metadata kwijt raken. Gelukkig back-upt calibre automatisch de metadata van elk boek in z’n map als een OPF bestand. Met de Database herstellen actie onder Bibliotheekonderhoud zoals boven beschreven, kunt u calibre het bestand metadata.db opnieuw laten opbouwen met de individuele OPF-bestanden.

U kunt boeken kopiëren of verplaatsen tussen verschillende bibliotheken (nadat u meer dan een bibliotheek heeft ingericht) door rechts te klikken op het boek en de actie te kiezen Kopieer naar bibliotheek.

Apparaat

Met de Apparaat optie kunt u boeken lezen in de hoofdopslag of opslagkaarten van uw apparaat of the apparaat uitwerpen (loskoppelen van Calibre). Dit pictogram wordt automatisch getoond op de hoofdwerkbalk in Calibre wanneer u een apparaat aansluit dat wordt ondersteund. U kunt erop klikken om de boeken te bekijken op uw apparaat. U kunt ook boeken van uw Calibre bibliotheek slepen en neerzetten op het pictrogram om deze over te dragen op uw apparaat. Andersom kunt u boeken verslepen en plaatsen van uw apparaat op het bibliotheek-icon van de werkbalk om boeken over te dragen van uw apparaat naar de Calibre bibliotheek.

Op schijf opslaan

Het opslaan op disk heeft 5 variaties, deze vind u door een rechterklik te geven op de knop.

1. Opslaan op schijf: Slaat de geselecteerde boeken op op de schijf, georganiseerd il mappen. De mapstructuur lijkt op

Author_(sort)
Title
Book Files

U kan de bestandsnaam en mapstructuur van op schijf opgeslagen bestanden controleren door een template in te stellen in Voorkeuren → Import/export → Boeken opslaan op schijf. Kijk ook bij De calibre sjabloontaal.

2. Opslaan op schijf in één map: Slaat de geselecteerde boeken op op de schijf in één map.

Voor 1. en 2. worden alle beschikbare formaten naast metadata op schijf opgeslagen voor elk geselecteerd boek. Metadata wordt bewaard in een OPF-bestand. Opgeslagen boeken kunnen opnieuw geïmporteerd worden in de bibliotheek zonder gegevensverlies met behulp van de optie Boeken toevoegen.

3. Bewaar alleen *<your preferred>* formaten op schijf: Slaat de geselecteerde boeken op op schijf in de mapstructuur getoond in (1.) maar enkel in uw geprefereerd e-boekformaat. U kunt uw voorkeursformaat instellen in Voorkeuren → Interface → Gedrag → Voorkeur uitvoerformaat

4. Bewaar alleen *<your preferred>* formaat op schijf in één map: Slaat geselecteerde boeken op op schijf in een enkele map maar enkel in uw e-boek voorkeursformaat. U kunt uw voorkeursformaat instellen in Voorkeuren → Interface → Gedrag → Voorkeur uitvoerformaat

5. Bewaar enkel formaat op schijf…: Slaat de geselecteerde boeken op op schijf in de mapstructuur getoond in (1.) maar enkel in het formaat dat u kiest uit de pop-uplijst.

Verbinden/delen

De Verbinden/delen actie laat u handmatig verbinden met een toestel of map op uw computer. Het laat u ook toe uw calibre bibliotheek in te stellen voor toegang per webbrowser of e-mail.

De Verbinden/delen actie heeft vier varianten, toegankelijk via een rechtse klik op de knop.

1. Verbinden met map: Met deze optie kunt u een verbinding maken met elke map op uw computer alsof het een apparaat is en gebruik maken van alle faciliteiten die Calibre biedt aan apparaten. Nuttig als uw apparaat niet ondersteund wordt door Calibre maar wel verbonden is met een USB-aansluiting.

2. Content server starten: Start calibre’e ingebouwde webserver. Na opstarten is uw calibre bibliotheek toegankelijk met een webbrowser vanaf het internet (indien gewenst). U kunt instellen hoe de webserver toegankelijk is door voorkeuren in te stellen op Voorkeuren → Delen → Delen over het net

3. Instellen e-mail gebaseerd delen van boeken: Hiermee kunt u boeken delen en mailinglijsten per e-mail. Nadat u e-mailadressen heeft ingericht, verstuurd Calibre nieuws- en boeken-updates naar de ingevoerde e-mailadressen. U kunt instellen hoe Calibre de e-mails verstuurd in de voorkeursinstellingen op Voorkeuren → Delen → Boeken delen per e-mail. Wanneer u eenmaal een of meer e-mailadressen heeft ingericht, zal deze optie worden vervangen door opties om boeken te versturen naar de ingevoerde e-mailadressen.

Boeken verwijderen

De Boeken verwijderen actie verwijdert boeken voorgoed, voorzichtig gebruiken dus. Het is contextgevoelig, d.w.z. hangt af van welke catalogus u heeft geselecteerd. Wanneer u Bibliotheek heeft geselecteerd worden boeken verwijderd uit de bibliotheek. Hebt u de e-reader geselecteerd, worden boeken verwijderd uit het toestel. Om enkel een specifiek formaat van een boek te verwijderen, gebruik de Metadata bewerken actie. Boeken verwijderen heeft ook vijf opties toegankelijk door rechts te klikken op de knop.

1. Geselecteerde boeken verwijderen: Hiermee verwijdert u alle boeken voorgoed die geselecteerd zijn in het boekenoverzicht.

2. Bestanden verwijderen van een bepaald formaat van geselecteerde boeken…: Hiermee verwijdert u voorgoed e-boekbestanden van een bepaald formaat van boeken geselecteerd in de boekenlijst.

3. Alle formaten verwijderen van geselecteerde boeken, behalve…: Hiermee verwijdert u voorgoed e-boekbestanden van elk formaat behalve een bepaald formaat van boeken geselecteerd in de boekenlijst.

4. Alle formaten verwijderen van geselecteerde boeken: Hiermee verwijdert u voorgoed alle e-boekbestanden van boeken geselecteerd in de boekenlijst. Alleen metadata blijven over.

5. Opslagen verwijderen van geselecteerde boeken: Hiermee kunt u afbeeldingen van omslagen voorgoed verwijderen van boeken die geselecteerd zijn in het boekenoverzicht.

6. Overeenkomende boeken verwijderen van toestel: Met deze optie verwijdert u e-boekbestanden van een verbonden apparaat die overeenkomen met boeken geselecteerd in de boekenlijst.

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

Notitie

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.

Voorkeuren

Met de Voorkeuren actie kunt u de werkwijze van Calibre aanpassen. Er zijn vier opties die zichtbaar worden door rechts op de knop te klikken.

1. Voorkeuren: Hiermee kunt u de werkwijze van Calibre op een aantal punten aanpassen. Het klikken op de knop voert deze actie ook uit.

2. Welkom wizard starten: Laat u de Welkom wizard starten die zichtbaar werd toen u calibre voor de eerste keer starte.

3. Plugins ophalen om Calibre uit te breiden: Opent een nieuw venster dat plugins toont voor Calibre. Deze plugins zijn ontwikkeld door derden om de functionaliteit van Calibre uit te breiden.

4. Herstarten in debug modus: Hiermee kunt u de foutenopsporing activeren die Calibre-ontwikkelaars helpen om problemen op te lossen waar u eventueel tegenaan loopt. Voor de meeste gebruikers moet dit uitgeschakeld blijven behalve als u gevraagd wordt door een ontwikkelaar om deze optie te activeren.

Catalogi

Een catalogus is een verzameling boeken. Calibre kan twee soorten catalogi beheren:

1. Bibliotheek: Dat is een verzameling boeken die bewaard worden in uw Calibre bibliotheek op uw computer.

2. Toestel: Dat is een verzameling boeken die bewaard wordt in uw e-reader. Deze wordt beschikbaar als u de reader verbindt met uw computer.

Veel bewerkingen zoals toevoegen van boeken, verwijderen, bekijken enz. zijn contextgevoelig. Als u bv. op de Tonen knop klikt wanneer u de Toestel catalogus heeft geselecteerd, zal calibre de bestanden op het toestel openen om te tonen. Als u de Bibliotheek catalogus heeft geselecteerd, worden in plaats daarvan de bestanden in uw calibre bibliotheek geopend.

Zoeken & sorteren

Met Zoeken & Sorteren kunt u enkele krachtige acties uitvoeren op uw boekenverzamelingen.

- U kunt ze sorteren op titel, auteur, datum, waardering etc. door op de kolomnamen te klikken. U kunt ook hiërarchisch op meerdere kolommen sorteren. Als u bijvoorbeeld klikt op de titel-kolom en daarna de auteur-kolom zullen de boeken worden gesorteerd op auteur en dan alle boeken van dezelfde auteur op titel.

- U kan naar een bepaald boek of boekenset zoeken via de Zoekbalk. Meer hierover hieronder.

- U kan snel en eenvoudig metadata bewerken door de door het item te selecteren dat u wilt veranderen en te drukken pop de E toets.

- U kan Acties uitvoeren op boekensets. Om meerdere boeken te selecteren kan u ofwel:

- Houd de Ctrl-toets ingedrukt en klik op de te selecteren boeken.

- Houd de Shift-toets ingedrukt en klik op het eerste en laatste boek van een te selecteren boekenreeks.

- U kunt instellen welke velden getoond worden met behulp van de Voorkeuren-dialoog.

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

De zoek interface

U kunt alle boek-metadata doorzoeken door zoektermen in te voeren in de Zoekbalk. Bijvoorbeeld:

Asimov Foundation format:lrf

Alle boeken in uw bibliotheek die Asimov en Foundation in hun metadata hebben staan en beschikbaar zijn in LRF formaat worden gevonden. Nog een paar voorbeelden:

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

Zoek soorten

Er zijn vier zoeksoorten: bevat, gelijk aan, reguliere expressie (bekijk regular expressions [https://en.wikipedia.org/wiki/Regular_expression]), en karakter variant. U kiest de zoeksoort met een voorvoegsel karakter.

‘Bevat’ opzoeking

Opzoekingen zonder voorvoegselkarakter zijn bevat en zijn standaard hoofdletterongevoelig. Een item komt overeen als de zoekstring eender waar voorkomt in de aangeduide metadata. U kan bevat opzoekingen hoofdlettergevoelig maken door de optie Hoofdlettergevoelig zoeken aan te vinken in Voorkeuren / Zoeken. Als de zoekoptie Karakters zonder accent komen overeen met die met accent en interpunctie wordt genegeerd is aangevinkt, komt een karakter overeen met al z’n varianten (bv e komt overeen met é, è, ê en ë) en alle interpunctie en witte ruimte wordt genegeerd. Als bv. de Karakters zonder accent… is aangevinkt, in geval van de rwee boektitels:

1. Big, Bothéred, and Bad

2. Big Bummer

deze opzoekingen vinden dan:

- title:"er" komt met beiden overeen (‘e’ met zowel ‘é’ als ‘e’).

- title:"g " komt met beiden overeen want spaties worden genegeerd.

- title:"g " komt met beiden overeen want de komma wordt genegeerd.

- title:"gb" komt met beiden overeen want ‘, ‘ wordt genegeerd in boek 1 en spaties in boek 2.

- title:"g b" komt met beiden overeen want komma en spatie worden genegeerd.

- title:"db" komt overeen met #1 want de spatie in ‘and Bad’ wordt genegeerd.

- title:"," komt met beiden overeen (komt eigenlijk met alle boeken overeen) want komma’s worden genegeerd.

Als de Karakters zonder accent… optie niet is aangevinkt zijn alle karakter varianten, interpunctie en spatiëring van betekenis.

U kan slechts één van de zoekopties Hoofdlettergevoelig zoeken en Karakters zonder accent komen overeen met die met en interpunctie wordt genegeerd.

‘Gelijk aan’ opzoeking

Gelijk aan opzoekingen worden aangegeven door de zoekstring te laten voorafgaan door een “is gelijk aan” teken (=). Bv de opzoeking tag:"=science" komt overeen met science, maar niet science fiction of hard science. Karaktervarianten zijn van betekenis: é komt niet overeen met 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" : Waar. History is een voorvoegsel van het label.

- tags:"=.History.Military" : Waar. History.Military is een voorvoegsel van het label.

- tags:"=.History.Military.WWII" : Waar. History.Military.WWII is een voorvoegsel van het label, zij het een onpassend.

- tags:"=.Military" : Onwaar. Military is geen voorvoegsel van het label.

- tags:"=.WWII" : Onwaar. WWII is geen voorvoegsel van het label.

- tags:"=..History" : Waar. De hiërarchie bevat de waarde History.

- tags:"=..Military" : Waar. De hiërarchie bevat de waarde Military.

- tags:"=..WWII" : Waar. De hiërarchie bevat de waarde WWII.

- tags:"=..Military.WWII" : Onwaar. De .. opzoeking kijkt naar enkelvoudige waardes.

‘Reguliere expressie’ opzoekingen

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 é.

‘Karakter variant’ opzoekingen

Karakter variant opzoekingen geeft u aan door de zoekstring te laten voorafgaan door een caret (^). Deze opzoeking is vergelijkbaar met de bevat opzoeking (boven) behalve dat:

- hoofdlettergebruik wordt altijd genegeerd

- karakter varianten komen altijd overeen

- interpunctie en witte ruimte/spaties zijn altijd belangrijk

De zoekopties Karakters zonder accent komen overeen met die met en interpunctie wordt genegeerd en Hoofdlettergevoelig zoeken worden genegeerd. Ze hebben geen effect op het gedrag van deze opzoeking.

Het volgende vergelijkt deze opzoeking met een bevat opzoeking ervan uitgaand dat de Karakters zonder accent… optie is aangevinkt (zie boven) met twee dezelfde boektitels:

1. Big, Bothéred, and Bad

2. Big Bummer

dan vinden deze karaktervariant opzoekingen:

- title:"^er" komt met beiden overeen (‘e’ met zowel ‘é’ als ‘e’).

- title:"^g" komt met beiden overeen

- title:"^g " komt met #2 overeen want de spatie is van betekenis

- title:"^g " komt met #1 overeen want de komma is van betekenis

- title:"^gb " komt met niets overeen want de komma en de spatie zijn van betekenis

- title:"^g b" komt met #2 overeen want de komma is van betekenis

- title:"^db" komt met niets overeen

- title:"^, " komt met #1 overeen (ipv alle boeken) want de komma is van betekenis

Zoekopdracht Syntaxis

Een zoekexpressie is een opeenvolging van zoektermen optioneel gescheiden door de operatoren and en or. Twee zoektermen zonder scheidende operator duiden op and. De and operator heeft prioriteit op de or operator; bv: de de expressie a or b and c is hetzelfde als a or (b and c). U kan haakjes gebruiken om de prioriteit te veranderen; bv (a or b) and c laat de or evalueren voor de and. U kan de operator not gebruiken om het resultaat van het evalueren van een zoekexpressie om te keren. Voorbeelden:

- not tag:foo vind alle boeken die het label foo niet bevatten

- not (author:Asimov or author:Weber) vind alle boeken niet geschreven door Asimov of 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\"

Als u echt een schuine streep links nodig hebt, iets dat regelmatig gebeurt in reguliere expressie opzoekingen, gebruik er dan twee (\\).

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.

“Meer informatie”

Om te zoeken naar een string die begint met een is gelijk aan, tilde of caret, zet een schuine streep links voor de string.

Omsluit zoekstrings met aanhalingstekens (”) als de string haakjes of spaties bevat. Bv. om boeken te zoeken met het label Science Fiction moet u zoeken naar tag:"=science fiction". Als u zoekt naar tag:=science fiction` vindt u alle boeken met het label science en het woord fiction in eender welke metadata.

U kan gemakkelijk geavanceerde opzoekingen bouwen met Geavanceerde zoekdialoog toegankelijk door klikken op de knop lsbil links van het zoekvak.

Beschikbare zoekvelden zijn:tag, title, author, publisher, series, series_index, rating, cover, comments, format, identifiers, date, pubdate, search, size, vl en aangepaste kolommen. Als een toestel aangesloten is, wordt het veld ondevice beschikbaar wanneer u de calibre bibliotheekweergave doorzoekt. Om de zoeknaam (de zogenaamde opzoeknaam) te vinden van een aangepaste kolom, zweef met de muis over het kolomkop in de bibliotheekweergave.

Datums

De syntaxis voor zoeken daar gegevens is:

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

Als de datum twijfelachtig is, wordt de huidige landinstelling gebruikt voor datum vergelijking. Bv. in een mm/dd/yyyy landinstelling wordt 2/1/2009 geïnterpreteerd als 1 Feb 2009. In een dd/mm/yyyy locale wordt het 2 Jan 2009. Er zijn enkele speciale datumstrings beschikbaar. Today wordt vandaag’s datum, eender welke dag het is. Yesterday` and thismonth (of de vertaalde equivalenten in de huidige taal) werken ook. Bijkomend kan de string daysago (ook vertaald) gebruikt worden om te vergelijken met een datum een aantal dagen geleden. Bijvoorbeeld:

date:>10daysago
date:<=45daysago

Om eventuele problemen te voorkomen met vertaalde uitdrukkingen voor een niet-Engelse versie van Calibre zijn de uitdrukkingen _today, _yesterday, _thismonth en _daysago altijd beschikbaar. Deze worden niet vertaald.

Datums en numerieke waardes doorzoeken met relationele vergelijkingen

Datum en numerieke velden ondersteunen de relatieve operatoren = (is gelijk), > (groter dan), >= (groter dan of is gelijk), < (kleiner dan), <= (kleiner dan of is gelijk) en != (niet gelijk). Beoordelingsvelden worden beschouwd als numeriek. Bv zoeken naar rating:>=3 vindt alle boeken beoordeeld 3 of hoger.

U kan zoeken naar boeken die een formaat hebben van een bepaalde grootte zoals dit:

- size:>1.1M zoekt boeken met een formaat groter dan 1.1MB

- size:<=1K zoekt boeken met een formaat kleiner dan of gelijk aan 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 zal boeken vinden met een willekeurige identificator.

- identifiers:false zal boeken vinden zonder identificatoren.

- identifiers:123 zoekt naar boeken van elk type met de waarde die 123 bevat.

- identifiers:=123456789 zoekt naar boeken met elk type die een waarde heeft die gelijk is 123456789.

- identifiers:=isbn: en identifiers:isbn:true vinden boeken met een type gelijk aan ISBN met iedere waarde

- identifiers:=isbn:false vindt boeken zonder type zoals ISBN.

- identifiers:=isbn:123 vindt boeken met type gelijk aan ISBN en waarde bevattend 123.

- identifiers:=isbn:=123456789 vindt boeken met type gelijk aan ISBN en waarde gelijk aan 123456789.

- identifiers:i:1 vindt boeken met een type dat een i bevat en een waarde van 1 bevatten.

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 De calibre sjabloontaal 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.

Voorbeelden:

- 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

Geavanceerde zoekdialoog

Zoekopdrachten opslaan
calibre laat u vaak gebruikte zoekopdrachten opslaan onder een speciale naam en deze opnieuw gebruiken met één klik. Maak een zoekopdracht aan door die ofwel in de zoekbalk in te geven of met de Label browser. Voer dan de naam in in het vak Opgeslagen Zoekopdrachten naast de Zoekbalk. Klik op het plus-icoon naast het vak opgeslagen zoekopdrachten om deze te bewaren.

Nu kunt u uw opgeslagen zoekopdracht vinden in de Label Browser onder Opgeslagen Zoekopdrachten. Een enkele klik laat u een complexe zoekopdracht makkelijk opnieuw uitvoeren zonder deze terug aan te maken.

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.

Virtuele bibliotheken

Een Virtuele Bibliotheek is een manier om te doen alsof uw calibre bibliotheek maar een paar boeken heeft in plaats van z’n hele collectie. Dit is een prima manier om uw grote verzameling boeken in kleinere hanteerbare stukken te verdelen. Om te leren hoe Virtuele bibliotheken te maken en gebruiken, zie de handleiding: Virtuele bibliotheken.

Temporarily marking books

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".

Raadt metadata op basis van bestandsnamen

Gewoonlijk haalt calibre metadata op uit het boekbestand. U kunt instellen dat metadata in plaats daarvan moeten worden gelezen uit de bestandsnaam via Voorkeuren → Import/Export → Boeken toevoegen → Lees metadata uit bestandinhoud.

You can also control how metadata is read from the filename using regular expressions (see Alles over het gebruik van reguliere expressies in calibre). In the Adding books section of the configuration dialog, you can specify a regular expression that calibre will use to try and guess metadata from the names of e-book files that you add to the library. The default regular expression is:

title - author

dat betekent, het programma gaat ervan uit dat alle tekens tot de eerste - tot de titel van het boek behoren en volgende tekens de naam van de auteur van het boek. Bijvoorbeeld, de bestandsnaam:

Foundation and Earth - Isaac Asimov.txt

wordt geïnterpreteerd als titel: Foundation and Earth en auteur: Isaac Asimov

Tip

Indien de bestandsnaam geen koppelteken bevat zal de bovenstaande reguliere expressie mislukken.

Boekgegevens

De Boekdetails tonen het omslag en de metadata van het huidige geselecteerde boek. Dat kan verborgen worden met de Layout knop rechtsonder in het hoofdvenster. De namen van de auteurs zichtbaar in het Boekdetails scherm zijn aanklikbaar, ze verwijzen u standaard naar de auteur z’n pagina in Wikipedia. Dit kunt u aanpassen door rechtsklikken op de auteursnaam en te kiezen Deze auteur beheren.

Als u metadata voor het boek downloadt, zal het Boekdetail-scherm automatisch een link tonen naar webpagina’s van het boek op Amazon, WorldCat enz. waar de metagegevens vandaan werden gehaald.

U kunt rechtsklikken op een individueel e-boekformaat in het Boekdetailscherm om het te verwijderen, te vergelijken met hun originele versies, te bewaren op schijf, te openen met een extern programma enz.

U kunt de omslag van het boek wijzigen door eenvoudig een afbeelding te slepen en neer te zetten op het Boekdetailscherm. Wilt u de omslagafbeelding bewerken met een extern programma, rechtsklik erop en kies Open met.

U kunt ook e-boekbestanden toevoegen aan het huidige boek door de bestanden op het Boekdetailscherm te slepen en neer te zetten.

Dubbelklikken op het Boekdetailscherm open het in een extra pop-upvenster.

Tenslotte kunt u precies aanpassen welke informatie getoond wordt in het Boekdetailscherm via Voorkeuren → Interface → Uitstralin & gevoel → Boekdetails.

Labelbrowser

Met de Labelbrowser kunt u gemakkelijk uw collectie doorzoeken op auteur/labels/reeksen/enz. Als u op een item klikt in de Labelbrowser, bijvoorbeeld de auteursnaam Isaac Asimov, toont de lijst met boeken rechts ervan alleen boeken van deze auteur. U kunt ook op categorienamen klikken. Bv. na het klikken op “Reeksen” worden alle boeken getoond in alle reeksen.

De eerste klik op een item zal de lijst van boeken beperken tot diegene die het item bevatten of er mee overeenkomen. Voortgaande op het voorbeeld hierboven, klikken op Isaac Asimov zal boeken van deze auteur tonen. Nogmaals klikken zal veranderen wat er getoond wordt, afhankelijk of het item kinderen heeft (zie subcategorieën en hiërarchische items hieronder). We gaan terug naar het Isaac Asimov voorbeeld, opnieuw op Isaac Asimov klikken zal de lijst beperken tot alle boeken niet geschreven door Isaac Asimov. Een derde klik zal de beperking verwijderen en alle boeken tonen. Door Ctrl of Shift ingedrukt te houden en te klikken op meerdere items, zullen beperkingen aangemaakt worden, gebaseerd op verschillende items. Bv. kan u Ctrl ingedrukt houden en op de labels Geschiedenis en Europe klikken om boeken over Europese geschiedenis te vinden. De Labelbrowser werkt door zoekexpressies te maken die automatisch toegevoegd worden aan de Zoekbalk. Kijken wat de Tag Browser creëert, is een goege manier om te leren hoe je simpele zoek expressies te maken.

Items in de Labelbrowser hebben gedeeltelijk gekleurde pictogrammen. De hoeveelheid kleur hangt af van de gemiddelde beoordeling van de boeken in deze categorie. Dus als de boeken van Isaac Asimov een gemiddelde van vier sterren hebben, is het pictogram voor Isaac Asimov in de LabelBrowser voor 4/5de gekleurd. U kunt met uw muisaanwijzer over het pictogram gaan om de gemiddelde beoordeling te bekijken.

De buiten-niveau items in de Labelbrowser, zoals Auteurs en Series, heten categorieën. U kan uw eigen categorieën creëren, Gebruikerscategorieën genaamd, handig om items te organiseren. Bv. u kan de Gebruikerscategorieën editor gebruiken (klik op de Configureren knop linksonder in de Labelbrowser en kies Beheer auteurs, series, enz. → Gebruikerscategorieën) om een Gebruikerscategorie te creëren genaamd Favorite Authors, zet dan de items voor uw favorieten in de categorie. Gebruikerscategorieën kunnen subcategorieën hebben. Bv. de Gebruikerscategorie Favorites.Authors is een subcategorie van Favorites. U kan ook Favorites.Series hebben, In dat geval zullen er twe subcategorieën zijn in Favorites. Subcategorieën creëert u door rechtsklikken op een Gebruikerscategorie en kiezen van Subcategorie toevoegen aan … en invoeren van de subcategorienaam of door gebruik van de Gebruikerscategorieën editor door invoeren van namen zoals het Favorieten voorbeeld boven.

U kunt Gebruikerscategorieën doorzoeken op dezelfde manier als ingebouwde categorieën door erop te klikken. Er zijn vier zoekmogelijkheden die de ronde maken door te klikken:

1. “alles dat overeenkomt met een item in de categorie” aangegeven door een enkel groen plus-teken.

2. “alles dat overeenkomt met een item in de categorie of de sub-categorieën” aangegeven door twee groene plus-tekens.

3. “alles dat niet overeenkomt met een item in de categorie” aangegeven door een enkel rood min-teken.

4. “alles dat niet overeenkomt met een item in de categorie of de sub-categorieën” aangegeven door twee rode min-tekens.

Het is ook mogelijk om hiërarchieën op te zetten binnen sommige tekst categorieën zoals labels, series en aangepaste kolommen. Deze hiërarchieën herkent u aan kleine driehoekjes, waardoor sub-items verborgen zijn. Om hiërarchieën van items in een categorie te gebruiken gaat u eerst naar Voorkeuren → Interface → Uitstraling & gevoel en voer de categoriena(a)m(en) in in het “Categorieën met hiërarchische items” vak. Als dit eenmaal gebeurd is, zullen items in deze categorie die punten bevatten getoond worden met de kleine driehoek. Bv. gaan we er van uit dat u een aangepaste kolom aanmaakt genaamd “Genre” en aangeeft dat deze hiërarchische items bevat. Als dit gebeurt is, zullen items zoals Mystery.Thriller en Mystery.Engels worden getoond als Mystery met ernaast de kleine driehoek. Klikken op de driehoek laat Thriller en Engels zien als sub-items. Lees ook Subgroepen van boeken beheren bijvoorbeeld “genre” voor meer informatie.

Hiërarchische items (items met kinderen) gebruiken dezelfde vier ‘klik-aan’ zoekopties als Gebruikerscategorieën. Items zonder kinderen gebruiken twee van de zoekopties: “alles overeenkomend” en “alles niet overeenkomend”.

U kunt items slepen en plaatsen in de Labelbrowser op de Gebruikerscategorieën om ze toe te voegen aan die categorie. Indien de bron een Gebruikerscategorie is, houdt de Shift ingedrukt tijdens het slepen om het item te verplaatsen naar de nieuwe categorie. U kunt ook boeken slepen en neerzetten van de boekenlijst op items in de Labelbrowser; neerzetten van een boek op een item zal dit automatisch toepassen voor de geplaatste boeken. Bv. een boek slepen op Isaac Asimov zal het boek instellen als een boek van Isaac Asimov. Het neerzetten op het label Geschiedenis zal het label Geschiedenis toevoegen aan de labels van het boek.

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.

U kunt instellen hoe items worden gesorteerd in de Labelbrowser met de knop Configureren onder links in de Labelbrowser. U kunt kiezen om te sorteren op naam, gemiddelde beoorderling of populariteit (populariteit is het aantal boeken met een item in uw bibliotheek; bijvoorbeeld de populariteit van Isaac Asimov is het aantal boeken in uw bibliotheek door Isaac Asimov).

Omslagenrooster

U kunt calibre de boekomslagen laten tonen in een raster in plaats van een lijst als u uw verzameling liever doorzoekt op omslagen. Het Raster wordt ingeschakeld door te klikken op de knop Layout rechtsonder in het calibre hoofdscherm. U kunt de grootte van de omslagen aanpassen en de achtergrond van het Raster via Vookeuren → Interface → Uitstraling & Gevoel  → Omslagraster. U kunt ook ieder veld onder de omslagen laten zien zoals titel of auteur of beoordeling of een aangepaste kolom die u zelf heeft gemaakt.

Omslagbrowser

Naast het hierboven beschreven Raster, kunt u calibre de omslagen ook laten tonen in een enkele rij. Dit wordt ingeschakeld via de Layout knop rechtsonder in het hoofdscherm. In Voorkeuren → Interface → Uiterlijk & Gedrag → Omslagbrowser kunt u het aantal getoonde omslagen wijzigen en zelfs de Omslagbrowser deze in een apart pop-upscherm laten zien.

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.

Snel-weergave

Soms wilt u een boek selecteren en snel een boekenlijst krijgen met dezelfde waarde in een categorie (auteurs, labels, uitgevers, series, enz.) als het huidig geselecteerde boek maar zonder het huidige zicht op de bibliotheek te veranderen. Dit kan met Snel Weergeven. Snel Weergeven opent ofwel een tweede venster of een paneel in de boekenlijst met een lijst boeken die overeenkomen met die bepaalde waarde. Bv. neem aan dat u een lijst wil zien met alle boeken met de auteur(s) van het huidige boek. Klik in de auteur cel waarin u geïnteresseerd bent en druk op de ‘Q’ toets of klik op het Snel Weergeven pictogram in het Layout onderdeel van het calibre venster. Een venster of paneel opent met al de auteurs voor dat boek links en alle boeken van de geselecteerde auteur rechts.

Enkele voorbeelden van snelweergave gebruik: zie snel welke andere boeken:

- pas een (enkele) label(s) toe op het huidig geselecteerde boek,

- zitten in dezelfde reeks als het huidige boek

- hebben dezelfde waardes in een persoonlijke kolom als het huidige boek

- zijn geschreven bij één van de auteurs van het huidige boek

- deel waardes in een aangepaste kolom

Er zijn twee keuzes voor waar de Snel Weergeven informatie verschijnt:

1. Het kan “niet-gedokt” openen: bovenop het calibre venster en blijft open tot u het expliciet sluit.

2. Het kan “gedokt” openen: als een paneel in het boekenlijst onderdeel van het calibre hoofdvenster.

U kan het paneel naar believen van gedokt naar niet-gedokt veranderen met de “Gedokt/Niet-gedokt” knop.

Het Snel Weergeven scherm kan open blijven en volgt de bewegingen in de boekenlijst. Bv. als u in de calibre bibliotheekweergave klikt op een categoriekolom (labels, series, auteurs, enz.) van een boek zal de Snel Weergeven venster inhoud veranderen om u in het linkerpaneel de waardes te tonen in die categorie voor het geselecteerde boek (bv. de labels voor dat boek). Het eerste item in die lijst wordt geselecteerd en Snel Weergeven toont u op het rechterpaneel alle boeken in uw bibliotheek die ie waarde gebruiken. Klik op een andere waarde in het linkerpaneel om de boeken te zien met die andere waarde.

Dubbelklik op een boek in het Snel Weergeven venster om dat boek te selecteren in de bibliotheek weergave. Dit past ook de items aan getoond in het Snel Weergeven scherm (het linkerpaneel) om de items te tonen in het nieuw geselecteerde boek.

Shift- of Ctrl- dubbelklik op een boek in het Snel Weergeven venster om de metadata bewerken dialoog te openen in het calibre venster. Het bewerkte boek zal Snel Weergegeven worden als u de metadata bewerken dialoog sluit.

U kunt zien of een kolom snel weergegeven kan worden door met uw muisaanwijzer over de kolomnaam te gaan en de tooltip voor dit kolomhoofd te bekijken. U kunt ook rechts te klikken op de kolomkop en bekijken of de optie “Snelweergave” wordt getoond in het menu; in dat geval is het kiezen van de optie Snelweergave hetzelfde als het indrukken van ‘Q’ van de huidge cel.

Opties (in Voorkeuren → Uitstraling & Gevoel → Snel Weergeven):

- Respecteer (of niet) de huidige Virtuele bibliotheek. Indien aangekruist toont Snel Weergeven enkel boeken in de huidige Virtuele bibliotheek. Standaard: respecteer Virtuele bibliotheken

- Verander de inhoud van het Snel Weergeven venster als de kolom veranderd in de boekenlijst met de pijltjestoetsen. Standaard: veranderingen met pijltjestoetsen niet volgen

- Verander de Snel “Weergegeven” kolom bij dubbelklikken van een cel in het Snel Weergeven venster . Anders wordt het boek veranderd maar de onderzochte kolom niet. Standaard: verander de kolom

- Verander de Snel “Weergegeven” kolom naar de huidige kolom bij indrukken van de return toets in het Snel Weergevenpaneel. Anders wordt het boek gewijzigd maar de onderzochte kolom niet. Standaard: verander de kolom.

- Kiezen welke kolommen getoond worden in het Snel Weergeven venster/paneel.

Taken

Het Takenpaneel toont het huidige aantal lopende taken. Taken zijn klussen die lopen in een apart proces. Ze bevatten het converteren van e-boeken en communicatie met uw reader. U kunt op het Takenpaneel klikken om de lijst van taken te bekijken. Als een taak voltooid is, kunt u een gedetailleerd log zien van deze taak door erop te dubbelklikken in de lijst. Dat is nuttig bij foutzoeken in taken die niet geheel gelukt zijn.

Toetsenbord sneltoetsen
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.

Notitie

Aandacht: De Calibre toetsenbord snelkoppeling heeft geen ‘modifier key’ nodig (Commando, Opties, Controle, Etc.), met uitzondering wanneer het is gemeld. Je hoeft alleen de letter toets in te drukken, bijv. E om aan te passen.

Toetsenbord snelkoppelingen voor het Calibre programma

Toetsenbord snelkoppeling

Actie

F2 (Enter for macOS)

Bewerk de metadata van het huidig geselecteerde veld in de boekenlijst.

A

Boeken toevoegen

Shift+A

Formaten toevoegen aan de geselecteerde boeken

C

Geselecteerde boeken converteren

D

Naar apparaat sturen

Del

Verwijder geselecteerde boeken

E

Bewerk metadata van geselecteerde boeken

G

Boeken downloaden

I

Toon boekgegevens

K

Bewerk inhoudsopgave

M

Geselecteerde records samenvoegen

Alt+M

Geselecteerde records samenvoegen, originelen bewaren

O

Bijbehorende map openen

P

Boeken bijschaven

S

Op schijf opslaan

T

Bewerk Boek

V

Tonen

Shift+V

Laatst gelezen boek tonen

Alt+V/Cmd+V for macOS

Specifiek formaat tonen

Alt+Shift+J

Schakel taaklijst

Alt+Shift+B

Schakel Omslagbrowser

Alt+Shift+D

Schakel Boekdetailpaneel

Alt+Shift+T

Schakel Labelbrowser

Alt+Shift+G

Schakel Omslagraster

Alt+A

Toon boeken bij dezelfde auteur als het huidige boek

Alt+T

Toon boeken met dezelfde labels als het huidige boek

Alt+P

Toon boeken met dezelfde uitgever als het huidige boek

Alt+Shift+S

Toon boeken uit dezelfde reeks als het huidige boek

/, Ctrl+F

Activeer de Zoekbalk

Shift+Ctrl+F

Open het Geavanceerde zoekvenster

Shift+Alt+F

Schakel de Zoekbalk

Esc

Huidige zoekterm wissen

Shift+Esc

Selecteer de boekenlijst

Ctrl+Esc

De Virtuele bibliotheek legen

Alt+Esc

Verwijder de extra restricties

Ctrl+*

Maak een tijdelijke Virtuele bibliotheek gebaseerd op de huidige zoekopdracht

Ctrl+Rechts

Selecteer de volgende Virtuele bibliotheek tab

Ctrl+Left

Selecteer de vorige Virtuele bibliotheek tab

N of F3

Zoek het volgende boek dat overeen komt met de huidige zoekopdracht (werkt alleen indien zoek accentueren aan staat in de zoek voorkeuren)

Shift+N of Shift+F3

Zoek het vorige boek dat overeen komt met de huidige zoekopdracht (werkt alleen indien zoek accentueren aan staat in de zoek voorkeuren)

Ctrl+D

Metadata en omslagafbeeldingen downloaden

Ctrl+R

Herstart Calibre

Ctrl+Shift+R

Herstart in debug modus

Shift+Ctrl+E

Voeg een leeg boek toe aan Calibre

Ctrl+M

Wissel gemarkeerd/ongemarkeerde status op geselecteerde boeken

Ctrl+/ or Ctrl+Alt+F

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

Q

Toon de Snelle weergave pop-up om boeken te tonen met gerelateerde reeksen/labels/enz.

Shift+Q

Selecteer het geopende Snel weergave paneel

Shift+S

Zoek in het Snel weergave paneel

F5

De huidige volgorde opnieuw toepassen

Ctrl+Q

Calibre afsluiten

X

Schakel auto scrollen van de boekenlijst

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

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Uw favoriete nieuws website toevoegen

Uw favoriete nieuws website toevoegen
calibre heeft een krachtig, flexibel en gemakkelijk te gebruiken kader voor downloaden van nieuws van internet en converteren naar een e-boek. Het volgende toont u aan de hand van voorbeelden, hoe u nieuws van verschillende websites kunt krijgen.

Om een goed begrip te krijgen van het gebruik van het raamwerk, volgt u de voorbeelden in de onderstaande volgorde:

- Compleet automatisch ophalen

- De Calibre blog

- bbc.co.uk

- Het ophaalproces aanpassen

- Gebruik van de print versie van bbc.co.uk

- Artikel stijlen vervangen

- Splitsen en verdelen

- Werkend voorbeeld

- Tips voor ontwikkelen van nieuwe recepten

- Verder lezen

- API documentatie

Compleet automatisch ophalen

Als uw nieuwsbron eenvoudig genoeg is, kan Calibre deze mogelijk volledig automatisch ophalen, het enige wat u hoeft te doen is de URL opgeven. Calibre verzamelt alle informatie die nodig is om een nieuwsbron te downloaden naar een :term :recept. Om Calibre over een nieuwsbron te vertellen, moet je een :term :recept ervoor maken. Laten we enkele voorbeelden bekijken:

De Calibre blog

De calibre blog is een blog met posts die veel nuttige calibre kenmerken beschrijven op een simpele en toegankelijke manier voor nieuwe calibre gebruikers. Om deze blog te downloaden als e-boek, vertrouwen we op de RSS feed of the blog:

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

Ik vond de RSS URL door te zoeken in “Abonneren op” onderaan de blogpagina en kiezen voor Posts → Atom. Om calibre de feeds te laten downloaden en converteren naar een e-boek, moet u rechtsklikken op de Nieuws ophalen knop en dan het Aangepaste nieuwsbron toevoegen menu item en dan de Nieuw Recept knop. Een dialoog zoals onder getoond verschijnt.

Voer eerst Calibre Blog in in het Recept titel veld. Dit zal de titel van het e-boek zijn dat wordt gecreëerd van het artikel in de feeds boven.

De volgende twee velden (Oudste artikel en Max. aantal artikelen) geven u enige controle over hoeveel artikelen moeten gedownload worden van elke feed, en ze zijn zo klaar als een klontje.

Om de feeds aan het recept toe te voegen, voer de feedtitel en de feed URL in en klik op de Feed toevoegen knop. Als u de feed hebt toegevoegd, klik gewoon op de Opslaan knop en u bent klaar! Sluit de dialoog.

Om uw nieuw recept te testen, klik op de Nieuws ophalen knop en in hat Aangepaste nieuwsbronnen sub-menu klik op calibre Blog. Na een paar minuten verschijnt het nieuw gedownloade e-boek met blogposts in de hoofdbibliotheekweergave (als uw reader aangesloten is, wordt het op de reader gezet i.p.v. in de bibliotheek). Selecteer het en raak de Tonen knop om te lezen!

De reden dat dit zo goed werkt en met zo weinig inspanning is dat de blog volledige inhoud RSS feeds voorziet, m.a.w. de artikel inhoud is ingebed in de feed zelf. Voor de meeste bronnen die nieuws voorzien op deze wijze, met volledige inhoud feeds, moet u niet meer inspanning doen om ze te converteren naar e-boeken. Nu een kijkje naar een nieuwsbron die geen volledige inhoud feeds levert. In zo’n feeds is het volledige artikel een webpagina en bevat de feed enkel een link met een korte samenvatting.

bbc.co.uk

Laten we de volgende twee feeds van De BBC proberen:

1. Nieuws Front Pagina: https://newsrss.bbc.co.uk/rss/newsonline_world_edition/front_page/rss.xml

2. Wetenschap/Natuur: https://newsrss.bbc.co.uk/rss/newsonline_world_edition/science/nature/rss.xml

Volg de procedure beschreven in De Calibre blog boven om een recept te creëren voor The BBC (met de feeds boven). Kijkend naar het gedownloade e-boek zien we dat calibre een lovenswaardige inspanning heeft gedaan om enkel de inhoud waar u om geeft te halen uit elk artikel z’n webpagina. Het proces is echter niet perfect. Soms is er nog ongewenste inhoud zoals menu’s en navigatie hulpjes of het verwijderde inhoud waar het had moeten afblijven, zoals artikelkoppen. Voor het perfecte proces zullen we het moeten aanpassen zoals beschreven in het volgende onderdeel.

Het ophaalproces aanpassen

Als u het downloadproces wil perfectioneren of inhoud downloaden van een zeer complexe webstek, kan u gebruik maken van al de kracht en soepelheid van het recept raamwerk. Om dat te doen, klik in de Aangepaste nieuwsbronnen dialoog gewoon op de Schakel over naar Geavanceerde modus knop.

De gemakkelijkste en dikwijls productiefste aanpassing is de afdrukversie van de online artikelen gebruiken. De afdrukversie bevat gewoonlijk veel minder rommel en vertaalt veel vlotter naar een e-boek. Laten we de afdrukversie van de artikelen van De BBC proberen.

Gebruik van de print versie van bbc.co.uk

De eerste stap is kijken naar het e-boek dat we downloadden van bbc.co.uk. Aan het einde van elk artikel in het e-boek is een kleine blurb die toont waar het artikel werd gedownload. Kopieer en plak die URL in een browser. Zoek op de webpagina van het artikel voor een link die wijst naar de “Afdrukversie”. Klik erop om de afdrukversie van het artikel te zien. Ziet er veel strakker uit! Vergelijk nu de twee URL’s. Bij mij waren het:

Artikel URL

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

Print versie URL

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

Het lijkt erop dat om de afdrukversie te krijgen, we elke artikel URL moeten laten voorafgaan door:

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

In de Geavanceerde modus van de Aangepaste nieuwsbronnendialoog zou u iets moeten zien zoals (denk eraan om het The BBC recept te selecteren voor naar geavanceerde modus over te schakelen):

U ziet dat de velden van de Basis modus op een rechttoe rechtaan manier zijn vertaald naar Python code. We moeten instructies toevoegen aan dit recept om de afdrukversie van het artikel te gebruiken. Al wat nodig is, is toevoegen van volgende twee regels:

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

Dit is Python, dus inspringing is belangrijk. Na toevoegen van de lijnen zou het er moeten uitzien als:

Boven definieert def print_version(self, url) een methode die door calibre wordt aangeroepen voor elk artikel. url is de URL van het originele artikel. Wat print_version doet, is die url nemen en vervangen door de nieuwe URL die wijst naar de afdrukversie van het artikel. Om te leren over Python [https://www.python.org] see the tutorial [https://docs.python.org/tutorial/].

Klik nu op de Toevoegen/updaten recept knop en uw wijzigingen worden opgeslagen. Download het e-boek opnieuw. U zou nu een veel betere versie moeten hebben. Een van de problemen met de nieuwe versie is dat de lettertypes op de webpagina van de afdrukversie te klein zijn. Dit wordt ge-auto-fixxed bij converteren naar een e-boek maar zelfs daarna is de lettertypegrootte van menu’s en navigatiebalk te groot vergeleken met de artikeltekst. Om dit te in orde te brengen, doen we nog wat aanpassingen in het volgende onderdeel.

Artikel stijlen vervangen

In de vorige sectie zagen we dat de lettertypegrootte voor artikelen van de afdrukversie van The BBC te klein was. Op de meeste webstekken, The BBC inbegrepen, is deze lettertypegrootte ingesteld door de CSS stijlbladen. We kunnen dit ophalen uitschakelen door toevoegen van de regel:

no_stylesheets = True

Het recept ziet er als volgt uit:

De nieuwe versie ziet er goed uit. Als u een perfectionist bent, dan zult u het volgende gedeelte willen lezen, deze gaat over het aanpassen van de gedownloade content.

Splitsen en verdelen
calibre bevat zeer krachtige en flexibele mogelijkheden om gedownloadede inhoud te manipuleren. Om enkele hiervan te tonen, laten we nogmaals een kijkje nemen bij onze oude vriend het The BBC recept. Bij kijken naar de broncode (HTML) van een paar artikelen (afdrukversie), zien we dat ze een voettekst hebben zonder bruikbare informatie, gevat in

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

Dit kan worden verwijderd door toevoeging van:

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

naar het recept. Tenslotte, laten we wat CSS dat we eerder uitschakelden, vervangen met onze eigen CSS geschikt voor converteren naar een e-boek:

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

Met deze toevoegingen is ons recept “productiekwaliteit” geworden.

Deze recept verkent enkel het tipje van de ijsberg als het over de kracht van calibre gaat. Om meer mogelijkheden van calibre te ontdekken, onderzoeken we een ingewikkelder voorbeeld uit het echte leven in de volgende sectie.

Werkend voorbeeld

Een redelijk complex real life voorbeeld dat meer van de API van BasicNewsRecipe toont is het recept voor 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'))

We zien verscheidene nieuwe kenmerken in dit recept. Om te beginnen:

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

Dit stelt de getoonde tijd op de voorpagina van het gecreëerde e-boek in op het formaat, Day, Day_Number Month, Year. Kijk op timefmt.

Dan zien we een groep richtlijnen voor het opschonen van de gedownloade HTML:

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

Deze verwijderen alles voor de eerste <h1> tag en alle na de eerste tag wiens id is footer. Bekijk remove_tags, remove_tags_before, remove_tags_after.

De volgende interessante functie is:

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

needs_subscription = True zegt calibre dat dit recept een gebruikersnaam en wachtwoord nodig heeft voor content toegang. Dit laat calibre vragen naar een gebruikersnaam en wachtwoord telkens u dit recept probeert te gebruiken. De code in calibre.web.feeds.news.BasicNewsRecipe.get_browser() doet het inloggen op de NYT website. Eens ingelogd zal calibre dezelfde ingelogde browser instance gebruiken om alle inhoud op te halen. Bekijk mechanize [https://mechanize.readthedocs.io/en/latest/] om de code in get_browser te begrijpen.

Het volgende nieuwe kenmerk is de calibre.web.feeds.news.BasicNewsRecipe.parse_index() methode. Zijn job is naar https://www.nytimes.com/pages/todayspaper/index.html te gaan en de lijst met artikelen op te halen die verschijnt in todays krant. Hoewel complexer dan gewoon RSS te gebruiken, het recept creëert een e-boek dat sterk lijkt op de krant van de dag. parse_index maakt zwaar gebruik van BeautifulSoup [https://www.crummy.com/software/BeautifulSoup/bs4/doc/] om de webpagina van de dagelijkse krant te verwerken. U kan ook andere, modernere verwerkers gebruiken als BeautifulSoup u niet bevalt. calibre komt met lxml [https://lxml.de/] en html5lib [https://github.com/html5lib/html5lib-python], welke de aanbevolen verwerkers zijn. Om ze te gebruiken, vervang de aanroep van index_to_soup() door het volgende:

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)

Het laatste, nieuwe kenmerk is de calibre.web.feeds.news.BasicNewsRecipe.preprocess_html() methode. Ze kan gebruikt worden voor willekeurige transformaties op elke gedownloade HTML pagina. Hier wordt ze gebruikt om de advertenties die de nytimes toont voor elk artikel te omzeilen.

Tips voor ontwikkelen van nieuwe recepten

De beste manier om nieuwe recepten te ontwikkelen is met de opdrachtregel. Creëer het recept in uw favoriete Python editor en sla het op in een bestand, bv. myrecipe.recipe. De .recipe extensie is vereist. U kan inhoud downloaden met dit recept met het commando:

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

Het commando e-boek-converteren downloadt alle webpagina’s en slaat ze op in het EPUB bestand mijnrecept.epub. De -vv optie laat e-boek-converteren een hoop informatie uitspuwen over wat het doet. De e-boek-converteren-recept-invoer --test optie laat het juist een paar artikels downloaden van maximaal twee feeds. Bijkomend plaatst e-boek-converteren de gedownloade HTML in de debug/input map, waar debug de map is die u specificeerde in de e-boek-converteren --debug-pipeline optie.

Als de download compleet is, kan u de gedownloade HTML bekijken door het bestand debug/input/index.html in een browser te openen. Als u tevreden bent dat de download en voorbewerking correct gebeurt, kan e-boeken genereren in verschillende formaten zoals onder getoond:

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

Als tevreden bent met uw recept en u vindt dat er genoeg vraag is om het in te voegen in de set met ingebouwde recepten, post dan uw recept in het calibre recipes forum [https://www.mobileread.com/forums/forumdisplay.php?f=228] om het te delen met andere gebruikers.

Notitie

Op macOS bevinden de commandoregelprogramma’s zich in de calibre-bundel, bijvoorbeeld, als u calibre hebt geïnstalleerd in /Applications bevinden de commandoregelprogramma’s zich in /Applications/calibre.app/Contents/MacOS/.

Zie ook

ebook-convert

De opdrachtregelinterface voor alle e-boekconversies.

Verder lezen

Om meer te leren over schrijven van geavanceerde recepten met enkele van de mogelijkheden beschikbaar in BasicNewsRecipe moet u volgende bronnen raadplegen:

API documentation

Documentatie over de BasicNewsRecipe class en al zijn belangrijke methodes en velden.

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

De broncode van BasicNewsRecipe

Ingebouwde recepten [https://github.com/kovidgoyal/calibre/tree/master/recipes]

De broncode voor de ingebouwde recepten die bij Calibre horen

Het calibre recepten forum [https://www.mobileread.com/forums/forumdisplay.php?f=228]

Massa’s ervaren calibre recept schrijven hier.

API documentatie

API documentatie voor receptenBasicNewsRecipe

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

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Uw favoriete nieuws website toevoegen »
- API documentatie voor recepten

API documentatie voor recepten

De API voor het schrijven van recepten is bepaald door de BasicNewsRecipe
class calibre.web.feeds.news.BasicNewsRecipe(options, log, progress_reporter)[broncode]

Basisklasse die logica bevat die nodig is in alle recepten. Door steeds meer functionaliteit in deze klasse te overschrijven, kunt u steeds meer aangepaste/krachtige recepten maken. Voor een handleiding introductie tot het maken van recepten, zie Uw favoriete nieuws website toevoegen.

abort_article(msg=None)[broncode]

Gebruik deze methode in een van de voorbewerk-methoden om de download voor het huidige artikel te beëindigen. Handig om artikelen over te slaan die ongepaste inhoud bevatten, zoals pure video-artikelen.

abort_recipe_processing(msg)[broncode]

Zorgt ervoor dat het downloadsysteem voor recepten het downloaden van recepten afbreekt, en er een simpel feedback-bericht aan de gebruiker wordt weergegeven.

add_toc_thumbnail(article, src)[broncode]

Gebruik dit vanuit populate_article_metadata met het src attribuut van een <img>label uit het artikel dat geschikt is om te gebruiken als de miniatuur die het artikel in de inhoudsopgave weergeeft. Of de miniatuur daadwerkelijk wordt gebruikt, is apparaatafhankelijk (momenteel alleen in gebruik bij de Kindles). Merk op dat de bedoelde afbeelding er een moet zijn die succesvol is gedownload, anders wordt deze genegeerd.

classmethod adeify_images(soup)[broncode]

Als je recept dat is geconverteerd naar EPUB problemen heeft met afbeeldingen bekeken in Adobe Digital Editions, gebruik je deze methode vanuit postprocess_html().

canonicalize_internal_url(url, is_link=True)[broncode]

Retourneer een set canonieke representaties van `` url``. De standaardimplementatie gebruikt alleen de hostnaam van de server en het pad van de URL, waarbij alle zoekparameters, fragmenten, enzovoort worden genegeerd. De canonieke weergaven moeten uniek zijn voor alle URL’s voor deze nieuwsbron. Als dat niet het geval is, kunnen interne links onjuist worden opgelost.

Parameters:

is_link – Is Waar als de URL afkomstig is van een interne link in een HTML-bestand. Onwaar als de URL de URL is die werd gebruikt om een artikel te downloaden.

cleanup()[broncode]

Gebruikt nadat alle artikelen zijn gedownload. Gebruik het om op te schonen, zoals het afmelden van abonnementssites, enz.

clone_browser(br)[broncode]

Kloon de browser br. Gekloonde browsers worden gebruikt voor multi-threaded downloads, omdat mechaniseren niet thread safe is. De standaard kloneringsroutines moeten de meeste browseraanpassingen vastleggen, maar als u iets exotisch in uw recept doet, moet u deze methode in uw recept overschrijven en handmatig klonen.

Gekloonde browserinstances gebruiken standaard dezelfde, thread-safe CookieJar, tenzij u aangepaste cookieverwerking hebt ingesteld.

default_cover(cover_file)[broncode]

Maak een algemene omslag voor recepten die geen omslag hebben

download()[broncode]

Download en verwerk alle artikelen uit de feeds in dit recept. Deze methode moet slechts één keer worden aangeroepen voor een bepaalde receptinstantie. Meer dan één keer aanroepen zal leiden tot ongedefinieerd gedrag. :return: Path to index.html

extract_readable_article(html, url)[broncode]

Haalt inhoud van hoofdartikel uit ‘html’, ruimt op en retourneert als een (article_html, extracted_title) tupel. Gebaseerd op het oorspronkelijke leesbaarheidsalgoritme van Arc90.

get_article_url(article)[broncode]

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)[broncode]

Geef een browserinstantie terug gebruikt om documenten van internet op te halen. Standaard geeft het een mechanize <https://github.com/jjlee/mechanize> _ browserinstantie terug die cookies ondersteunt, robots.txt negeert, vernieuwingen verwerkt en een mozilla firefox user-agent heeft.

Als uw recept vereist dat u eerst inlogt, overschrijf dan deze methode in uw subklasse. Bijvoorbeeld: de volgende code wordt gebruikt in het New York Times recept om in te loggen voor volledige toegang

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()[broncode]

Retourneer een URL naar de omslagafbeelding voor deze editie of` Geen. Standaard retourneert het de waarde van het lid self.cover_url dat normaal` Geen` is. Als u wilt dat uw recept een omslag voor het e-book download, vervangt u deze methode in uw subklasse of stelt u de lid-variabele self.cover_url in voordat deze methode wordt aangeroepen.

get_extra_css()[broncode]

Retourneert standaard self.extra_css. Negeren als je de extra_css programmatisch wilt genereren.

get_feeds()[broncode]

Retourneer een lijst met RSS-feeds om op te halen voor dit profiel. Elk element van de lijst moet een 2-elementen-tupel van het formaat (titel, URL) zijn. Als de titel Geen of een lege tekenreeks is, wordt de titel uit de feed gebruikt. Deze methode is handig als uw recept een aantal bewerkingen moet uitvoeren om uit te zoeken welke feeds moeten worden gedownload. Als dit het geval is, overschrijft dit in uw subklasse.

get_masthead_title()[broncode]

Overschrijven in subklasse om iets anders dan de recept-titel te gebruiken

get_masthead_url()[broncode]

Retourneer een URL naar de masthead-afbeelding voor deze editie of` Geen. Standaard retourneert het de waarde van het lid self.masthead_url dat normaal` Geen` is. Als u wilt dat uw recept een masthead voor het e-book download, vervangt u deze methode in uw subklasse of stelt u de lid-variabele self.masthead_url in voordat deze methode wordt aangeroepen. Masthead-afbeeldingen worden gebruikt in Kindle MOBI-bestanden.

get_obfuscated_article(url)[broncode]

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.

Deze methode is meestal nuttig voor sites die het moeilijk maken om automatisch toegang te krijgen tot artikelinhoud.

get_url_specific_delay(url)[broncode]

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.

Returns:

A floating point number, the delay in seconds.

classmethod image_url_processor(baseurl, url)[broncode]

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)[broncode]

Gemaksmethode die een URL naar de indexpagina brengt en een BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/bs4/doc> _ terug geeft.

url_or_raw: ofwel een URL of de gedownloade indexpagina als een tekenreeks

is_link_wanted(url, tag)[broncode]

Retourneer Waar als de link moet worden gevolgd of anders Onwaar. Hiermee wordt standaard NotImplementedError gezet waardoor de downloader deze negeert.

Parameters:

- url – De te volgen URL

- tag – Het label waarvan de URL is afgeleid

parse_feeds()[broncode]

Maak een lijst met artikelen uit de lijst met feeds geretourneerd door BasicNewsRecipe.get_feeds(). Retourneer een lijst met Feed objecten.

parse_index()[broncode]

Deze methode moet worden geïmplementeerd in recepten die een website verwerken in plaats van feeds om een lijst met artikelen te genereren. Typische toepassingen zijn voor nieuwsbronnen met een “Printeditie” -webpagina met een overzicht van alle artikelen in de huidige gedrukte editie. Als deze functie is geïmplementeerd, wordt deze gebruikt in plaats van BasicNewsRecipe.parse_feeds().

Het moet een lijst retourneren. Elk element van de lijst moet een 2-elementen tupel zijn van de vorm ('feed title', lijst van artikelen).

Elke lijst met artikelen moet woordenboeken bevatten met de vorm:

{
'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.
}

Als voorbeeld zie het recept voor het downloaden van The Atlantic. Daarnaast kunt u ‘author’ toevoegen voor de auteur van het artikel.

Als u de verwerking om een of andere reden wilt afbreken en Calibre aan de gebruiker een eenvoudig bericht in plaats van een fout wilt laten tonen, roep dan meth: abort_recipe_processing aan.

populate_article_metadata(article, soup, first)[broncode]

Wordt aangeroepen wanneer elke HTML-pagina die bij het artikel hoort wordt gedownload. Bedoeld om te worden gebruikt om artikelmetadata te krijgen zoals auteur / samenvatting / enz. van de bewerkte HTML (soep).

Parameters:

- article – Een object van klasse Calibre.web.feeds.Article. Als u de samenvatting wijzigt, vergeet dan niet om ook de text_summary te wijzigen

- soup – Bewerkte HTML behorend bij dit artikel

- first – Waar als de bewerkte HTML de eerste pagina van het artikel is.

postprocess_book(oeb, opts, log)[broncode]

Voer de benodigde nabewerking uit op het bewerkte gedownloade e-boek.

Parameters:

- oeb – Een OEBBoek object

- opts – Omzet opties

postprocess_html(soup, first_fetch)[broncode]

Deze methode wordt aangeroepen met de bron van elk gedownload HTML bestand, nadat het is bewerkt voor koppelingen en afbeeldingen. Het kan worden gebruikt om willekeurig krachtige post-processing uit te voeren op de HTML. Het moet soup teruggeven na bewerking.

Parameters:

- soup – Een BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/bs4/doc> _ instantie die de gedownloade HTML bevat.

- first_fetch – Waar als dit de eerste pagina van een artikel is.

preprocess_html(soup)[broncode]

Deze methode wordt aangeroepen met de bron van elk gedownload HTML bestand, voor dat het is bewerkt voor koppelingen en afbeeldingen. Het wordt aangeroepen na het opschonen zoals gevraagd door remove_tags enz. Het kan worden gebruikt om willekeurig krachtige post-processing uit te voeren op de HTML. Het moet soup teruggeven na bewerking.

soup: Een BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/bs4/doc> _ instantie die de gedownloade HTML bevat.

preprocess_image(img_data, image_url)[broncode]

Voer enige bewerking uit op gedownloade afbeeldingsgegevens. Dit wordt aangeroepen voor de onbewerkte gegevens voordat enige formaat wijziging is gedaan. Moet de verwerkte onbewerkte gegevens retourneren. Retourneer None om de afbeelding over te slaan.

preprocess_raw_html(raw_html, url)[broncode]

Deze methode wordt aangeroepen met de bron van elk gedownload HTML bestand, voordat het is bewerkt in een object-boom. raw_html is een unicode-tekenreeks die de onbewerkte HTML vertegenwoordigt die is gedownload van internet. url is de URL van waaruit de HTML is gedownload.

Merk op dat deze methode werkt * vóór * preprocess_regexps.

Deze methode moet de bewerkte raw_html retourneren als een unicode-object.

classmethod print_version(url)[broncode]

Neem een `url` wijzend naar de webpagina met artikelinhoud en retourneer de URL wijzend naar de gedrukte versie van het artikel. Doet standaard niets. Bijvoorbeeld:

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

publication_date()[broncode]

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)[broncode]

Deze methode wordt aangeroepen met de bron van elk gedownload HTML bestand, voordat een van de opschoonattributen zoals remove_tags, keep_only_tags worden toegepast. Merk op dat preprocess_regexps al is toegepast. Het is bedoeld om het recept toe te staan advertentiepagina’s over te slaan. Als de soep een advertentiepagina vertegenwoordigt, retourneert u de HTML-code van de echte pagina. Anders retourneert u Geen.

soup: Een BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/bs4/doc> _ instantie die de gedownloade HTML bevat.

sort_index_by(index, weights)[broncode]

Gemaksmethode om de titels in index te sorteren volgens` gewichten`. index wordt op zijn plaats gesorteerd. Retourneert index.

index: Een lijst van titels.

gewichten: een woordenboek dat gewichten toewijst aan titels. Als titels in de index niet in ‘gewichten’ voorkomen, wordt aangenomen dat ze een gewicht van 0 hebben.

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

Gemaksmethode om een `BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/b43/doc>` _ Tag te nemen en de tekst er recursief uit te halen, inclusief eventuele CDATA-secties en alt tagattributen. Geeft een mogelijk lege Unicode string terug.

use_alt: Als` True` probeer het alt-attribuut te gebruiken voor labels die geen tekstuele inhoud hebben
tag: BeautifulSoup [https://www.crummy.com/software/BeautifulSoup/bs4/doc/] Tag

articles_are_obfuscated = False

Stel in op True en implementeer get_obfuscated_article() om websites te beheren die het moeilijk maken om inhoud te schrapen.

auto_cleanup = False

Haal automatisch alle tekst uit de gedownloade artikelpagina’s. Gebruikt de algoritmen van het leesbaarheidsproject. Als u dit instelt op True, betekent dit dat u zich geen zorgen hoeft te maken over het handmatig opschonen van de gedownloade HTML (hoewel handmatige opschoning altijd beter is).

auto_cleanup_keep = None

Geef elementen op die het algoritme voor automatische opruiming nooit mag verwijderen. De syntaxis is een XPath-uitdrukking. Bijvoorbeeld:

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

Indien Waar dan is de navigatiebalk gecentreerd, anders is deze links uitgelijnd

compress_news_images = False

Stel dit in op Onwaar om alle parameters voor schaling en compressie te negeren en afbeeldingen onveranderd door te geven. Als Waar en de andere compressieparameters op hun standaardwaarden gelaten, worden JPEG afbeeldingen geschaald om in de schermafmetingen ingesteld door het uitvoerprofiel te passen en maximaal tot (w * h)/16 gecomprimeerd waarbij w x h de geschaalde afbeeldingsafmetingen zijn .

compress_news_images_auto_size = 16

De factor gebruikt bij automatisch comprimeren van JPEG afbeeldingen. Indien ingesteld op Geen, is automatische compressie uitgeschakeld. Anders worden afbeeldingen verkleind naar (w*h)/compress_news_images_auto_size bytes indien mogelijk door verlaging kwaliteitsniveau, waarbij w x h de afbeeldingsdimensies in pixels zijn. Minimale JPEG-kwaliteit is 5/100, deze beperking wordt dus mogelijk niet gehaald. Deze parameter kan genegeerd worden door de parameter compress_news_images_max_size die een vaste maximumgrootte voor afbeeldingen biedt. Merk op dat als u scale_news_images_to_device inschakelt, de afbeelding eerst geschaald wordt en dan de kwaliteit verlaagd totdat de grootte minder is dan (w * h)/factor, waarbij w en h nu de afmetingen van de geschaalde afbeelding zijn. Met andere woorden, deze compressie gebeurt na het schalen.

compress_news_images_max_size = None

Stel JPEG-kwaliteit in zodat afbeeldingen de gegeven grootte (in KBytes) niet overschrijden. Indien ingesteld, negeert deze parameter automatische compressie via compress_news_images_auto_size. De minimale JPEG-kwaliteit is 5/100, deze beperking wordt dus mogelijk niet gehaald.

conversion_options = {}

Receptspecifieke opties om de conversie van de gedownloade content naar een e-book te sturen. Deze zullen elke door gebruiker of plugin opgegeven waarden overschrijven, dus alleen gebruiken als dit absoluut noodzakelijk is. Bijvoorbeeld:

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

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

Standaard wordt de omslagafbeelding teruggegeven door get_cover_url() gebruikt als omslag voor het tijdschrift. Dit negeren in uw recept zegt calibre de gedownloade omslag in een frame weer te geven waar breedte en hoogte zijn uitgedrukt als een percentage van de gedownloade omslag. cover_margins = (10, 15, ‘#ffffff’) geeft de hoes een witte marge van 10px links en rechts, 15px boven en onder. Kleurnamen gedefinieerd hier [https://www.imagemagick.org/script/color.php]. Merk op dat om welke reden dan ook wit niet altijd werkt in Windows. Gebruik in plaats daarvan #ffffff

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 = ''

Een paar regels die de inhoud beschrijven die door dit recept wordt gedownload. Dit wordt voornamelijk gebruikt in een GUI die een lijst met recepten aanbiedt.

encoding = None

Geef een overschrijf-codering op voor sites met een onjuiste karakterset-specificatie. De meest gebruikelijke is ‘latin1` opgeven en “cp1252” gebruiken. Als None, probeer de codering te detecteren. Als het een aanroepbare is, wordt deze aangeroepen met twee argumenten: het receptobject en de bron die moet worden gedecodeerd. Het moet de gedecodeerde bron retourneren.

extra_css = None

Specificeer elke extra CSS die toegevoegd moet worden aan gedownloadde HTML. Het wordt ingevoegd in <style> tags, net voor de sluit </head> tag daarbij alle CSS negerend behalve wat is aangegeven met gebruik van het stijl attribuut bij individuele HTML tags. Merk op dat als u programmatisch de extra_css wilt genereren, negeer de get_extra_css() methode. Bv.:

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

feeds = None

Lijst met feeds om te downloaden. Kan zowel [url1, url2, ...] als [('title1', url1), ('title2', url2),...] zijn

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']

verwijdert alle URLs met ads.doubleclick.net erin.

Maar één van BasicNewsRecipe.match_regexps of BasicNewsRecipe.filter_regexps mag gedefinieerd zijn.

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

Negeer dubbels van artikels die in meermaals aanwezig zijn. Een dubbel artikel is een artikel dat dezelfde titel en/of URL heeft. Om artikelen met dezelfde titel te negeren, stel dit in op:

ignore_duplicate_articles = {'title'}

Om in plaats daarvan URL’s te gebruiken, stel het in op:

ignore_duplicate_articles = {'url'}

Om te matchen op titel of URL, stel het in op:

ignore_duplicate_articles = {'title', 'url'}

keep_only_tags = []

Behoud enkel de gespecificeerde tags en hun kinderen. Voor het formaat om een tag te specificeren, zie BasicNewsRecipe.remove_tags. Als deze lijst niet leeg is, wordt de <body> tag geleegd en hervuld met de tags die overeenkomen met de vermeldingen in deze lijst. Bv.:

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

behoud enkel tags die een id attribuut hebben van “content” of “heading”.

language = 'und'

De taal van het nieuws. Moet een ISO-639 code zijn, twee of drie karakters lang

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 = []

Lijst met reguliere expressies die bepaalt welke links te volgen. Wordt genegeerd indien leeg. Enkel gebruikt als is_link_wanted niet geïmplementeerd is. Bv.:

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

komt overeen met alle URLs die page=some number becatten.

Maar één van BasicNewsRecipe.match_regexps of BasicNewsRecipe.filter_regexps mag gedefinieerd zijn.

max_articles_per_feed = 100

Maximaal aantal artikelen te downloaden van elke feed. Dit is vooral bruikbaar voor feeds die geen artikeldatum hebben. Voor de meeste feeds moet u BasicNewsRecipe.oldest_article gebruiken

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)

Minimaal benodigde Calibre versie om dit recept te gebruiken

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

Volgorde van artikelen omdraaien in iedere 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

Maximum aantal tekens om te gebruiken in korte omschrijving

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 voor ophalen van bestanden van de server in seconden

title = 'Onbekende nieuwsbron'

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.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- De e-boek viewer

De e-boek viewer
calibre bevat een ingebouwde e-boek viewer die alle belangrijke e-boek formaten kan lezen. De e-boek viewer is zeer aanpasbaar en heeft veel geavanceerde functies.

- Starten van de e-boek viewer

- Door een e-book navigeren

- Tekst highlighten

- Lees hardop

- In de tekst zoeken

- Links volgen met enkel het toetsenbord

- De look and feel van de leeservaring personalizeren

- Opzoeken in woordenboek

- Kopieer tekst en afbeeldingen

- Inzoomen op afbeeldingen

- Toetsenbord sneltoetsen

- Niet-opnieuw stroombare inhoud

- Uw boek ontwerpen om goed te werken in calibre viewer

Starten van de e-boek viewer

U kunt ieder boek bekijken in uw calibre-bibliotheek door het boek te selecteren en de knop Bekijken te drukken. Hiermee wordt het boek geopend in de lezer. U kunt de lezer ook inschakelen vanaf het Start-menu in Windows. In macOS, kunt U het vastzetten in het dock en het van daaruit starten. In Linux you kunt U de starter in het desktop menu gebruiken of het commando command ebook-viewer gebruiker.

Door een e-book navigeren

U kunt in een boek ‘pagina’s omslaan’ door:

- Klikken in de linker- of rechtermarge of in de pagina met de muis

- Drukken op :kbd: spatiebalk, :kbd:` pagina omhoog`, :kbd: pagina omlaag of pijltoetsen

- Op een touchscreen tikken op de tekst of naar links en rechts vegen

U heeft toegang tot de lezerbediening via:

- Rechtsklikken op de tekst

- Drukken op de :kbd: Esc of :kbd:` Menu` toetsen

- Op een aanraakscherm door op het bovenste 1/3 van het scherm te tikken

De viewer heeft twee modi, ‘gepagineerd” en “vloeiend”. In gepagineerde modus komt het boek in pagina’s, zoals een papieren boek. In vloeiende modus loopt de tekst door zoals in een web browser. U kan ertussen schakelen met de viewer Voorkeuren → Conversie onder Algemene opties → Pagina opmaak of door de Ctrl+M toets in te drukken.

Bladwijzers

Als u midden in een boek bent en de e-boek viewer sluit, zal deze onthouden waar u stopte met lezen en daar terugkeren de volgende keer u het boek opent. U kan ook bladwijzers instellen in het boek met de Bladwijzers knop in de e-boek viewer bediening of door Ctrl+B in te drukken. Bij EPUB boeken worden deze bladwijzers opgeslagen in het EPUB bestand zelf. U kan bladwijzers opslaan en dan het bestand naar een vriend sturen. Als ze het bestand openen, zullen ze uw bladwijzers kunnen zien. U kan dit uitschakelen in de Diversen sectie van de viewer voorkeuren.

Inhoudsopgave

Als het boek dat u leest een inhoudsopgave heeft, kunt u die openen door op de knop: guilabel :`inhoudsopgave’ te drukken. Dit geeft een lijst met secties in het boek weer. U kan op eender welke klikken om naar dat gedeelte van het boek te springen.

Navigeren op locatie

E-boeken hebben, in tegenstelling tot papieren boeken, geen concept van pagina’s. U kunt naar exacte locaties in e-boeken verwijzen met behulp van de Ga naar →  Locatie-functionaliteit in de lezerbediening.

U kunt deze locatiegegevens gebruiken om ondubbelzinnig naar delen van de boeken te verwijzen bij bespreken met vrienden of ernaar verwijzen in andere werken. U kunt deze locaties invoeren onder Ga naar →  Locatie in de lezerbediening.

Er is een URL die u kan kopiëren naar het plakbord en plakken in andere programma’s of documenten. Klikken op deze URL opet het boek in de calibre e-boek viewer op de huidige locatie.

Als u op de links klikt in het e-boek die u naar andere delen in het boek brengen, zoals een eindnoot, dan kan u de knoppen Terug and Voorwaarts links bovenaan in de lezerbediening. Deze knoppen gedragen zich zoals die in een web browser.

Referentiemodus
calibre heeft ook een handige Reference mode. U kan het aanzetten door op de Reference mode knop in de lezerbediening te klikken. Als u dit doet, krijgt elke alinea een uniek nummer weergeven aan het begin, bestaand uit de sectie- en alineanummers.

U kunt dit nummer gebruiken om ondubbelzinnig te refereren aan delen van de boeken wanneer u die bespreekt met vrienden of in andere werken hieraan wilt refereren. U kunt deze nummers ingeven in het guilabel:Go to function om naar een specifieke referentie locatie te gaan.

Tekst highlighten

Als u tekst selecteert in de viewer verschijnt er een kleine pop-up naast de selectie. U kan op de highlight knop in die balk klikken om een highlight te maken. U kan nota’s toevoegen en de kleur van de highlight veranderen. Bij een aanraakscherm, tik lang op een woord om te selecteren en de pop-up te tonen. In highlight modus kan u welke tekst geselecteerd is met aanraak vriendelijke selectiegrepen. Sleep de grepen naar de boven- of ondermarge om te scrollen tijdens selecteren. U kan ook Shift+klik of rechtse klik om de selectie uit te breiden, zeer bruikbaar bij multi-pagina selectie.

U kan de Highlights knop in de viewerbediening gebruiken om een apart paneel met een lijst van alle highlights in het boek te tonen, gesorteerd per hoofdstuk.

U kan alle highlights in uw hele calibre bekijken doorbladeren door rechts te klikken op de Tonen knop en Doorblader annotaties te kiezen.

Tenslotte, als u de calibre Content server’s in browser viewer gebruikt, kan u de viewer z’n annotaties laten synchroniseren met de browser viewer via Voorkeuren → Diversen in de viewer voorkeuren en invoeren van de gebruikersnaam van de Content server viewer om mee te synchroniseren. Gebruik de joker * om te synchroniseren met anonieme gebruikers.

Lees hardop

De viewer kan tekst hardop voorlezen. Om te gebruiken, klik gewoon op de Luidop lezen knop in de viewer bediening om te starten. Het woord dat gelezen wordt is gehighlight. Spraak wordt gesynthetiseerd vanuit de tekst door diensten van uw besturingssysteem voor tekst-naar-spraak. U kand de gebruikte stem veranderen door op het gear icoon te klikken in de balk getoond als Luidop lezen actief is.

U kan ook gehighlighte delen luidop voorlezen door de Luidop lezen knop toe te voegen aan selectiebalk in de viewer voorkeuren onder Selectiegedrag.

Notitie

Ondersteuning voor tekst-naar-spraak in browsers is zeer onvolledig en vol fouten dus hoe goed Luidop lezen  zal werken in de in-browser viewer hangt af van hoe goed de onderliggende browser tekst-naar-spraak ondersteunt. In ‘t bijzonder, highlighten van het huidige woord werkt niet en veranderen van snelheid of stem laat het opnieuw beginnen.

Notitie

In Linux, Luidop lezen heeft Speech Dispatcher [https://freebsoft.org/speechd] nodig, geïnstalleerd en werkend.

Notitie

In Windows zijn misschien niet alle stemmen zichtbaar voor het SAPI sub-systeem gebruikt voor tekst-naar-spraak. Er zijn instructies om alle stemmen zichtbaar te maken [https://www.mobileread.com/forums/showpost.php?p=4084051&postcount=108].

In de tekst zoeken

The viewer has very powerful search capabilities. Press the Ctrl+F key or access the viewer controls and click search. The simplest form of searching is to just search for whatever text you enter in the text box. The different forms of searching are chosen by the search mode box below the search input. Available modes are:

1. Contains - The simplest default mode. The text entered in the search box is searched for anywhere. All punctuation, accents and spaces are ignored. For example, the search: Pena will match all of the following: penal, pen a, pen.a and Peña. If you select the Case sensitive box then accents, spaces and punctuation are no longer ignored.

2. Whole words - Searches for whole words. So for example, the search pena will match the word Peña but not the word Penal. As with Contains searches above, accents and punctuation are ignored unless the Case sensitive box is checked.

3. Nearby words - Searches for whole words that are near each other. So for example, the search calibre cool will match places where the words calibre and cool occur within sixty characters of each other. To change the number of characters add the new number to the end of the list of words. For instance, calibre cool awesome 120 will match places where the three words occur within 120 characters of each other. Note that punctuation and accents are not ignored for these searches.

4. Regex - Interprets the search text as a regular expression. To learn more about using regular expressions, see the tutorial.

Links volgen met enkel het toetsenbord

De e-boekviewer heeft een Hintmodus die u toelaat op links in de tekst te klikken zonder muis.Druk de Alt+F toets in en alle links in het huidige scherm worden gehighlight met een cijfer of letter erover. Tik de letter op uw toetsenbord om op de link te klikken. Drukken op de Esc toets voert de Hintmodus af zonder een link te selecteren.

Als er meer dan 35 links op het scherm zijn, hebben sommige meerdere letters. Tik in dat geval de eerste en de tweede of de eerste en tik Enter om te activeren. U kan ook Backspace gebruiken om een tikfout te herstellen.

De look and feel van de leeservaring personalizeren

U kunt lettergroottes in een wip wijzigen door Lettergrootte in de lezerbediening te gebruiken of Ctrl ++ of Ctrl+- of de toets Ctrl ingedrukt te houden en het muiswiel te gebruiken.

Kleuren kunnen worden gewijzigd in de Colors sectie van de lezervoorkeuren.

U kunt het aantal pagina’s dat op het scherm wordt weergegeven alsmede de paginamarges wijzigen in Paginalay-out in de lezervoorkeuren.

U kunt aangepaste kopteksten en voetteksten weergeven, zoals de resterende tijd om te lezen, de titel van het huidige hoofdstuk, de boekpositie, enz. via het Kopteksten en voetteksten van de voorkeuren van de lezer.

Meer geavanceerde aanpassing kunnen worden bereikt met de instellingen :guilabel: Styles. Hier kunt u een achtergrondafbeelding specificeren om onder de tekst weer te geven en ook kunt u een stijlblad instellen dat op elk boek wordt toegepast. Hiermee kunt u dingen doen zoals alineastijlen wijzigen, tekstuitvulling enz. Voor voorbeelden van aangepaste stylesheets die door gebruikers van calibre worden gebruikt, zie `de forums <https://www.mobileread.com/forums/showthread.php?t=51500 > `_.

Opzoeken in woordenboek

U kan de betekenis van woorden opzoeken door dubbelklikken of lang tikken op het woord dat u wilt opzoeken en dan te klikken op de opzoek knop die eruit ziet als een bibliotheek.

Kopieer tekst en afbeeldingen

U kunt tekst en afbeeldingen selecteren door de inhoud met uw muis te slepen en vervolgens met de rechtermuisknop te klikken en te selecteren Copy om naar het klembord te kopiëren. Het gekopieerde materiaal kan als platte tekst en afbeeldingen in een andere applicatie worden geplakt.

Inzoomen op afbeeldingen

U kunt inzoomen om een afbeelding op ware grootte in een apart venster weer te geven door erop te dubbelklikken of lang te tikken. U kunt er ook op rechtsklikken en kiezen: guilabel :Bekijk afbeelding.

Toetsenbord sneltoetsen

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

Toetsenbord snelkoppeling

Actie

Home, Ctrl+ArrowUp, Ctrl+ArrowLeft

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

Ctrl+Home

Blader naar het begin van het boek

Ctrl+End

Blader naar het einde van het boek

End, Ctrl+ArrowDown, Ctrl+ArrowRight

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

ArrowUp

Blader terug, gelijkmatig in vloeiende-stand en bij volledig scherm in pagina-stand

ArrowDown

Blader vooruit, gelijkmatig in vloeiende-stand en bij volledig scherm in pagina-stand

ArrowLeft

Langzaam naar links scrollen in vloeiende-stand en bij een pagina in pagina-stand

ArrowRight

Langzaam naar rechts scrollen in vloeiende-stand en bij een pagina in pagina-stand

PageUp, Shift+Spacebar

Blader gelijkmatig terug bij volledig scherm

PageDown, Spacebar

Blader gelijkmatig vooruit bij volledig scherm

Ctrl+PageUp

Scroll naar de vorige paragraaf

Ctrl+PageDown

Scroll naar de volgende paragraaf

Alt+ArrowLeft

Terug

Alt+ArrowRight

Volgende

Ctrl+T

Toggle Table of Contents

Ctrl+S

Lees hardop

Alt+P

Change settings quickly by creating and switching to profiles

Alt+f

Volg links met het toetsenbord

Ctrl+C

Naar klembord kopiëren

Alt+C

Kopieer huidige locatie naar klembord

Ctrl+Shift+C

Kopieer huidige locatie als calibre:// URL naar klembord

/, Ctrl+f, Cmd+f

Begin met zoeken

F3, Enter

Volgende zoeken

Shift+F3, Shift+Enter

Vorige zoeken

Ctrl+Plus, Meta+Plus

Lettertype vergroten

Ctrl+Minus, Meta+Minus

Lettertype verkleinen

Ctrl+0

Standaard font grootte herstellen

Ctrl+]

Verhoog het aantal pagina’s per scherm

Ctrl+[

Verlaag het aantal pagina’s per scherm

Ctrl+Alt+C

Maak het aantal pagina’s per scherm automatisch

F11, Ctrl+Shift+F

Volledig scherm aan/uit

Ctrl+M

Wissel tussen Pagina-stand en Vloeiende-stand voor tekst layout

Ctrl+W

Schakel de schuifbalk in

Ctrl+X

Wissel de Referentie-stand

Ctrl+B

Toon/verberg bladwijzers

Ctrl+Alt+B

Nieuwe bladwijzer

Ctrl+N, Ctrl+E

Toon de metadata van het boek

Ctrl+Alt+F5, Ctrl+Alt+R

Boek herladen

Ctrl+Shift+ArrowRight

Wijzig de huidige selectie vooruit door een woord

Ctrl+Shift+ArrowLeft

Wijzig de huidige selectie achteruit door een woord

Shift+ArrowRight

Wijzig de huidige selectie vooruit met een letter

Shift+ArrowLeft

Wijzig de huidige selectie achteruit met een letter

Shift+ArrowDown

Wijzig de huidige selectie vooruit met een regel

Shift+Home

Breid huidige selectie uit tot het begin van de regel

Shift+End

Breid huidige selectie uit tot het einde van de regel

Ctrl+A

Alles selecteren

Shift+ArrowUp

Wijzig de huidige selectie achteruit met een regel

Ctrl+Shift+ArrowDown

Wijzig de huidige selectie vooruit met een paragraaf

Ctrl+Shift+ArrowUp

Wijzig de huidige selectie achteruit met een paragraaf

Esc, MenuKey

Toon de e-boekviewer controls

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

Toon de e-boekviewer voorkeuren

Ctrl+G, ;, :

Ga naar een geselecteerd boek of positie

Ctrl+Spacebar

Schakel automatisch scrollen in

Alt+ArrowUp

Sneller automatisch bladeren

Alt+ArrowDown

Trager automatisch bladeren

Ctrl+I

Toon/verberg Controleur

Ctrl+L

Toon/verberg de woorden pop-up venster

Ctrl+Q (Cmd+Q on macOS)

Afsluiten

Ctrl+P

Afdrukken naar PDF

Ctrl+F11

Wissel de werkbalk

Ctrl+H

Schakel het deelvenster met markeringen

Ctrl+D

Bewerk dit boek

Niet-opnieuw stroombare inhoud

Sommige boeken hebben zeer brede inhoud die niet kan afbreken aan paginagrenzen. Bv. tabellen of <pre> tags. In dergelijke gevallen schakelt u de viewer naar flow-modus door Ctrl+M te tikken om deze inhoud te lezen. Anderzijds kunt u ook volgende CSS toevoegen aan het :guilabel: Stijlen gedeelte van de viewervoorkeuren om de viewer te dwingen tekstregels op te splitsen in <pre> tags:

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

Uw boek ontwerpen om goed te werken in calibre viewer

De calibre viewer zet de is-calibre-viewer class op het root element. U kan dus CSS regels schrijven die er enkel op van toepassing zijn. Bijkomend zal de viewer volgende classes op het body element zetten:

body.calibre-viewer-dark-colors

Instellen wanneer donker kleurenschema te gebruiken

body.calibre-viewer-light-colors

Instellen wanneer licht kleurenschema te gebruiken

body.calibre-viewer-paginated

Instellen wanneer in gepagineerde modus

body.calibre-viewer-scrolling

Instellen wanneer vloeiende (niet-gepagineerde) modus te gebruiken

body.calibre-footnote-container

Set when displaying a popup footnote

Tenslotte, u kan het de calibre kleurenschema kleurengebruiken via CSS variabelen [https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties]. De calibre viewer definieert de volgende variabelen: --calibre-viewer-background-color, --calibre-viewer-foreground-color en optioneel --calibre-viewer-link-color in kleurthema’s die een link kleur definiëren.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- E-boek conversie

E-boek conversie
calibre heeft een conversiesysteem ontworpen voor gebruiksvriendelijkheid. Normaal voegt u gewoon een boek toe aan calibre, klikt op converteren en calibre zal hard proberen een uitvoer te genereren zo dicht mogelijk bij de invoer. calibre accepteert echter een groot aantal invoerformaten, niet allemaal even geschikt voor conversie naar e-boeken. In het geval van zo’n invoerformaten, of als u gewoon meer controle over het conversiesysteem wilt, heeft calibre veel opties om het conversieproces te verfijnen. Merk op dat het conversiesysteem van calibre geen vervanging is voor een volledige e-boek bewerker. Om e-boeken te bewerken raad ik aan ze eerst te converteren naar EPUB of AZW3 via calibre en dan de Boek bewerken functie te gebruiken om ze in perfecte vorm te krijgen. U kunt dan het bewerkte e-boek gebruiken als invoer voor conversie naar andere formaten in calibre.

Dit document behandelt hoofdzakelijk de conversie-instellingen zoals gevonden in het conversie dialoogvenster, onder afgebeeld. Al deze instellingen zijn ook beschikbaar via de opdrachtregel, zie ook ebook-convert. In calibre kunt u hulp krijgen bij elke individuele instelling door er met uw muis over te gaan waarna een tooltip verschijnt met de beschrijving.

Inhoud

- Inleiding

- Uitstraling & gevoel

- Pagina opmaak

- Heuristische verwerking

- Zoeken & vervangen

- Structuurdetectie

- Inhoudsopgave

- Gebruik afbeeldingen als hoofdstuktitels tijdens de conversie van HTML invoer-documenten

- Gebruik tag attributen om de tekst te halen t.b.v. Inhoudsopgave

- Hoe opties worden ingesteld/opgeslagen voor conversie

- Formaat specifieke tips

Inleiding

Wat u als eerste moet weten over het systeem van conversie is dat het ontworpen is als een pijplijn. Schematisch ziet het er als volgt uit:

Het invoerformaat wordt eerst geconverteerd naar XHTML door de gepaste Input plugin. Deze HTML wordt vervolgens getransformeerd. In de laatste stap wordt de verwerkte XHTML geconverteerd naar het opgegeven uitvoerformaat door de juiste Uitvoer plugin. De resultaten van de conversie kunnen sterk variëren, op basis van het invoerformaat. Sommige formaten converteren beter dan andere. Een lijst met de beste bronformaten voor conversie is beschikbaar: ref:hier <best-source-formats>.

De transformaties uitgevoerd op de XHTML-uitvoer zijn waar alles gebeurt. Er zijn verschillende transformaties, bijvoorbeeld om boek metadata in te voegen als een pagina aan het begin van het boek, om hoofdstukkoppen te detecteren en automatisch een Inhoudsopgave te maken, om de lettergroottes proportioneel aan te passen, enz. Het is belangrijk om te onthouden dat alle transformaties op de XHTML-uitvoer van de Invoer plug-in werken, niet op het invoerbestand zelf. Dus als u bijvoorbeeld calibre vraagt om een RTF-bestand naar EPUB te converteren, wordt het eerst intern naar XHTML geconverteerd, de verschillende transformaties worden op de XHTML toegepast en vervolgens maakt de Uitvoer plug-in het EPUB-bestand, automatisch alle metadata, inhoudsopgave, enz genererend.

U kunt dit proces in actie zien door de debug optie te gebruiken. Geef het pad op naar een map voor de uitvoer van de foutenopsporing. Tijdens conversie zal calibre de XHTML gegenereerd door de diverse stadia van de conversie pijplijn in verschillende sub-mappen plaatsen. De vier sub-mappen zijn:

Stappen in het conversieproces

Map

Omschrijving

invoer

Dit bevat de HTML-uitvoer door de invoer plug-in. Gebruik dit om de invoer plug-in te debuggen.

doorlopen

Het resultaat van voorbewerking en converteren naar XHTML van de uitvoer van de Invoer plug-in. Gebruik voor foutopsporing in de structuurdetectie.

structuur

Na structuurdetectie maar voor CSS afvlakken en lettergrootte conversie. Gebruik voor foutopsporing in lettergrootte conversie en CSS transformaties.

verwerkt

Net voor het e-boek wordt doorgegeven aan de uitvoer plug-in. Gebruik voor foutopsporing in de Uitvoer plug-in.

Als u het invoerdocument wilt bewerken voor calibre het converteert, kan u dit best in de input submap doen, dan inpakken als ZIP bestand en dit gebruiken als invoerformaat voor volgende conversies. Om dit te doen, gebruik de Meta informatie bewerken dialoog om het ZIP bestand toe te voegen als formaat voor het boek en selecteer dan, linksboven in het dialoogvenster, ZIP als invoerformaat.

Dit document behandelt hoofdzakelijk de verschillende transformaties op de tussenliggende XHTML en hoe ze te controleren. Op het einde zijn er tips specifiek voor elk type invoer/uitvoer formaat.

Uitstraling & gevoel

Inhoud

- Lettertypen

- Tekst

- Opmaak

- Stijlen

- Transformeer stijlen

- Transformeer HTML

Deze groep opties controleert verschillende aspecten van het uitzicht van het geconverteerde e-boek.

Lettertypen

Een van de fijnste functies van de e-leeservaring is de mogelijkheid om makkelijk de lettergrootte in te stellen voor individuele behoeftes en lichtomstandigheden. calibre heeft gesofisticeerde algoritmes om ervoor te zorgen dat het boeken in een uniforme lettergrootte presenteert, ongeacht wat is gespecificeerd in het invoerdocument.

De basis lettergrootte van een document is de algemene lettergrootte in dat document, d.i. de grootte van de meeste tekst in dat document. Als u een Basis lettergrootte specificeert, zal calibre automatisch de lettergrootte proportioneel aanpassen, zodat de meest voorkomende grootte de gespecificeerde basis lettergrootte wordt en andere lettergroottes gepast geschaald worden. Door een grotere basis lettergrootte te kiezen, kunt u de letters in het document groter maken en omgekeerd. Wanneer u de basis lettergrootte instelt, moet u voor het beste resultaat ook de lettergrootte-sleutel instellen.

Normaal zal calibre automatisch een basis fontgrootte kiezen, gepast voor het uitvoerprofiel dat u gekozen hebt (zie Pagina opmaak). U kan dit echter hier negeren indien de standaard niet geschikt is voor u.

Met de optie Lettergrootte-sleutel controleert u hoe niet-basis lettergroottes worden geschaald. Het lettertype schaal algoritme werkt met een lettergrootte sleutel, eenvoudig een komma gescheiden lijst van lettergroottes. Deze sleutel vertelt calibre hoeveel “stappen” groter of kleiner een bepaalde grootte moet zijn vergeleken met de basisgrootte. Het idee is dat er een begrensd aantal lettergroottes in een document mag voorkomen. Bv: een grootte voor de tekst, een paar groottes voor verschillende titels en een paar voor super/subscripten en voetnoten. De lettergrootte-sleutel laat calibre de groottes in het invoerdocument scheiden in separate “bakken” overeenkomend met verschillende logische lettergroottes.

Ter illustratie een voorbeeld. Stel dat het brondocument dat we converteren gemaakt is door iemand met uitstekend gezichtsvermogen met een basis lettergrootte van 8pt. Dat betekent dat de meeste tekst in het document in 8pt is gezet, met de titels een beetje groter (bv 10 of 12pt) en voetnoten iets kleiner op 6pt. Als we nu de volgende instellingen gebruiken:

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

Het uitvoerdocument krijgt een basis lettergrootte van 12pt, titels 14 en 16pt en voetnoten 8pt. Aangenomen dat we de grootste titels meer willen laten opvallen en de voetnoten ook. Om dit te bereiken zal de lettergrootte-sleutel gewijzigd moeten worden naar:

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

De grootste titels worden nu 18pt, terwijl de voetnoten 9pt worden. U kunt met deze instellingen spelen en proberen uit te vinden wat voor u de optimale waarden zijn met behulp van de wizard die u kunt openen door op de kleine knop naast de Lettergrootte-sleutel klikt.

Het schalen van lettergrootte in de conversie kunt u hier ook uitschakelen indien u de lettergroottes van het invoerdocument wilt behouden.

Een verwante instelling is Lijnhoogte. Lijnhoogte bepaalt de verticale hoogte van lijnen. Standaard (regelhoogte 0) is er geen manipulatie van lijnhoogten . Als u een niet-standaardwaarde opgeeft, worden regelhoogten ingesteld op alle plaatsen zonder eigen lijnhoogten. Dit is echter eerder een botte bijl en moet spaarzaam worden gebruikt. Als u de lijnhoogten voor een deel van de invoer wilt aanpassen, is het beter om de Extra CSS te gebruiken.

Hier kunt u calibre ook laten weten referenced lettertypen in het boek in te sluiten. Dit laat de lettertypen werken op toestellen, zelfs als ze niet beschikbaar zijn op het toestel.

Tekst

Tekst kan worden uitgevuld of niet. Uitgevulde tekst heeft extra spaties tussen woorden voor een nette rechtermarge. Sommige mensen verkiezen aan uitgevulde tekst, anderen niet. Normaal gesproken bewaart calibre de uitvulling van het originele document. Als u dit wilt negeren, gebruik de optie Text justification in dit gedeelte.

U kunt calibre ook laten Interpunctie verslimmen, dit vervangt gewone aanhalingstekens, streepjes en ellipsen door hun typografisch correcte alternatieven. Merk op dat dit algoritme niet perfect is, dus het loont om de resultaten te beoordelen. Het omgekeerde, namelijk: guilabel: Interpunctie verdommen is ook beschikbaar.

Tenslotte is er: guilabel: Invoer karakter encodering. Oudere documenten specificeren soms hun karaktercodering niet. Bij conversie kunnen hierdoor niet-Engelse of speciale karakters, zoals slimme aanhalingstekens, beschadigd zijn. calibre probeert de karaktercodering van het brondocument te auto-detecteren, maar dat lukt niet altijd. U kunt het dwingen een bepaalde codering aan te nemen met deze instelling. cp1252 is een algemene codering voor documenten geproduceerd met Windows software. U leest best ook Hoe converteer ik mijn bestand met niet-Engelse karakters of smart quotes? voor meer informatie over coderingsproblemen.

Opmaak

Normaal worden alinea’s in XHTML gerenderd met een lege regel tussen alinea’s en zonder inspringen. In calibre kunt u dit beïnvloeden met een paar opties. Verwijder ruimte tussen alinea’s verwijdert de lege regels tussen alinea’s. Het stelt het tekst inspringen ook in op 1.5em (kan gewijzigd worden) om een nieuwe alinea te markeren. Invoegen lege regel doet het tegenovergestelde door precies één lege regel in te voegen tussen twee alinea’s. Beide opties zijn veelomvattend, verwijderen van lege regels of juist het invoegen voor alle alinea’s (technisch <p> en <div> tags).U stelt gewoon de optie in en deze zal worden uitgevoerd ongeacht hoe rommelig het invoerbestand is. Er is een uitzondering: wanneer het invoerbestand harde regeleindes gebruikt voor alinea scheiding.

Als u de spatiëring tussen alle alinea’s wilt verwijderen, behalve een select aantal, gebruik dan deze opties niet. Voeg in plaats daarvan de volgende CSS-code toe aan Extra CSS 1:

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

Vervolgens, in uw bron-document, markeer de alinea’s die afstand nodig hebben met class=”spacious”. Indien uw invoerdocument niet in HTML is, gebruik de optie Foutopsporing beschreven in de Inleiding om HTML te krijgen (gebruik de input submap).

Een andere handige optie is: guilabel: Lineariseer tabellen. Sommige slecht ontworpen documenten gebruiken tabellen om de lay-out van tekst op de pagina te regelen. Bij het converteren bevatten deze documenten vaak tekst die van de pagina afloopt en andere artefacten. Met deze optie wordt de inhoud uit de tabellen gehaald en lineair gepresenteerd. Merk op dat deze optie alle tabellen lineariseert, dus gebruik het alleen als u zeker weet dat het invoerdocument geen tabellen gebruikt voor legitieme doeleinden, zoals het presenteren van tabelinformatie.

Stijlen

De Extra CSS optie laat u willekeurige CSS specificeren, toe te passen op alle HTML bestanden in de invoer. Deze CSS wordt met hoge prioriteit toegepast en negeert dus de meeste CSS in het invoerdocument. Gebruik deze instelling om de presentatie/opmaak van uw document aan te passen. Wilt u bijvoorbeeld dat alle alinea’s met class eindnotitie rechts zijn uitgelijnd, voegt u gewoon toe:

.endnote { text-align: right }

of als u de insprong van alle paragrafen wilt aanpassen:

p { text-indent: 5mm; }

Extra CSS is een zeer krachtige optie, maar u moet wel enige kennis hebben over hoe CSS werkt om het potentieel volledig te benutten. U kunt de optie Foutenopsporing pijplijn gebruiken zoals boven beschreven om te zien welke CSS in opgenomen in het invoerdocument.

Een eenvoudigere optie is gebruik van: guilabel: Filter stijlinformatie. Hiermee verwijdert u alle CSS eigenschappen van de opgegeven types uit het document. Bv: u kunt het gebruiken om alle kleuren of lettertypen te verwijderen.

Transformeer stijlen

Dit is de krachtigste styling gerelateerde functie. U kunt er regels mee definiëren die stijlen veranderen op basis van verschillende omstandigheden. Bv: u kunt het gebruiken om alle groene kleuren in blauw te veranderen, of om alle vetgedrukte stijlen uit de tekst te verwijderen of om alle koppen in een bepaalde kleur te kleuren, enz.

Transformeer HTML

Lijkt op stijlen transformeren maar laat u de HTML inhoud van het boek aanpassen. U kan een tag vervangen door een andere, classes en andere attributen aan tags toevoegen gebaseerd op hun inhoud, enz.

Pagina opmaak

De Pagina instelling opties zijn er om schermindeling zoals marges en schermafmeting te controleren. Er zijn opties om marges in te stellen die gebruikt worden door de Uitvoer plug-in indien het geselecteerde Uitvoerformaat marges ondersteunt. Daarnaast moet u een Invoerprofiel kiezen en een Uitvoerprofiel. Beide profielsets zijn in principe verantwoordelijk voor interpretatie van de afmetingen in invoer/uitvoer documenten, schermafmetingen en standaard lettergrootte-sleutels.

Als u weet dat het bestand dat u converteert, bedoeld is voor gebruik op een specifiek toestel/software platform, kies dan het overeenkomend invoerprofiel, of neem gewoon het standaard profiel. Als uw weet dat de bestanden die u maakt voor een bepaald toesteltype zijn, kies het overeenkomende uitvoerprofiel, of neem een van de Generieke uitvoerprofielen. Als u converteert naar MOBI of AZW3 zal u meestal één van de Kindle uitvoerprofielen kiezen. Anders is uw beste keuze voor een moderne E-boek reader het Generiek e-inkt HD uitvoerprofiel.

Het Uitvoerprofiel controleert ook de schermafmeting. Dit zorgt er bijvoorbeeld voor dat afbeeldingen auto-schalen om op het scherm te passen in sommige uitvoerformaten. Kies dus een profiel van een toestel dat lijkt op de schermafmeting van uw apparaat.

Heuristische verwerking

Heuristische verwerking biedt een hoop functies om veelvoorkomende problemen in slecht opgemaakte invoerdocumenten te proberen detecteren en corrigeren. Gebruik deze functies als uw invoerdocument slecht is geformatteerd. Deze functies zijn afhankelijk van algemene patronen, houdt er rekening mee dat een optie soms tot slechtere resultaten kan leiden, dus wees voorzichtig. Een aantal van deze opties verwijdert bijvoorbeeld alle niet-brekende spatie-entiteiten, of kan vals-positieve overeenkomsten met betrekking tot de functie bevatten.

Activeer heuristische verwerking

Deze optie activeert calibre’s Heuristische verwerking stap in de conversiepijplijn. Dit moet ingeschakeld zijn om diverse sub-functies toe te passen.

Harde regeleinden verwijderen

Deze optie activeren laat calibre proberen harde regeleinden te detecteren en corrigeren in het document via interpunctie en regellengte. calibre zal eerst proberen of harde regeleinden voorkomen, als het erop lijkt dat ze niet bestaan, zal calibre niet proberen deze te verwijderen. De regelterugloop factor kunt u verlagen om calibre te ‘dwingen’ de harde regeleinden te verwijderen.

Regelterugloop factor

Deze optie controleert het algoritme dat calibre toepast om harde regeleinden te verwijderen. Bijvoorbeeld: Als de waarde voor deze optie 0.4 is, betekent dit dat calibre harde regeleinden verwijdert van regeleinden waarvan de lengte minder dan 40% van alle regels in het document zijn. Als uw document maar een paar regeleinden heeft die gecorrigeerd moeten worden dan zou deze waarde gereduceerd moeten worden naar iets tussen 0.1 en 0.2.

Bepaal en markeer ongeformateerde hoofdstuktitels en sub-hoofdstuktitels

Indien uw document geen hoofdstuktitels en ondertitels heeft die anders zijn geformatteerd dan de rest van de tekst kan calibre met deze optie proberen om deze te bepalen en ze omvatten met kop-labels. <h2> labels voor hoofdstuktitels; <h3> labels voor alle gevonden titels.

Deze functie zal geen inhoudsopgave creëren, maar vaak kan dit helpen om standaard de hoofdstukken te herkennen en een inhoudsopgave op te bouwen. Stel de XPath bij onder Structuurdetectie als een inhoudsopgave niet automatisch wordt gemaakt. Als er geen andere titels in het document zijn, zal de instelling “//h:h2” onder Structuurdetectie de makkelijkste manier zijn om een inhoudsopgave voor het document te maken.

De ingevoegde titels worden niet geformatteerd; om titels op te maken gebruik de optie Extra CSS onder de Uiterlijk en gedrag conversie-instellingen. Bijvoorbeeld: Om titel-labels te centreren, gebruik onderstaande:

h2, h3 { text-align: center }

Hernummer sequenties van <h1> of <h2> labels

Sommige uitgevers formatteren hoofdstuktitels met meerdere <h1> of <h2> tags opeenvolgend. Met de standaard conversie-instellingen zal calibre deze titels opsplitsen in twee titels. Deze optie zal de titel-labels hernummeren om opsplitsing te voorkomen.

Verwijder lege regels tussen alinea’s

Deze optie laat calibre lege regels binnen het document analyseren. Wanneer iedere alinea is gescheiden van de volgende door een lege regel zal calibre alle lege alinea’s verwijderen. Meerdere lege regels worden beschouwd als scenario-onderbreking en als een enkele alinea behouden. Deze optie verschilt van de Verwijder alinea-afstand optie onder ‘Uitstraling & Gevoel’ doordat deze de HTML-inhoud daadwerkelijk wijzigt, terwijl de andere optie de opmaak van het document wijzigt. Deze optie kan ook alinea’s verwijderen die ingevoegd werden met behulp van de Lege regel invoegen optie .

Zorg ervoor dat scene-onderbrekingen altijd geformatteerd worden

Met deze optie probeert calibre om gewone scene-onderbrekingen markers te detecteren en ervoor te zorgen dat deze centraal worden uitgelijnd. ‘Zachte’ scene-onderbrekingen markers, d.i. scene-onderbrekingen bepaald door extra lege ruimte worden opgemaakt zodat ze niet getoond worden in combinatie met pagina-einden.

Vervang scene-onderbrekingen

Als deze optie is geconfigureerd zal calibre de scene-onderbrekingen markers vervangen door vervangende tekst gespecificeerd door de gebruiker. Merk op aub dat sommige sierkarakters mogelijk niet worden ondersteund door alle lezers.

Normaal vermijdt u best het gebruik van HTML tags. calibre zal tags negeren en vooraf gedefinieerde mark-up gebruiken. <hr /> tags, d.i. horizontale lijn en <img> tags zijn uitzonderingen. Horizontale lijnen kunnen optioneel worden gespecificeerd met stijlen, voegt u uw eigen stijl toe, voorzie zeker de ‘width’-instelling, anders wordt de stijl genegeerd. Afbeelding tags kunnen gebruikt worden, maar calibre heeft geen mogelijkheid de afbeelding toe te voegen tijdens conversie, dit moet later gebeuren met de ‘Boek bewerken’ functie.

Voorbeeld afbeeldingstag (plaats de afbeelding in een map ‘Afbeeldingen’ in de EPUB na conversie):

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

Voorbeeld horizontale lijn met opmaak:

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

Verwijder overbodige koppeltekens

calibre analyseert alle inhoud met koppeltekens wanneer deze optie geactiveerd is. Het document zelf dient als woordenboek in de analyse. Hiermee kan calibre nauwkeurig koppeltekens verwijderen in elk woord in het document in elke taal alsook verzonnen of rare wetenschappelijke begrippen. Grootste nadeel is dat woorden die maar één keer voorkomen niet worden gewijzigd. Analyse gebeurt in twee stappen, de eerste analyseert de regeleinden. Regeleinden worden alleen verwijderd als het woord bestaat in het document met of zonder een koppelteken. De tweede stap analyseert alle woorden met koppelteken, koppeltekens worden verwijderd indien het woord ergens anders bestaat zonder overeenstemming.

Algemene woorden en patronen cursiveren

Wanneer geactiveerd zoekt calibre naar algemene woorden en patronen die cursieve schrijfwijze aangeven en ze cursiveren. Voorbeelden zijn algemene schrijfwijzen zoals ~word~ of uitspraken die algemeen cursief worden getoond, bijv. Latijnse uitdrukkingen zoals ‘etc.’ of ‘et cetera’.

Vervang gegeven inspringing met CSS inspringing

Sommige documenten bepalen inspringende tekst met behulp van spaties. Wanneer deze optie is geactiveerd zal calibre proberen deze te detecteren en vervangen door een 3% tekst insprong met behulp van CSS.

Zoeken & vervangen

Deze opties zijn vooral nuttig voor conversie van PDF documenten of OCR conversies, hoewel ze ook helpen om vele document-specifieke problemen op te lossen. Zo plaatsen sommige conversies kop- en voetteksten in de tekst. Deze opties gebruiken reguliere expressies om deze of andere willekeurige teksten te proberen te detecteren en verwijderen of vervangen ze. Onthoud dat deze werken met de tussentijdse XHTML gemaakt in de conversie pijplijn. Er is een wizard om u te helpen de reguliere expressies aan te passen voor uw document. Klik op de toverstaf naast het expressievak en klik op de ‘Test’ knop na het samenstellen uw zoekuitdrukking. Gevonden overeenkomsten worden gemarkeerd in Geel.

Het zoeken werkt met een Python reguliere expressie. Overeenkomende tekst wordt eenvoudig verwijderd of vervangen met behulp van het vervangingspatroon. Het vervangingspatroon is optioneel, indien leeg, wordt overeenkomende tekst verwijderd uit het document. U komt meer te weten over reguliere expressies en hun syntax op Alles over het gebruik van reguliere expressies in calibre.

Structuurdetectie

Structuurdetectie houdt in dat calibre z’n best doet structurele elementen te detecteren in het invoerdocument wanneer deze niet netjes zijn gespecificeerd. Bijvoorbeeld: hoofdstukken, pagina-einden, kop- en voetteksten enz. Zoals u begrijpt, varieert dit sterk van boek tot boek. Gelukkig heeft calibre zeer krachtige opties om dit te controleren. Met kracht komt complexiteit, maar als u de tijd neemt om de complexiteit te leren kennen, zult u vinden dat het dit waard is.

Hoofdstukken en pagina-eindes
calibre heeft twee sets opties voor hoofdstukdetectie en pagina-einden invoegen. Dit kan soms verwarrend zijn omdat calibre standaard pagina-einden invoegt voor gevonden hoofdstukken en tevens op de plekken gevonden door de optie pagina-einden. De reden is dat vaak waar pagina-einden ingevoegd moeten worden geen markeringen zijn voor hoofdstukken. Tevens kunnen gevonden hoofdstukken optioneel ingevoegd worden in de automatisch gegenereerde Inhoudsopgaven.

calibre gebruikt XPath, een krachtige taal die de gebruiker markeringen voor hoofdstukken/pagina-einden laat bepalen. XPath kan een uitdaging zijn om te gebruiken maar gelukkig is er een XPath handleiding in de Gebruikershandleiding. Onthoud dat Structuurdetectie werkt met het tussentijdse XHTML bestand geproduceerd door de conversie-pijplijn. Gebruik de foutopsporingsoptie beschreven in de Inleiding om de passende instellingen voor uw boek te bepalen. Er is ook een knop voor een XPath-wizard om help te krijgen met het genereren van simpele XPath-expressies.

Standaard gebruikt calibre de volgende expressie voor hoofdstukdetectie:

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

Deze expressie is tamelijk ingewikkeld, ze probeert een aantal algemene zaken gelijktijdig af te handelen. Dit betekent dat calibre ervan uitgaat dat hoofdstukken beginnen met <h1> of <h2> tags die de woorden (hoofdstuk, boek, sectie of deel) bevatten of het attribuut class=”chapter” hebben.

Een verwante optie is Hoofdstuk markeren waarmee u controleert wat calibre doet bij detectie van een hoofdstuk. Standaard voegt het een pagina-einde in voor het hoofdstuk. U kunt een horizontale lijn laten invoegen in plaats van of met een pagina-einde. U kunt er ook voor kiezen om niets te doen.

De standaard instelling om pagina-einden te detecteren is:

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

wat betekent dat calibre standaard een pagina-einde invoegt vóór iedere <h1> en <h2>-tag.

Notitie

De standaard expressie kan anders zijn afhankelijk van het invoerformaat dat u wilt converteren.

Diversen

Er zijn nog een paar opties in deze sectie.

Metadata invoegen als pagina aan het begin

Een van de grote voordelen van calibre is dat u zeer uitgebreide metadata kunt bijhouden over al uw boeken, voor bijvoorbeeld een beoordeling, labels, commentaren etc. Deze optie voegt een enkele pagina in met alle metadata in het geconverteerde boek direct na de omslagfoto. Zie dit als u eigen gepersonaliseerde boekomslag.

Verwijder eerste afbeelding

Soms is de omslagfoto opgenomen in het bron-document in plaats van een afzonderlijke omslagfoto. indien u ook een omslagfoto pecificeert in Calibre zal het geconverteerde boek twee omslagfoto’s hebben. Deze optie zal de eerste foto van het bron-document verwijderen en zo ervoor zorgen dat het geconverteerde boek maar een omslagfoto krijgt en wel degene die u specificeert in Calibre.

Inhoudsopgave

Wanneer het invoer-document een Inhoudopgave heeft in de metadata zal calibre deze gebruiken. Maar een aantal oudere formaten ondersteunen of een op metadata gebaseerde inhoudsopgave niet of individuele documenten hebben er geen. In deze gevallen kunnen de opties helpen om een inhoudsopgave automatisch te genereren in het geconverteerde boek gebaseerd op de inhoud van het invoer-document.

Notitie

Het kan een uitdaging zijn om de opties precies goed te krijgen. Indien u liever de inhoudsopgave handmatig wilt maken/bewerken, converteer in EPUB of AZW3 formaten en vink het keuzevakje aan onder aan de sectie Inhoudsopgave in de conversie-dialoog Handmatig verfijnen van inhoudsopgave na conversie. Hiermee opent u de editor na de conversie. Hiermee kunt u items maken in de Inhoudsopgave door eenvoudig te klikken op de plek in het boek waarheen moet worden verwezen. U kunt tevens de editor zelf gebruiken zonder een conversie te doen. Ga naar Voorkeuren → Werkbalken en voeg de inhoudsopgave-editor toe aan de hoofdwerkbalk. Darna selecteert u het boek dat u wilt bewerken en klik op de editor-knop.

De eerste optie is Forceer gebruik auto gegenereerde Inhoudsopgave. Door deze optie aan te vinken dwingt u calibre de in de metadata gevonden inhoudsopgave te overschrijven met de automatisch gegenereerde.

De standaard werkwijze van aanmaken van een automatisch gegenereerde Inhoudsopgave is dat Calibre eerst probeert om hoofdstukken te detecteren en deze gaat toevoegen aan de inhoudopgave. U kunt meer te weten komen hoe u de detectie van hoofdstukken kunt aanpassen in bovenstaande sectie Structuurdetectie. Indien u de gedetecteerde hoofdstukken niet wilt toevoegen aan de gegenereerde inhoudsopgave, vink de keuze aan Gedetecteerde hoofdstukken niet toevoegen.

Indien minder dan het Hoofdstuk drempel aantal hoofdstukken zijn gedetecteerd, zal calibre alle hyperlinks in het invoerdocument aan de inhoudsopgave toevoegen. Dit functioneert vaak prima: veel invoerdocumenten hebben een gelinkte inhoudsopgave aan het begin. De Aantal links optie kan gebruikt worden om dit gedrag te controleren. Indien nu, worden geen links toegevoegd. Indien groter dan nul wordt dat aantal links toegevoegd.

Calibre filtert automatisch duplicaten uit van de gegenereerde inhoudsopgave. Maar als er extra ongewenste items zijn kunt u deze uitfilteren met de optie Inhoudsopgave filteren. Dit is een reguliere expressie die de titel van items afstemt met de gegenereerde inhoudsopgave. Zodra een overeenkomst gevonden wordt, zal het verwijderd worden. Bijvoorbeeld om alle itels “Volgende” of “Vorige” te verwijderen, gebruik:

Next|Previous

Met de optie Niveau 1,2,3 inhoudsopgave kunt u een gevanceerde gelaagde inhoudsopgave maken. Dat zijn XPath-expressies die overeenkomende labels in het tussentijdse XHTML-bestand zoeken. Zie Inleiding hoe u dit XHTML-bestand kunt openen. Lees ook XPath handleiding om te leren hoe XPath-expressies samengesteld worden. Naast iedere optie is een knop die een wizard opent om te helpen met het maken van een basis XPath-expressie. Het volgende eenvoudige voorbeeld laat zien hoe deze opties gebruikt kunnen worden.

Aangenomen u heeft een invoer-document dat resulteert in XHTML dat er als volgt uitziet:

<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>

Hierna stellen we de opties als volgt in:

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

Dit zal resulteren in een automatisch gegenereerde twee traps inhoudsopgave die er als volgt uitziet:

Chapter 1
Section 1.1
Section 1.2
Chapter 2
Section 2.1

Waarschuwing

Niet alle uitvoer-formaten ondersteunen een gelaagde inhoudsopgave. Probeer het eerst met EPUB. Als dat werkt, probeer het dan met het formaat van uw keuze.

Gebruik afbeeldingen als hoofdstuktitels tijdens de conversie van HTML invoer-documenten

Aangenomen dat u een afbeelding wilt gebruiken als hoofdstuktitel, maar u wilt toch dat calibre automatisch een Inhoudsopgave genereert van de hoofdstuktitels. Gebruik de volgende HTML code om dit te bereiken:

<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>

Stel de Niveau 1 Inhoudsopgave in als //h:h2. Voor hoofdstuk twee zal Calibre de titel ophalen van de waarde van het attribuut title uit de <h2> tag omdat deze geen tekst heeft.

Gebruik tag attributen om de tekst te halen t.b.v. Inhoudsopgave

Indien u bijzonder lange hoofdstuktitels heeft en kortere versies in de inhoudsopgave wilt hebben kunt u het “title”-attribuut gebruiken om dat te bereiken; bijvoorbeeld:

<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>

Stel de Niveau 1 Inhoudsopgave in als //h:h2/@title. Calibre haalt nu de titel op van de waarde van het title-attribuut van de <h2> labels in plaats van de tekst binnen de labels. N.B.: Het aanhangsel /@title in de XPath-expressie kunt u in deze vorm gebruiken om Calibre te vertellen om de tekst te halen van ieder attribuut dat u wenst.

Hoe opties worden ingesteld/opgeslagen voor conversie

Er zijn twee plaatsen waar de opties voor conversie kunnen worden ingesteld in Calibre. De eerste is in Voorkeuren->Conversie. Deze instellingen zijn de standaards voor de conversie-opties. Wanneer u probeert om een nieuw boek te converteren worden deze instellingen standaard gebruikt.

U kunt ook instellingen wijzigen in een conversie-dialoog voor elke boek-conversie. Wanneer u een boek converteert zal Calibre onthouden welke instellingen u heeft gebruikt voor dat boek; op deze manier zullen de opgeslagen instellingen voor ieder individueel boek voorrang krijgen boven de standaards in Voorkeuren als u dit boek opnieuw wilt converteren. U kunt de individuele instellingen terugzetten naar de standaards met behulp van de knop Hertelen naar standaards in het individuele boek in de conversie-dialoog. U kunt de opgeslagen instellingen verwijderen voor meerdere boeken door alle betreffende boeken te selecteren en dan te klikken op de knop Metadata bewerken om het dialoogvenster Bulk metadata bewerken onderaan van het dialoogvenster is een optie om opgeslagen conversie-instellingen te verwijderen.

Wanneer u meerdere boeken in bulk converteerd worden de instellingen genomen in de onderstaande volgorde (de laatste overheerst):

- Van de standaards ingesteld in Voorkeuren->Conversie

- Van de opgeslagen conversie-instellingen voor elk boek dat wordt geconverteerd (indien aanwezig). Dit kan worden uitgeschakeld door de optie in de linkerbovenhoek van het dialoogvenster Bulkconversie.

- Van de instellingen in het Bulk-conversie-dialoogvenster

N.B.: De laatste instellingen voor elk boek in een Bulk conversie worden opgeslagen en opnieuw gebruikt als dat boek opnieuw wordt geconverteerd. Aangezien de hoogste prioriteit in Bulkconversie wordt gegeven aan de instellingen in het dialoogvenster Bulkconversie, zullen deze alle boekspecifieke instellingen overschrijven. Daarom moet u alleen boeken in bulk converteren die soortgelijke instellingen nodig hebben. De uitzonderingen zijn metadata en invoer-formaat specifieke instellingen. Omdat het dialoogvenster voor Bulk Conversie geen instellingen heeft voor deze twee opties, worden deze gehaald uit de boek-specifieke instellingen (indien aanwezig) of de standaards.

Notitie

U kunt de actuele instellingen zien die tijdens conversie gebruikt worden door te klikken op te roterende pictogram in de rechter benedenhoek en dat dubbelklikken op de individuele conversietaak. Dit opent het logboek van de conversie en bevat de actuele instellingen bovenaan.

Formaat specifieke tips

Hier vindt u tips betreffende conversie van specifieke formaten. Opties voor specifieke formaten voor invoer of uitvoer die beschikbaar zijn in het converie-dialoogvenster onder hun eigen sectie, bijvoorbeeld TXT invoer of EPUB uitvoer.

Microsoft Word documenten omzetten
calibre kan “.docx” bestanden gemaakt in Microsoft Word 2007 of later automatisch converteren. Voeg het bestand toe aan Calibre en klik “converteren”.

Notitie

Er is een demo .docx-bestand [https://calibre-ebook.com/downloads/demos/demo.docx] dat de mogelijkheden van de Calibre-conversie-engine demonstreert. Download het gewoon en converteer het naar EPUB of AZW3 om te zien wat Calibre kan doen.

Calibre zal automatisch een Inhoudsopgave genereren gebaseerd op titels als u deze markeert met de stijlen Titel 1, Titel 2 etc. in Word. Open de uitvoer e-boek in de Calibre lezer en klik op de knop Inhoudsopgave om de gegenereerde Inhoudsopgave te bekijken.

Oudere .doc bestanden

Voor oudere .doc-bestanden kunt u in Microsoft Word ht document opslaan als HTML en het HTML-bestand converteren met Calibre. Bij het opslaan als HTML moet u wel erop letten dat u de optie “Opslaan als webpagina, gefilterd” gebruikt omdat dit zorgt voor een zuivere HTML-code dat zich goed laat converteren. N.B.: Word maakt werkelijk rommelige HTML en het converteren kan lange tijd duren, wees geduldig. Als u beschikt over een nieuwere versie van Word kunt u het ook meteen opslaan als docx.

Een ander alternatief is om het gratis LibreOffice te gebruiken. Open uw .doc-bestand in LibreOffice en sla het op als .docx, dat direct in Calibre kan worden geconverteerd.

TXT documenten omzetten

TXT documenten hebben geen formattering zoals vetgedrukt, cursief e.d. of documentstructuur zoals alinea’s, titels, secties e.d. maar er bestaan meerdere algemeen gebruikte conventies. Standaard probeert calibre automatische detectie van de correcte formattering en opmaak gebaseerd op deze conventies.

TXT invoer ondersteunt een aantal opties om onderscheid te maken hoe alinea’s worden gedetecteerd.

Alinea stijl: Auto

Analyseert het tekstbestand en probeert automatisch te bepalen hoe alinea’s zijn gedefiniëerd.Deze optie zal prima functioneren; indien u ontevreden bent met de resultaten probeer een van de handmatige opties.

Alinea stijl: Blok

Gaat ervan uit dat een of meer lege regels een alinea aangeven:

This is the first.

This is the
second paragraph.

Alinea stijl: Enkele

Gaat ervan uit dat elke regel een alinea is:

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

Alinea stijl: Print

Gaat ervan uit dat elke alinea begint met een inspringing (of een tab of 2+ spaties). Alinea eindigt wanneer de volgende regel die begint met een inspringing is bereikt:

This is the
first.
This is the second.

This is the
third.

Alinea stijl: Ongeformatteerd

Gaat ervan uit dat het document niet opgemaakt is maar wel harde regeleinden heeft. Leestekens en gemiddelde regellengtes worden gebruikt om alinea’s te herstellen.

Alinea stijl: Auto

Tracht het type van formatering opmaak te dectecteren. Als er geen opmaak is gebruikt, zal de formatting heuristisch toegepast worden

Activeer heuristische verwerking

Doorzoekt het document naar hoofdstuktitels, scene-onderbrekingen en cursief geschreven woorden en zal de passende HTML-code toepassen tijdens conversie.

Formattering: Markdown

Calibre ondersteunt ook de invoer van TXT door een transformatie-preprocessor bekend als Markdown. Markdown voegt een basis-opmaak toe aan TXT-documenten zoals vetgedrukt, cursiev, sectie-titels, tabellen, lijsten, een Inhoudsopgave etc. Het markeren van hoofdstuktitels met een voorlopende # en hoofdstuk XPath detectie-expressie in te stellen als “//h:h1” is de makkelijkste manier om een nette inhoudsopgave te laten genereren van een TXT-document. U kunt meer lezen over de markdown-syntax op daringfireball [http://daringfireball.net/projects/markdown/syntax].

Alinea stijl: Ongeformatteerd

Past geen speciale opmaak toe op de tekst, het document wordt geconverteerd naar HTML zonder andere wijzigingen.

PDF-documenten omzetten

PDF-documenten zijn van de slechtste formaten om van te converteren. Ze hebben een vast pagina en tekstplaatsingsformaat . Heel moeilijk dus om te bepalen waar een alinea eindigt en een andere begint. calibre zal proberen paragrafen te ontwarren met een configureerbare Line un-wrapping factor. Deze schaal dient om de lengte te bepalen waarop een lijn wordt afgebroken. Geldige waarden zijn een decimaal tussen 0 en 1. Standaard: 0,45, net onder de mediaanlijnlengte. Verlaag deze waarde om meer tekst toe te voegen aan het ontwarren. Verhoog voor minder. U kunt deze waarde aanpassen in de conversie-instellingen onder PDF Input.

Ze hebben ook vaak kop- en voetteksten als onderdeel van het document dat bij de tekst wordt opgenomen. Gebruik het: guilabel: Zoeken en vervangen paneel om kop- en voetteksten te verwijderen om dit probleem te verhelpen. Als de kop- en voetteksten niet uit de tekst worden verwijderd, kan het uitpakken van de alinea weggooien. Lees: ref: regexptutorial om te leren hoe u de opties voor het verwijderen van kop- en voetteksten gebruikt.

Enkele beperkingen van PDF invoer zijn:

- Ingewikkelde documenten met meerdere kolommen en afbeeldingen worden niet ondersteund.

- Extraheren van vector fotobestanden en tabellen vanuit het document is ook niet ondersteund.

- Sommige PDF’s gebruiken speciale hiërogliefen of symbolen om ll or ff or fi etc. weer te geven. Conversie van deze kan functioneren of ook niet afhankelijk van hoe deze intern worden weergegeven in het PDF.

- Links en Inhoudopgaves worden niet ondersteund

- PDF’s die ingesloten niet-Unicode-lettertypen gebruiken om niet-Engelse tekens weer te geven, zullen resulteren in onleesbare uitvoer voor die tekens

- Sommige PDFs maken gebruik van foto’s met OCR tekst. In zulke gevallen zal Calibre de OCR tekst gebruiken, wat heel anders kan zijn van wat je ziet als je de PDF bekijkt

- PDFs die gebruik maken van complexe tekst zoals recht naar links talen en wiskundige formules zullen niet juist worden geconverteerd.

Om te herhalen PDF is echt, echt slecht formaat om te gebruiken als input. Als je absoluut PDF wilt gebruiken, dan kan de output varieëren van uitstekend tot onbruikbaar, afhankelijk van de input PDF.

Stripboek collecties

Een comic boek collectie is een .cbc-bestand. Een .cbc-bestand in een gezipt bestand dat andere CBZ/CBR bestanden bevat. Daarnaast moet een .cbc-bestand een simepel tekstbestand bevatten genaamd comics.txt, in UTF-8 codering. Het comics.txt bestand moet een lijst bevatten van de comics-bestanden binnen het .cbc-bestand in de vorm van bestandsnaam:titel zoals hieronder:

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

Het .cbc bestand zal dan bevatten:

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

Calibre zal dit .cbc-bestand automatisch converteren in een e-boek met een Inhoudsopgave met links naar iedere ingave in comics.txt.

EPUB geavanceerde opmaak demo

Verschillende geavanceerde opmaak voor EPUB-bestanden wordt gedemonstreerd in dit demobestand [https://calibre-ebook.com/downloads/demos/demo.epub]. Het bestand is gemaakt van met de hand gecodeerde HTML met Calibre en is bedoeld om te worden gebruikt als een sjabloon voor uw eigen inspanningen om EPUB te maken.

De bron-HTML waaruit het is gemaakt, is beschikbaar: download_file:demo.zip <demos/demo.zip>. De instellingen die worden gebruikt om de EPUB van het ZIP-bestand te maken, zijn:

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

N.B.: Omdat het bestand de mogelijkheden van EPUB verkent zullen de meeste geavanceerde opmaak niet werken op readers die minder kunnen dan de ingebouwde EPUB lezer van Calibre.

ODT documenten omzetten

Calibre kan direct ODT (OpenDocument Tekst) bestanden converteren. U moet stijlen gebruiken om uw document op te maken en zo min mogelijke interne opmaak gebruiken. Wanneer u afbeeldingen in uw document invoegt moet u deze verankeren aan een alinea. Afbeeldingen verankerd aan een pagina zullen aan het begin van de conversie eindigen.

Om automatische detectie van hoofdstukken te activeren, moet u deze markeren met de ingebouwde stijlen genaamd Kop 1, Kop 2, …, Kop 6 (Kop 1’ is gelijk aan de HTML tag <h1>, Kop 2 aan <h2> enz). Als u converteert in calibre kunt u opgeven welke stijl u gebruikte in het Detecteer hoofdstukken op vak . Voorbeeld:

- Als u Hoofdstukken markeert met stijl Kop 2, moet u in het vak ‘Detecteer hoofdstukken op’ //h:h2 invullen

- Voor een geneste Inhoudsopgave met Secties gemarkeerd met Kop 2 en de Hoofdstukken gemarkeerd met Kop 3 moet u //h:h2|//h:h3 invullen. Op de Conversie - Inhoudsopgave pagina vult u in het vak Niveau 1 Inhoudsopgave //h:h2 in en in het vak Niveau 2 Inhoudsopgave vult u //h:h3 in.

De bekende document-eigenschappen (Titel, sleutelwoorden, beschrijving, maker) worden herkent en Calibre gebruikt de eerste afbeelding (niet te klein en met de juiste aspect-ratio) als omslag-afbeelding.

Er is zelfs een geavanceerde eigenschappen conversie-modus, die geactiveerd wordt in de aangepaste eigenschap opf.metadata `` ('Ja of Nee') voor Ja te kiezen in uw ODT-document (Bestand->Eigenschappen->Gebruikergedefinieerde eigenschappen). Wanneer deze eigenschap door calibre wordt gedetecteerd worden de volgende aangepaste eigenschappen herkend (``opf.authors overschrijft de auteur van het document):

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

Daarnaast kunt u de afbeelding specificeren de gebruikt wordt als omslag door deze te benoemen als opf.cover (rechts klikken, Afbeelding->Opties->Naam) in het ODT. Wanneer geen afbeelding met deze naam wordt gevonden, zal de ‘slimme’ methode toegepast. Omdat de omslag-detectie kan resulteren in dubbele omslagen in bepaalde uitvoer-formaten, zal het proces de alinea verwijderen (maar alleen indien de inhoud de omslag is!) van het document. Maar dit functioneert alleen met de benoemde afbeelding!

Om omslag-detectie uit te schakelen kunt u de gebruikersgedefineerde eigenschap opf.nocover (‘Ja of Nee’) instellen op Ja in geavanceerde modus.

Naar PDF omzetten

De eerste, belangrijkste instelling om te bepalen bij conversie naar PDF is het paginaformaat. Standaard gebruikt calibre “U.S. letter”. U kunt dit wijzigen naar een ander standaard formaat of een volledig aangepast in de PDF Output sectie van de conversie dialoog. Als u een PDF genereert om op een specifiek toestel te gebruiken, kunt u de optie inschakelen om het formaat te gebruiken van het :guilabel:’output profile`. Dus als uw uitvoerprofiel is ingesteld op Kindle, maakt calibre een PDF met een formaat geschikt om te bekijken op het kleine Kindle scherm.

Kop- en voetteksten

U kunt willekeurige kop- en voeterteksten insluiten op elke pagina van een PDF door kop- en voetertekst-sjablonen te specificeren. Sjablonen zijn knipsels van HTML-code die worden gerenderd op de plekken voor kop- en voetertekst. Bijvoorbeeld om pagina-nummers te centreren onder op elke pagina in groen gebruikt u het volgende voetersjabloon:

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

Calibre zal automatisch _PAGENUM_ vervangen door de huidige paginanummer. U kunt zelfs andere inhoud op even en oneven pagina’s plaatsen, zo zal het volgende koptekst-sjabloon de titel zien op oneven pagina’s en de auteur op even pagina’s:

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

calibre zal automatisch: code: _TITLE_ en :code:` _AUTHOR_` vervangen door de titel en auteur van het document dat wordt geconverteerd. Instelling: code: justify-content to: code:` flex-end` zorgt ervoor dat de tekst goed wordt uitgelijnd.

U kunt ook tekst aan de linker en rechter rand weergeven en de lettergrootte wijzigen, zoals aangetoond met deze koptekst sjabloon:

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

Dit laat de titel links zien en de auteur rechts en in een lettergrootte die iets kleiner is dan de hoofdtekst.

U kunt ook de huidige sectie in sjablonen gebruiken, zoals hieronder te zien is:

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

_SECTION_ is replaced by whatever the name of the current section is. These names are taken from the metadata Table of Contents in the document (the PDF Outline). If the document has no table of contents then it will be replaced by empty text. If a single PDF page has multiple sections, the first section on the page will be used. Similarly, there is a variable named _TOP_LEVEL_SECTION_ that can be used to get the name of the current top-level section.

U kunt JavaScript zelfs gebruiken in de kop- en voettekstsjablonen. De volgende sjabloon zorgt er bijvoorbeeld voor dat paginanummers beginnen met 4 in plaats van 1:

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

Bovendien zijn er nog enkele variabelen die u kunt gebruiken in de kopteksten en voetteksten, zoals hieronder beschreven:

- `` _TOTAL_PAGES_ ‘’ - totaal aantal pagina’s in het pdf-bestand, handig om bijvoorbeeld een voortgangsteller te implementeren.

- `` _TOP_LEVEL_SECTION_PAGES_ ‘’ - totaal aantal pagina’s in de huidige hoogste niveau sectie

- `` _TOP_LEVEL_SECTION_PAGENUM_ ‘’ - het paginanummer van de huidige pagina binnen de huidige hoogste niveau sectie

Notitie

Wanneer u kop- en voetteksten toevoegt, zorg er dan voor dat u de boven- en ondermarges van de pagina instelt op waarden die groot genoeg zijn, onder het gedeelte: guilabel: PDF Output van het conversie-dialoogvenster.

Afdrukbare inhoudsopgave

U kunt ook een afdrukbare Inhoudsopgave invoegen aan het einde van het PDF dat de paginanummers voor elke sectie laat zien. Dit is zinvol als u het PDF op papier wilt afdrukken. Indien u het PDF op een electronisch apparaat wilt gebruiken, dan biedt de PDF samenvatting deze functionaiteit en wordt standaard gegenereerd.

U kunt het uiterlijk van de gegenereerde inhoudsopgave aanpassen door de instelling Extra CSS-conversie te gebruiken onder het Uiterlijk en Gevoel gedeelte van de conversiedialoog. De standaard gebruikte CSS wordt hieronder vermeld, kopieer deze gewoon en breng de gewenste wijzigingen aan.

.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 }

Aangepaste paginamarges voor individuele HTML-bestanden

Als u een EPUB- of AZW3-bestand converteert met daarin meerdere afzonderlijke HTML-bestanden en u de paginamarges voor een bepaald HTML-bestand wilt wijzigen, kunt u het volgende stijlblok aan het HTML-bestand toevoegen met de calibre E-book editor:

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

Schakel vervolgens in het gedeelte PDF-uitvoer van het conversiedialoog de optie in: guilabel:Gebruik paginamarges van het document dat wordt geconverteerd. Nu hebben alle pagina’s die uit dit HTML-bestand zijn gegenereerd `` 10pt``-marges.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- E-boeken bewerken

E-boeken bewerken

Calibre heeft een geïntegreerde e-boek editor die kan worden gebruikt EPUB en AZW3 (Kindle) formaten. De verwerker laat je de HTML en CSS zien die intern in het boek bestand gebruikt wordt. Met een direct voorbeeld zie je de wijzigingen die je maakt direct terug. Het programma bevat ook diverse functies voor het opschonen van de code of het afmaken van taken.

U kunt deze editor activeren door rechts te klikken op een boek in Calibre en kiezen voor Boek bewerken.

Inhoud

Basis werkstroom

De Verkenner

- Bestanden hernoemen

- Bestanden samenvoegen

- Volgorde van tekstbestand veranderen

- Kaft markeren

- Bestanden verwijderen

- Bestanden exporteren

- Nieuwe afbeeldingn/lettertype/enz. toevoegen of nieuwe lege bestanden maken

- Bestanden vervangen

- Het efficiënt linken van sylesheets aan HTML-bestanden

Zoeken & vervangen

- Opgeslagen zoekopdrachten

- Functie modus

- Zoeken tussen HTML labels

Geautomatiseerde functies

- Inhoudsopgave bewerken

- Het boek controleren

- Cover toevoegen

- Insluiten van gerefereerde lettertypes

- Ingevoegde lettertypes uitdunnen

- Interpunctie verbeteren

- Transformeer CSS eigenschappen

- Ongebruikte CSS regels verwijderen

- Repareer HTML

- Alle bestanden leesbaarder maken

- Een inline Inhoudsopgave invoegen

- Semantiek instellen

- Stijl informatie filteren

- Boek ingewanden upgraden

Checkpoints

Het Live voorbeeld paneel

- HTML bestanden splitsen

Het realtime CSS paneel

Diverse gereedschappen

- De Inhoudsopgave weergave

- Spellingscontrole van woorden in het boek

- Speciale tekens invoegen

- De code controle weergave

- Controle van externe links

- Externe bronnen downloaden

- Bestanden sorteren in mappen op type

- Importeren van bestanden in andere e-book formaten als EPUB

- De rapporten functie

Speciale mogelijkheden in de code editor

- Syntaxis markering

- Contextgevoelige hulp

- Auto-aanvullen

- Fragmenten

Basis werkstroom

Notitie

A video tour of the calibre E-book editor is available here [https://calibre-ebook.com/nl/demo#tutorials].

Wanneer u voor het eerst een boek opent met het Boek bewerken gereedschap ziet u een overzicht van bestanden aan de linkerkant. Dit zijn de individuele HTML bestanden, stylesheets, afbeeldingen enz. die de inhoud van het boek opmaken. Dubbelklik op een bestand om deze te bewerken. Als u een paar kleine aanpassingen wilt doen is dat ok; voor ingewikkeldere wijzigingen moet u zich verdiepen in de HTML Handleiding [http://html.net/tutorials/html/] en CSS handleiding [http://html.net/tutorials/css/].

Wanneer u in de editor wijzigingen aanbrengt in de HTML of CSS, worden de wijzigingen in het voorbeeldvenster aan de rechterkant in een voorbeeld van de wijzigingen weergegeven. Als je tevreden bent met hoe de veranderingen die je hebt gemaakt kijken, klik dan op de Opslaan knop of gebruik :guilabel:` Bestand->Opslaan` om je wijzigingen in het e-boek op te slaan.

One useful feature is Checkpoints. Before you embark on some ambitious set of edits, you can create a checkpoint. The checkpoint will preserve the current state of your book, then if in the future you decide you don’t like the changes you have made to you can go back to the state when you created the checkpoint. To create a checkpoint, use Edit → Create checkpoint. Checkpoints will also be automatically created for you whenever you run any automated tool like global search and replace. The checkpointing functionality is in addition to the normal undo/redo mechanism when editing individual files. Checkpoints are needed for when changes are spread over multiple files in the book.

Dat is in feite de manier om boeken te bewerken – Open een bestand, maak veranderen, bekijk een voorbeeld en sla op. De rest van deze handleiding zal de verschillende hulpmiddelen behandelen die het mogelijk maken dat u specifieke taken zo goed mogelijk uit kan voeren.

De Verkenner

De Verkenner toont een overzicht van de diverse bestanden binnen het boek dat u bewerkt. De bestanden staan op gesorteerd op categorie, met tekst (HTML) bestanden bovenaan, gevolgd door stylesheet (CSS) bestanden, afbeeldingen enz. Dubbelklikken op een bestand opent deze voor het bewerken. Het bewerken wordt ondersteund voor HTML, CSS en afbeeldingen. De volgorde van tekstbestanden is dezelfde als tijdens het lezen in het boek. Alle andere bestanden zijn alfabetisch gesorteerd.

Door met de muisaanwijzer over een bestand te gaan toont de grootte en onderaan het scherm het gehele pad naar het bestand in het boek. Bedenk wel dat bestanden binnen e-boeken gecomprimeerd zijn. De grootte van het boek zelf is dus niet de som van de grootte van de individuele bestanden.

Veel bestanden hebben een speciale betekenis in het boek. Deze hebben normaal een icoon naast hun namen om de speciale betekenis aan te geven. Bv. in de afbeelding links ziet u dat de bestanden cover_image.jpg en titlepage.xhtml het icoon van een omslag naast zich hebben om aangevend dat het boekomslagafbeelding en titelpagina zijn. Het bestand content.opf heeft een metadata icoon om aan te geven dat de boek metadata er aanwezig zijn en het bestand toc.ncx heeft een T icoon om aan te geven dat er een Inhoudsopgave is.

U kunt meerdere acties uitvoeren op bestanden door er met de rechter muisknop op te klikken.

Bestanden hernoemen

U kunt een bestand hernoemen door er rechts op te klikken en kiezen voor Hernoemen. Hernoemen van een bestand zal automatisch alle links en referenties bijwerken in het gehele boek. Dus alles wat u moet doen is een nieuwe naam in te geven en Calibre doet de rest.

U kunt ook veel bestanden in groep hernoemen. Dit is bruikbaar als u wilt dat de bestanden een simpel naamschema volgen. Bv.: U wilt alle HTML bestanden hernoemen naar Hoofdstuk-1.html, Hoofdstuk-2.html enz. Selecteer de bestanden die u wilt hernoemen door de Shift or Ctrl toets ingedrukt te houden en op de bestanden te klikken. Dan rechtsklikken en selecteer In groep hernoemen. Geef een voorvoegsel in en het begingetal voor de automatische nummering, klik OK en het is klaar. De in groep hernoemen dialoog laat u ook bestanden hernoemen in de volgorde waarin ze verschijnen in het boek in plaats van waarin u ze selecteerde, bruikbaar, bijvoorbeeld, om alle afbeeldingen te hernoemen in de volgorde waarin ze verschijnen.

Tenslotte kan u de bestandsextensie in groep veranderen voor alle geselecteerde bestanden. Selecteer meerdere bestanden, zoals boven, rechtsklikken en kies Verander de extensie voor de geselecteerde bestanden.

Bestanden samenvoegen

Soms wilt u misschien twee HTML bestanden of twee CSS bestanden samenvoegen. Het kan soms bruikbaar zijn om alles in een enkel bestand te hebben. Pas op toch want veel inhoud in een enkel bestand zetten kan prestatieproblemen geven bij lezen van het boek in een standaard reader.

Om meerdere bestanden samen te voegen, selecteer door de Ctrl toets ingedrukt te houden en erop te klikken (selecteer enkel bestanden van één type, alle HTML bestanden of alle CSS-bestanden enz.). dan rechts klikken en selecteer samenvoegen. Dat is alles. calibre voegt de bestanden samen en let erop dat alle links en referenties wijzigen naar de samengevoegde bestanden. N.B.: Samenvoegen van bestanden kan soms tekstopmaak wijzigen omdat individuele bestanden misschien verschillende stijlbladen gebruikten.

U kan ook tekstbestanden selecteren en ze slepen en neerzetten op een ander tekstbestand om de geselecteerde met het doel tekstbestand samen te voegen.

Volgorde van tekstbestand veranderen

You can re-arrange the order in which text (HTML) files are opened when reading the book by simply dragging and dropping them in the File browser or clicking on the file to move and then pressing the Ctrl+Shift modifiers with the Up, Down, Home or End keys. For the technically inclined, this is called re-ordering the book spine.

Note that you have to drop the items between other items, not on top of them, this can be a little fiddly until you get used to it. Dropping on top of another file will cause the files to be merged.

Kaft markeren

E-boeken hebben normaal een omslagafbeelding. Deze afbeelding wordt aangegeven in de Verkenner door een icoon van een bruin boek naast de afbeeldingsnaam. Als u een andere afbeelding als omslag wilt, kunt u dit doen door rechtsklikken op het bestand en kiezen Markeer als omslag.

Daarnast kennen EPUB-bestanden een titelpagina. Een titelpagina is een HTML-bestand die ageert als de titelpagina/omslag van het boek. U kunt een HTML-bestand markeren als de titelpagina tijdens het bewerken van EPUBs door rechts te klikken. Indien er ook andere inhoud in staat zoals het eerste hoofdstuk dan gaat deze inhoud verloren als de gebruiker ooit het EPUB-bestand converteert in Calibre naar een ander formaat. Dit gebeurt omdat Calibre ervan uitgaat dat de gemarkeerde titelpagina alleen de omslag bevat en geen andere inhoud.

Bestanden verwijderen

U kunt bestanden verwijderen door er rechts op te klikken of door ze te selecteren en de Del-toets in te drukken. Het verwijderen van een bestand verwijdert alle referenties tot het bestand van het OPF-bestand zodat u dit klusje niet zelf hoeft te doen. Hoewel referenties op andere locaties niet worden verwijderd, kunt u het gereedschap Boek controleren gebruiken om deze makkelijk te vinden en verwijderen/vervangen.

Bestanden exporteren

U kunt een bestand exporteren van een boek naar ergens anders op uw computer. Dit is nuttig als u aan een bestand wilt werken met gespecialiseerde software. Om dit te doen rechtsklikt u op het bestand en selecteer Export.

Als u klaar bent met bewerken aan het geëxporteerde bestand unt u dit opnieuw importeren in het boek door weer rechts te klikken op het bestand en selecteer Vervangen door bestand… waardoor u het bestand in het boek kunt vervangen door het eerder geëxporteerde bestand.

U kan ook bestanden kopiëren tussen verschillende editor instanties. Selecteer de bestanden die u wilt kopiëren in Bestand browser, klik rechts en kies, Kopieer geselecteerde bestanden naar een andere editor instantie. Dan, in de andere editor instantie, klik rechts in de Bestand browser en kies Plak bestand van andere editor instantie.

Nieuwe afbeeldingn/lettertype/enz. toevoegen of nieuwe lege bestanden maken

U kunt een nieuwe afbeelding, lettertype, stijlblad, enz. toevoegen van uw computer in een boek door te klikken op Bestand → Nieuw bestand. Dit laat u ofwel een bestand importeren door te klikken op de Importeer bestand knop ofwel een nieuw leeg HTML bestand of stijlblad maken door een bestandsnaam in te vullen in het vakje voor het nieuwe bestand.

U kunt ook tegelijkertijd meerdere bestanden importeren in het boek met behulp van Bestand->Importeer bestanden in boek.

Bestanden vervangen

U kunt makkelijk bestaande bestanden in het boek vervangen door rechts te klikken op het bestand en selecteer vervangen. Hiermee worden alle links en referenties automatisch bijgewerkt, in het geval dat het vervangende bestand een andere naam heeft dan het bestand dat wordt vervangen.

Het efficiënt linken van sylesheets aan HTML-bestanden

Voor uw comfort kunt u meerdere HTML bestanden selecteren in de Bestand browser, rechtsklikken en Stijlbladen linken kiezen om automatisch de <link> tags in te voegen voor die stijlbladen in alle geselecteerde HTML bestanden.

Zoeken & vervangen

Boek bewerken heeft een zeer krachtige zoek/vervang interface waarmee u tekst kunt zoeken/vervangen in het huidige bestand, in alle bestanden en zelfs in een gemarkeerd deel van het huidige bestand. U kunt normaal zoeken of reguliere expressies gebruiken. Over het gebruik van reguliere expressies om geavanceerd te zoeken, zie Alles over het gebruik van reguliere expressies in calibre.

Start zoeken en vervangen met menu-item Zoeken → Zoek/vervang (u moet dan wel een HTML of CSS-bestand aan het bewerken zijn).

Type de tekst die u wilt vinden in het Zoek-vakje en de vervanger in het Vervang-vakje. U kunt de betreffende knoppen klikken om de volgende match te vinden, de huidige match vervangen en alle matches vervangen.

Door de keuzelijst onderaan het kader te gebruiken, kunt u zoeken in het huidig bestand, alle tekstbestanden, alle stylesheets of alle bestanden. U kunt ook de zoekmethode kiezen, dus een normale (tekenreeks) zoekopdracht of met een reguliere expressie.

U kunt alle matches tellen van een zoekopdracht met :guilabel:`Zoeken->Tellen `. De teller loopt over alles, bestanden/delen die u heeft geselecteerd in de keuzelijst.

U kunt ook springen naar een bepaalde regel in de huidige geopende editor met Zoeken → Spring naar regel.

Notitie

Benut de maximale kracht van zoeken en vervangen met reguliere expressies. Zie Alles over het gebruik van reguliere expressies in calibre.

Opgeslagen zoekopdrachten

U kunt regelmatig gebruikte zoek/vervang uitdrukkingen (ook functie modus expressies) opslaan en meermaals herbruiken. Om een zoekopdracht op te slaan, rechtsklik in het Zoek vak en selecteer Huidige zoekopdracht opslaan.

De opgeslagen zoekopdrachten kunt u tonen via Zoeken → Opgeslagen zoekopdrachten. U ziet een lijst met zoek/vervang opdrachten die u kunt toepassen. U kunt zelfs meerdere opdrachten selecteren uit de lijst door de Ctrl toets ingedrukt te houden tijden het klikken om meerdere zoek/vervang-opdrachten uit te voeren in één bewerking.

Functie modus

Functie modus laat u willekeurige, krachtige Python functies schrijven die draaien op elke Zoek/vervang. U kan ongeveer elke tekstmanipulatie doen die u wilt in functie modus. Voor meer informatie bekijk Functie modus voor zoek & vervangen in de editor.

Zoeken tussen HTML labels

Er is ook een speciale mogelijkheid om naar tekst te zoeken tussen HTML-labels. Bijvoorbeeld als in de HTML-code de nadruk is op een <i>woord</i>. kunt u zoeken op op een woord en het wordt gevonden ondanks dat er een <i> tag staat. Gebruik deze functie met het menu-item Zoeken → Zoeken HTML markup negeren.

Geautomatiseerde functies

Boek bewerken heeft diverse gereedschappen om te helpen met algemene taken. Deze zijn toegankelijk in het Gereedschappen menu.

Inhoudsopgave bewerken

Er is een speciaal gereedschap om het bewerken van de Inhoudsopgave te versoepelen. Zet het in gang met Gereedschappen → Inhoudsopgave → Inhoudsopgave bewerken.

Dit gereedschap laat de de huidige Inhoudsopgave (indien aanwezig) zien aan de linkerkant. Dubbelklik op een vermelding om de tekst te wijzigen. U kunt ook de volgorde van vermeldingen wijzigen door deze te slepen en te plaatsen of de knoppen aan de rechterkant te gebruiken.

Voor boeken die geen inhoudsopgave hebben levert het gereedschap diverse opties om een Inhoudsopgave automatisch te laten genereren uit de tekst. U kunt de hierbij gebruik maken van de hoofdstukken in het document, van links, van individuele bestanden enzovoort.

U kunt vermeldingen bewerken door erop te klikken en dan te klikken op de knop Wijzig de locatie waarheen deze vermelding verwijst. Dit opent een klein weergave-venster van het boek, beweeg de muisaanwijzer over het weergave-venster en klik waar u wilt dat de vermelding naartoe verwijst. Een dikke groene lijn toont u de plaats. Klik Ok als u tevreden bent met de plaats.

Het boek controleren

Het gereedschap Boek controleren zoekt naar mogelijke knelpunten in uw boek waardoor het op readers niet werkt als bedoeld. Activeer dit met Gereedschappen → Boek controleren.

Alle gevonden problemen worden gerapporteerd in een mooie, gebruiksvriendelijke lijst. Op een vermelding klikken, toont u enige hulp over die fout en biedt u de optie die te auto-herstellen indien mogelijk. U kan ook dubbelklikken op de fout om de locatie in een editor te openen zodat u het zelf kan herstellen.

Sommige van onderdelen van de controle zijn:

- Slechtgevormde HTML. Alle HTML die niet als goedgevormde XML verwerkt wordt, wordt gerapporteerd. Herstellen zorgt ervoor dat uw markup in elke context werkt zoals bedoeld. calibre kan deze fouten ook auto-herstellen maar dit heeft soms onverwachte effecten, omzichtig te gebruiken dus. Zoals altijd wordt een checkpunt gemaakt vóór auto-herstellen zodat u alles gemakkelijk terug kan draaien. Auto-herstellen werkt door markup verwerking door het zeer fout tolerante HTML5 algoritme, gevolgd door conversie naar doed gevormde XML.

- Verkeerde of onbekende CSS stijlen. Alle ongeldige CSS of met eigenschappen niet gedefinieerd in de CSS 2.1 standaard (plus enkele van CSS3) wordt gerapporteerd. CSS wordt gecontroleerd in alle stijlbladen, inline stijlattributen en <style> tags in HTML bestanden.

- Onbekende linken. Linken die wijzen naar bestanden in het boek die niet bestaan, worden gerapporteerd.

- Niet-gebruikte bestanden. Bestanden in het boek, niet gebruikt door andere bestanden of niet in de opbouw worden gerapporteerd.

- Verscheidene doorsnee problemen in OPF bestanden zoals dubbele opbouw of manifest items, gebroken idrefs or meta omslag tags, ontbrekende vereiste onderdelen en zo voort.

- Diverse controles op compatibiliteit betreffende bekende veelvoorkomende problemen die het boek slecht laten werken op e-readers.

Cover toevoegen

You can easily add a cover to the book via Tools → Add cover. This allows you to either choose an existing image in the book as the cover or import a new image into the book and make it the cover. When editing EPUB files, the HTML wrapper for the cover is automatically generated. If an existing cover in the book is found, it is replaced. The tool also automatically takes care of correctly marking the cover files as covers in the OPF.

Insluiten van gerefereerde lettertypes

Accessed via Tools → Embed reference fonts, this tool finds all fonts referenced in the book and if they are not already embedded, searches your computer for them and embeds them into the book, if found. Please make sure that you have the necessary copyrights for embedding commercially licensed fonts, before doing this.

Ingevoegde lettertypes uitdunnen

Accessed via Tools → Subset embedded fonts, this tool reduces all the fonts in the book to only contain glyphs for the text actually present in the book. This commonly reduces the size of the font files by ~ 50%. However, be aware that once the fonts are subset, if you add new text whose characters are not previously present in the subset font, the font will not work for the new text. So do this only as the last step in your workflow.

Interpunctie verbeteren

Converteer platte tekst streepjes, weglatingstekens, aanhalingstekens, meerdere afbreekstreepjes, enz. om naar de typografisch juiste equivalenten. NB, het algoritme kan soms foute resultaten geven, met name als enkele aanhalingstekens in het spel zijn bij het begin van samentrekkingen. Toegankelijk via Gereedschappen → Slimme interpunctie.

Transformeer CSS eigenschappen

Create rules to transform the styling of the book. For example, create a rule to convert all red text to green or to double the font size of all text in the book or make text of a certain font family italic, etc.

De regels creëren is eenvoudig, de regels volgen een natuurlijk taalformaat dat eruit ziet als:

- Als de eigenschap kleur rood is, verander het naar groen

- Als de eigenschap lettertype-grootte elke waarde is, vermenigvuldig de waarde met 2

Toegankelijk via Gereedschappen → Transformeer stijlen.

Ongebruikte CSS regels verwijderen

Remove all unused CSS rules from stylesheets and <style> tags. Some books created from production templates can have a large number of extra CSS rules that don’t match any actual content. These extra rules can slow down readers that need to process them all. Accessed via Tools → Remove unused CSS.

Repareer HTML

This tool simply converts HTML that cannot be parsed as XML into well-formed XML. It is very common in e-books to have non-well-formed XML, so this tool simply automates the process of fixing such HTML. The tool works by parsing the HTML using the HTML5 algorithm (the algorithm used in all modern browsers) and then converting the result into XML. Be aware that auto-fixing can sometimes have counter-intuitive results. If you prefer, you can use the Check Book tool discussed above to find and manually correct problems in the HTML. Accessed via Tools → Fix HTML.

Alle bestanden leesbaarder maken

Dit gereedschap wordt gebruikt om alle HTML en CSS-bestanden automatisch te formatteren zo dat ze er “netjes uitzien”. De code springt automatisch in zodat alle regels er netjes bijstaan, lege regels worden toegevoegd waar passend enzovorts. Dit verfraaien reparereert automatisch ook defecte HTML/CSS. daarom kunt u beter eerst het Boek controleren gereedschap gebruiken om alle problemen te corrigeren en pas daarna het verfraaien te doorlopen. Toegankelijk met Gereedschappen → Alle bestanden verfraaien.

Notitie

In HTML any text can have significant whitespace, via the CSS white-space directive. Therefore, beautification could potentially change the rendering of the HTML. To avoid this as far as possible, the beautify algorithm only beautifies block level tags that contain other block level tags. So, for example, text inside a <p> tag will not have its whitespace changed. But a <body> tag that contains only other <p> and <div> tags will be beautified. This can sometimes mean that a particular file will not be affected by beautify as it has no suitable block level tags. In such cases you can try different beautification tools, that are less careful, for example: HTML Tidy [https://infohound.net/tidy/].

Een inline Inhoudsopgave invoegen

Normally in e-books, the Table of Contents is separate from the main text and is typically accessed via a special Table of Contents button/menu in the e-book reading device. You can also have calibre automatically generate an inline Table of Contents that becomes part of the text of the book. It is generated based on the currently defined Table of Contents.

If you use this tool multiple times, each invocation will cause the previously created inline Table of Contents to be replaced. The tool can be accessed via Tools → Table of Contents → Insert inline Table of Contents.

Semantiek instellen

This tool is used to set semantics in EPUB files. Semantics are simply, links in the OPF file that identify certain locations in the book as having special meaning. You can use them to identify the foreword, dedication, cover, table of contents, etc. Simply choose the type of semantic information you want to specify and then select the location in the book the link should point to. This tool can be accessed via Tools → Set semantics.

Stijl informatie filteren

This tool can be used to easily remove specified CSS style properties from the entire book. You can tell it what properties you want removed, for example, color, background-color, line-height and it will remove them from everywhere they occur — stylesheets, <style> tags and inline style attributes. After removing the style information, a summary of all the changes made is displayed so you can see exactly what was changed. The tool can be accessed via Tools → Filter style information.

Boek ingewanden upgraden

This tool can be used to upgrade the book’s internals, if possible. For instance it will upgrade EPUB 2 books to EPUB 3 books. The tool can be accessed via Upgrade book internals.

Checkpoints

Checkpoints are a way to mark the current state of the book as “special”. You can then go on to do whatever changes you want to the book and if you don’t like the results, return to the checkpointed state. Checkpoints are automatically created every time you run any of the automated tools described in the previous section.

You can create a checkpoint via Edit → Create checkpoint. And go back to a previous checkpoint with Edit → Revert to …

The check pointing functionality is in addition to the normal Undo/redo mechanism when editing individual files. Checkpoints are needed for when changes are spread over multiple files in the book or when you wish to be able to revert a large group of related changes as a whole.

You can see a list of available checkpoints via View → Checkpoints. You can compare the current state of the book to a specified checkpoint using the E-books vergelijken tool – by selecting the checkpoint of interest and clicking the Compare button. The Revert to button restores the book to the selected checkpoint, undoing all changes since that checkpoint was created.

Het Live voorbeeld paneel

The File preview gives you an overview of the various files inside The live preview panel shows you the changes you are making live (with a second or two of delay). As you edit HTML or CSS files, the preview panel is updated automatically to reflect your changes. As you move the cursor around in the editor, the preview panel will track its location, showing you the corresponding location in the book. Clicking in the preview panel, will cause the cursor in the editor to be positioned over the element you clicked. If you click a link pointing to another file in the book, that file will be opened in the edit and the preview panel, automatically.

You can turn off the automatic syncing of position and live preview of changes – by buttons under the preview panel. The live update of the preview panel only happens when you are not actively typing in the editor, so as not to be distracting or slow you down, waiting for the preview to render.

The preview panel shows you how the text will look when viewed. However, the preview panel is not a substitute for actually testing your book an actual reader device. It is both more, and less capable than an actual reader. It will tolerate errors and sloppy markup much better than most reader devices. It will also not show you page margins, page breaks and embedded fonts that use font name aliasing. Use the preview panel while you are working on the book, but once you are done, review it in an actual reader device or software emulator.

Notitie

The preview panel does not support embedded fonts if the name of the font inside the font file does not match the name in the CSS @font-face rule. You can use the Check Book tool to quickly find and fix any such problem fonts.

HTML bestanden splitsen

One, perhaps non-obvious, use of the preview panel is to split long HTML files. While viewing the file you want to split, click the Split mode button under the preview panel . Then simply move your mouse to the place where you want to split the file and click. A thick green line will show you exactly where the split will happen as you move your mouse. Once you have found the location you want, simply click and the split will be performed.

Splitting the file will automatically update all links and references that pointed into the bottom half of the file and will open the newly split file in an editor.

You can also split a single HTML file at multiple locations automatically, by right clicking inside the file in the editor and choosing Split at multiple locations. This will allow you to easily split a large file at all heading tags or all tags having a certain class and so on.

Het realtime CSS paneel

The Live CSS panel shows you all the style rules that apply to the tag you are currently editing. The name of tag, along with its line number in the editor are displayed, followed by a list of matching style rules.

It is a great way to quickly see which style rules apply to any tag. The view also has clickable links (in blue), which take you directly to the location where the style was defined, in case you wish to make any changes to the style rules. Style rules that apply directly to the tag, as well as rules that are inherited from parent tags are shown.

The panel also shows you what the finally calculated styles for the tag are. Properties in the list that are superseded by higher priority rules are shown with a line through them.

You can enable the Live CSS panel via View → Live CSS.

Diverse gereedschappen

Er zijn nog meer gereedschappen bruikbaar voor het bewerken van uw boek

De Inhoudsopgave weergave

The Table of Contents view shows you the current table of contents in the book. Double clicking on any entry opens the place that entry points to in an editor. You can right click to edit the Table of Contents, refresh the view or expand/collapse all items. Access this view via View → Table of Contents.

Spellingscontrole van woorden in het boek

Je kunt de spellingscontrole laten lopen via Tools → Check spelling.

Words are shown with the number of times they occur in the book and the language the word belongs to. Language information is taken from the books metadata and from lang attributes in the HTML files. This allows the spell checker to work well even with books that contain text in multiple languages. For example, in the following HTML extract, the word color will be checked using American English and the word colour using British English:

<div lang="en_US">color <span lang="en_GB">colour</span></div>

Notitie

U kan dubbelklikken op een woord om het volgende voorkomen te accentueren in de editor. Nuttig als u het handmatig wil bewerken of de context bekijken.

To change a word, simply double click one of the suggested alternative spellings on the right, or type in your own corrected spelling and click the Change selected word to button. This will replace all occurrences of the word in the book. You can also right click on a word in the main word list to change the word conveniently from the right click menu.

You can have the spelling checker ignore a word for the current session by clicking the Ignore button. You can also add a word to the user dictionary by clicking the Add to dictionary button. The spelling checker supports multiple user dictionaries, so you can select the dictionary you want the word added to.

You can also have the spelling checker display all the words in your book, not just the incorrectly spelled ones. This is useful to see what words are most common in your book and to run a simple search and replace on individual words.

Notitie

If you make any changes to the book by editing files while the spell check tool is open, you should click the Refresh button in the Spell check tool. If you do not do this and continue to use the Spell check tool, you could lose the changes you have made in the editor.

Notitie

To exclude an individual file from being spell checked when running the spell check tool, you can use the Exclude files button or add the following comment just under the opening tag in the file:

<!-- calibre-no-spell-check -->

Nieuwe woordenboeken toevoegen

The spelling checker comes with builtin dictionaries for the English and Spanish languages. You can install your own dictionaries via Preferences → Editor → Manage spelling dictionaries. The spell checker can use dictionaries from the LibreOffice program (in the .oxt format). You can download these dictionaries from The LibreOffice Extensions repository [https://extensions.libreoffice.org/?Tags%5B%5D=50].

Speciale tekens invoegen

You can insert characters that are difficult to type by using the Edit → Insert special character tool. This shows you all Unicode characters, simply click on the character you want to type. If you hold Ctrl while clicking, the window will close itself after inserting the selected character. This tool can be used to insert special characters into the main text or into any other area of the user interface, such as the Search and replace tool.

Because there are a lot of characters, you can define your own Favorite characters, that will be shown first. Simply right click on a character to mark it as favorite. You can also right click on a character in favorites to remove it from favorites. Finally, you can re-arrange the order of characters in favorites by clicking the Re-arrange favorites button and then drag and dropping the characters in favorites around.

You can also directly type in special characters using the keyboard. To do this, you type the Unicode code for the character (in hexadecimal) and then press the Alt+X key which will convert the previously typed code into the corresponding character. For example, to type ÿ you would type ff and then Alt+X. To type a non-breaking space you would use a0 and then Alt+X, to type the horizontal ellipsis you would use 2026 and Alt+X and so on.

Finally, you can type in special characters by using HTML named entities. For example, typing &nbsp; will be replaced by a non breaking space when you type the semi-colon. The replacement happens only when typing the semi-colon.

De code controle weergave

This view shows you the HTML coding and CSS that applies to the current element of interest. You open it by right clicking a location in the preview panel and choosing Inspect. It allows you to see the HTML coding for that element and more importantly, the CSS styles that apply to it. You can even dynamically edit the styles and see what effect your changes have instantly. Note that editing the styles does not actually make changes to the book contents, it only allows for quick experimentation. The ability to live edit inside the Inspector is under development.

Controle van externe links

You can use this tool to check all links in your book that point to external websites. The tool will try to visit every externally linked website, and if the visit fails, it will report all broken links in a convenient format for you to fix.

Externe bronnen downloaden

You can use this tool to automatically download any images/stylesheets/etc. in the book that are not bundled with the book (i.e. they have URLs pointing to a location on the internet). The tool will find all such resources and automatically download them, add them to the book and replace all references to them to use the downloaded files.

Bestanden sorteren in mappen op type

Often when editing EPUB files that you get from somewhere, you will find that the files inside the EPUB are arranged haphazardly, in different sub-folders. This tool allows you to automatically move all files into sub-folders based on their types. Access it via Tools → Arrange into folders. Note that this tool only changes how the files are arranged inside the EPUB, it does not change how they are displayed in the File browser.

Importeren van bestanden in andere e-book formaten als EPUB

The editor includes the ability to import files in some other e-book formats directly as a new EPUB, without going through a full conversion. This is particularly useful to directly create EPUB files from your own hand-edited HTML files. You can do this via File → Import an HTML or DOCX file as a new book.

De rapporten functie

The editor includes a nice Reports tool (via Tools → Reports) that shows summaries of the files, images, links, words, characters and styles used in the book. Every line in the report is hot-linked. Double clicking a line jumps to the place in the book where that item is used or defined (as appropriate). For example, in the Links view, you can double click entries the Source column to jump to where the link is defined and entries in the Target column to jump to where the link points.

Speciale mogelijkheden in de code editor

De Calibre HTML editor is erg krachtig. Het heeft veel mogelijkheden om het editeren van HTML (en CSS) gemakkelijker te maken

Syntaxis markering

De HTML editor heeft een erg gesofistikeerd syntax accentuatie. Mogelijkheden zijn:

- De tekst binnen vet, schuin en hoofdingen zijn vet/schuin gemaakt

- As you move your cursor through the HTML, the matching HTML tags are highlighted, and you can jump to the opening or closing tag with the keyboard shortcuts Ctrl+{ and Ctrl+}. Similarly, you can select the contents of a tag with Ctrl+Alt+T or Ctrl+Shift+T.

- Foutieve HTML is gemarkeerd met rode onderstreping

- Spelling errors in the text inside HTML tags and attributes such as title are highlighted. The spell checking is language aware, based on the value of the lang attribute of the current tag and the overall book language.

- CSS binnen de <style> labels is geaccentueerd.

- Speciale karakters die moeilijk zijn te onderscheiden van niet-brekende spaties, zoals de verschillendes soorten hyphens, en zo verder zijn geaccentueerd.

- Links naar andere bestanden in <a> tags, <img> en <link> tags hebben allemaal de bestandsnamen geaccentueerd. Als de bestandsnaam waar ze naar wijzen niet bestaat, is de bestandsnaam gemarkeerd met een rode lijn eronder.

Contextgevoelige hulp

Je kan met de rechtermuisknop klikken op een HTML tag of CSS eigenschap om hulp te verkrijgen over die tag of eigenschap.

U kan ook de Ctrl toets ingedrukt houden en klikken op elke bestandsnaam in een link tag om dat bestand te openen in de editor. Ctrl klikken op een class naam brengt u naar de eerste stijlregel die overeenkomt met de tag en de class.

Rechtsklikken op een classnaam in een HTML bestand laat u de class hernoemen, elk voorkomen van de class veranderend doorheen het boek en al z’n stijlbladen.

Auto-aanvullen

Wanneer je een e-book aan het bewerken bent, is de lastigste taak om links te maken naar andere bestanden in het boek, of naar CSS stylesheets, of afbeeldingen. Je moet de goede bestandsnaam en het goede relatieve pad achterhalen. De editior heeft een auto-complete functie om dat makkelijker te maken.

As you type a filename, the editor automatically pops up suggestions. Simply use the Tab key to select the correct file name. The editor even offers suggestions for links pointing to an anchor inside another HTML file. After you type the # character, the editor will show you a list of all anchors in the target file, with a small snippet of text to help you choose the right anchor.

Note that unlike most other completion systems, the editor’s completion system uses subsequence matching. This means that you can type just two or three letters from anywhere in the filename to complete the filename. For example, say you want the filename ../images/arrow1.png, you can simply type ia1 and press Tab to complete the filename. When searching for matches, the completion system prioritizes letters that are at the start of a word, or immediately after a path separator. Once you get used to this system, you will find it saves you a lot of time and effort.

Fragmenten

The calibre E-book editor supports snippets. A snippet is a piece of text that is either re-used often or contains a lot of redundant text. The editor allows you to insert a snippet with only a few key strokes. The snippets are very powerful, with many features, such as placeholders you can jump between, automatic mirroring of repeated text and so on. For more information, see Fragmenten.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- E-boeken bewerken »

- Functie modus voor zoek & vervangen in de editor

Functie modus voor zoek & vervangen in de editor

De :guilabel: Zoek & vervangen tool in de editor ondersteunt een functiemodus. In deze modus kunt u reguliere expressies combineren (zie :doc: regexp) met willekeurig krachtige Python-functies om allerlei geavanceerde tekstverwerking uit te voeren.

In de standaard regexp modus voor zoeken en vervangen, geeft u zowel een reguliere expressie op om naar te zoeken als een sjabloon, om alle gevonden overeenkomsten te vervangen. In functiemodus geeft u in plaats van een vast sjabloon een willekeurige functie op in de Python programmeertaal [https://docs.python.org]. Hiermee kunt u veel dingen doen die niet mogelijk zijn met eenvoudige sjablonen.

Technieken voor het gebruik van de functie modus en de syntaxis worden beschreven aan de hand van voorbeelden, waarin u wordt uitgelegd hoe u functies kunt maken om steeds complexere taken uit te voeren.

Automatisch het hoofdlettergebruik van titels in documenten corrigeren

Hier zullen we gebruik maken van een van de ingebouwde functies in de editor om automatisch het hoofdlettergebruik van alle tekst binnen heading-tags naar elk woord met hoofdletters te veranderen:

Find expression: <([Hh][1-6])[^>]*>.+?</\1>

Kies voor de functie eenvoudigweg de Titel-case tekst (negeer tags) ingebouwde functie. Dit verandert titels die eruit zien als: <h1>een TITEL</h1> naar <h1>Een Titel</h1>. Het werkt zelfs als er andere HTML-tags in de heading-tags voorkomen.

Uw eerste aangepaste functie - koppeltekens slim maken

The real power of function mode comes from being able to create your own functions to process text in arbitrary ways. The Smarten Punctuation tool in the editor leaves individual hyphens alone, so you can use the this function to replace them with em-dashes.

To create a new function, simply click the Create/edit button to create a new function and copy the Python code from below.

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
return match.group().replace('--', '—').replace('-', '—')

Every Search & replace custom function must have a unique name and consist of a Python function named replace, that accepts all the arguments shown above. For the moment, we won’t worry about all the different arguments to replace() function. Just focus on the match argument. It represents a match when running a search and replace. Its full documentation in available here [https://docs.python.org/library/re.html#match-objects]. match.group() simply returns all the matched text and all we do is replace hyphens in that text with em-dashes, first replacing double hyphens and then single hyphens.

Gebruik deze functie met de zoek reguliere expressie:

>[^<>]+<

En het vervangt alle koppeltekens met em-streepjes maar enkel in echte tekst en niet in HTML tag definities.

De kracht van functie modus - een spellingswoordenboek gebruiken om fout koppeltekengebruik te herstellen

E-boeken gecreëerd van scans van gedrukte boeken bevatten dikwijls fout koppeltekengebruik – woorden gesplitst aan het einde van een regel in d gedrukte tekst. We gaan een eenvoudige functie schrijven om zulke woorden automatisch te vinden en herstellen.

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

Gebruik deze functie met dezelfde zoek reguliere expressie als voorheen, namelijk:

>[^<>]+<

And it will magically fix all mis-hyphenated words in the text of the book. The main trick is to use one of the useful extra arguments to the replace function, dictionaries. This refers to the dictionaries the editor itself uses to spell check text in the book. What this function does is look for words separated by a hyphen, remove the hyphen and check if the dictionary recognizes the composite word, if it does, the original words are replaced by the hyphen free composite word.

Note that one limitation of this technique is it will only work for mono-lingual books, because, by default, dictionaries.recognized() uses the main language of the book.

Automatische nummering secties

Now we will see something a little different. Suppose your HTML file has many sections, each with a heading in an <h2> tag that looks like <h2>Some text</h2>. You can create a custom function that will automatically number these headings with consecutive section numbers, so that they look like <h2>1. Some 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'

Gebruik het met de find expresse:

(?s)(<h2[^<>]*>)(.+?</h2>)

Plaats de cursor aan het begin van het bestand en klik op Alle vervangen.

This function uses another of the useful extra arguments to replace(): the number argument. When doing a Replace All number is automatically incremented for every successive match.

Een andere nieuwe functie is het gebruik van de replace.file_order – instelling die voor 'spine' betekent dat als deze zoekopdracht draait op meerdere HTML files, de bestanden verwerkt worden in de volgorde waarin ze verschijnen in het book. Bekijk Kies bestandsvolgorde bij uitvoeren meerdere HTML bestanden voor details.

Maak automatische inhoudsopgave aan

Finally, lets try something a little more ambitious. Suppose your book has headings in h1 and h2 tags that look like <h1 id="someid">Some Text</h1>. We will auto-generate an HTML Table of Contents based on these headings. Create the custom function below:

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'

En gebruik het met de find expressie:

<(h[12]) [^<>]* id=['"]([^'"]+)['"][^<>]*>([^<>]+)

Run the search on All text files and at the end of the search, a window will popup with “Debug output from your function” which will have the HTML Table of Contents, ready to be pasted into toc.html.

The function above is heavily commented, so it should be easy to follow. The key new feature is the use of another useful extra argument to the replace() function, the data object. The data object is a Python dictionary that persists between all successive invocations of replace() during a single Replace All operation.

Another new feature is the use of call_after_last_match – setting that to True on the replace() function means that the editor will call replace() one extra time after all matches have been found. For this extra call, the match object will be None.

This was just a demonstration to show you the power of function mode, if you really needed to generate a Table of Contents from headings in your book, you would be better off using the dedicated Table of Contents tool in Tools → Table of Contents.

De API voor de functie modus

All function mode functions must be Python functions named replace, with the following signature:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
return a_string

When a find/replace is run, for every match that is found, the replace() function will be called, it must return the replacement string for that match. If no replacements are to be done, it should return match.group() which is the original string. The various arguments to the replace() function are documented below.

The match argument

The match argument represents the currently found match. It is a Python Match object [https://docs.python.org/library/re.html#match-objects]. Its most useful method is group() which can be used to get the matched text corresponding to individual capture groups in the search regular expression.

The number argument

The number argument is the number of the current match. When you run Replace All, every successive match will cause replace() to be called with an increasing number. The first match has number 1.

Het file_name argument

This is the filename of the file in which the current match was found. When searching inside marked text, the file_name is empty. The file_name is in canonical form, a path relative to the root of the book, using / as the path separator.

Het metadata argument

This represents the metadata of the current book, such as title, authors, language, etc. It is an object of class calibre.ebooks.metadata.book.base.Metadata. Useful attributes include, title, authors (a list of authors) and language (the language code).

Het dictionaries argument

This represents the collection of dictionaries used for spell checking the current book. Its most useful method is dictionaries.recognized(word) which will return True if the passed in word is recognized by the dictionary for the current book’s language.

Het data argument

This a simple Python dictionary. When you run Replace all, every successive match will cause replace() to be called with the same dictionary as data. You can thus use it to store arbitrary data between invocations of replace() during a Replace all operation.

Het functions argument

The functions argument gives you access to all other user defined functions. This is useful for code re-use. You can define utility functions in one place and re-use them in all your other functions. For example, suppose you create a function name My Function like this:

def utility():
# do something

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...

Dan, in een andere functie, hebt u toegang tot de utility() functie, zo:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
utility = functions['My Function']['utility']
...

You can also use the functions object to store persistent data, that can be re-used by other functions. For example, you could have one function that when run with Replace All collects some data and another function that uses it when it is run afterwards. Consider the following two functions:

# 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']
...

Foutzoeken in uw functies

You can debug the functions you create by using the standard print() function from Python. The output of print will be displayed in a popup window after the Find/replace has completed. You saw an example of using print() to output an entire table of contents above.

Kies bestandsvolgorde bij uitvoeren meerdere HTML bestanden

When you run a Replace all on multiple HTML files, the order in which the files are processes depends on what files you have open for editing. You can force the search to process files in the order in which the appear by setting the file_order attribute on your function, like this:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...

replace.file_order = 'spine'

file_order accepts two values, spine and spine-reverse which cause the search to process multiple files in the order they appear in the book, either forwards or backwards, respectively.

Having your function called an extra time after the last match is found

Sometimes, as in the auto generate table of contents example above, it is useful to have your function called an extra time after the last match is found. You can do this by setting the call_after_last_match attribute on your function, like this:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...

replace.call_after_last_match = True

Appending the output from the function to marked text

When running search and replace on marked text, it is sometimes useful to append so text to the end of the marked text. You can do that by setting the append_final_output_to_marked attribute on your function (note that you also need to set call_after_last_match), like this:

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

Suppressing the result dialog when performing searches on marked text

You can also suppress the result dialog (which can slow down the repeated application of a search/replace on many blocks of text) by setting the suppress_result_dialog attribute on your function, like this:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...

replace.suppress_result_dialog = True

Meer voorbeelden

More useful examples, contributed by calibre users, can be found in the calibre E-book editor forum [https://www.mobileread.com/forums/showthread.php?t=237181].

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- E-boeken bewerken »
- Fragmenten

Fragmenten

De calibre e-boek editor ondersteunt fragmenten. Een fragment is een stukje tekst dat ofwel dikwijls herbruikt wordt ofwel veel redundante tekst bevat. De editor laart u toe een fragment in te voegen met een paar toetsaanslagen. Bv veronderstel dat u dikwijls tags invoegt bij het bewerken van HTML bestanden, dan kan u simpelweg <a intikken en op Control+J drukken. De editor zal het uitbreiden tot:

<a href="filename"></a>

Niet alleen dat, het woord bestandsnaam zal worden geselecteerd, met de cursor eroverheen, zodat u eenvoudig de echte bestandsnaam kunt typen, met handige editorfunctie :ref: editor_auto_complete. En als u klaar bent met het typen van de bestandsnaam, druk opnieuw op Control+J en de cursor springt naar de positie tussen de <a> tags, zodat u eenvoudig de tekst voor de link kunt typen.

Het fragmentensysteem in de editor is zeer geavanceerd, er zijn een paar ingebouwde fragmenten en u kunt uw eigen fragmenten maken, aangepast aan uw bewerkingsstijl.

De volgende bespreking van de ingebouwde fragmenten zou de kracht van het fragmentensysteem moeten illustreren.

Notitie

U kunt ook snippets gebruiken in de invoervelden in het :guilabel: Zoeken & vervangen paneel, echter, plaatshouders (met Control+J om rond te springen) zullen niet werken.

De ingebouwde fragmenten

De ingebouwde fragmenten worden onder beschreven. Merk op dat u ze kunt negeren door uw eigen fragmenten te maken met dezelfde triggertekst.

Opvultekst [Lorem] invoegen

Het eerste ingebouwde fragment, en het eenvoudigste, is om vultekst in een document in te voegen. De vultekst is afkomstig uit De finibus bonorum et malorum [https://en.wikipedia.org/wiki/De_finibus_bonorum_et_malorum] een filosofisch werk van Cicero (vertaald naar Engels). Om het te gebruiken, typ gewoon `` Lorem ‘’ in een HTML-bestand en druk op Control+J. Het wordt vervangen door een paar alinea’s vultekst.

De definitie van dit fragment is heel eenvoudig, de triggertekst is gedefinieerd als ``Lorem’’ en de sjabloon is gewoon gedefinieerd als de letterlijke tekst om in te voegen. U kunt het gemakkelijk aanpassen om uw favoriete vultekst te gebruiken.

Een zelfsluitende HTML tag [<>] invoegen

Laten we nu een gemakkelijke voorbeeld van het krachtige concept plaatshouders bekijken. Stel dat u de zelfsluitende tag <hr/> wil invoegen. Tik gewoon <> en druk op Control+J, de editor breid het fragment uit naar:

<|/>

Het | symbool stelt hier de huidige cursorpositie voor. U kan dan hr tikken en op Control+J drukken om de cursor achter het einde van de tag te plaatsen. Dit fragment is gedefinieerd als:

Trigger: <>
Template: <$1/>$2

Plaatshouders zijn gewoon het dollar ($) teken, gevolgd door een nummer. Als het fragment wordt uitgebreid door Control+J te drukken, komt de cursor bij de eerste plaatshouder (de plaatshouder met het laagste nummer). Als u nogmaals Control+J drukt, springt de cursor naar de volgende (de plaatshouder met het eerstvolgende nummer).

Een HTML link tag [<a] invoegen

HTML link tags delen een gemene structuur. Ze hebben een href attribuut en wat tekst tussen de open- en sluittag. Een fragment om het intikken efficiënter te maken leidt ons tot meer functies van plaatshouders. Om dit fragment te gebruiken, tik gewoon <a en druk op Control+J. De editor breidt dit uit tot:

<a href="filename|"></a>

Niet alleen dat, het woord bestandsnaam zal worden geselecteerd, met de cursor eroverheen, zodat u eenvoudig de echte bestandsnaam kunt typen, met de handige editorfunctie :ref: editor_auto_complete. En als u klaar bent met het typen van de bestandsnaam, druk opnieuw op Control+J en de cursor springt naar de positie tussen de <a> tags, zodat u eenvoudig de tekst voor de link kunt typen. Als u klaar bent met de tekst, druk weer op Control+J om voorbij de sluittag te springen. Dit fragment heet:

Trigger: <a
Template: <a href="${1:filename}">${2*}</a>$3

Er zijn hier enkele nieuwe functies. Eerst, de $1 plaatshouder werd complexer. Er is nu wat standaard tekst inbegrepen (het woord bestandsnaam). Als een plaatshouder standaard tekst bevat, vervangt de plaatshouder de standaard tekst als het fragment wordt uitgebreid. En als u naar een plaatshouder met standaard tekst springt met Control+J wordt de tekst geselecteerd. Zo kan u standaard tekst gebruiken als geheugensteuntje om belangrijke stukken van de sjabloon in te vullen. U kan standaard tekst specificeren voor een plaatshouder met de syntax: ${<number>:default text}.

De andere nieuwe functie is dat de tweede plaatshouder achter een asterisk heeft (${2*}). Dit wil zeggen dat de plaatshouder elke tekst die was geselecteerd voor het uitbreiden van de sjabloon vervangt. Om dit in actie te zien, selecteer wat tekst in de editor, druk Control+J tik <a en druk terug op Control+J, de sjabloon wordt uitgebreid tot:

<a href="filename">whatever text you selected</a>

Een HTML afbeelding tag [<i] invoegen

Dit lijkt veel op het HTML link invoegen, zoals boven gezien. Het laat u toe snel een <img src="filename" alt="description" /> tag in te voegen en te springen tussen de src en alt attributen:

Trigger: <i
Template: <img src="${1:filename}" alt="${2*:description}" />$3

Een willekeurige HTML tag [<<] invoegen

Dit laat u toe een willekeurige complete HTML tag in te voegen (of eerder ingevoerde tekst erin te zetten). Om te gebruiken, tik << en druk Control+J. De editor breidt het uit tot:

<|></>

Tik de tag naam, bijvoorbeeld: span en druk op Control+J, dit resulteert in:

<span>|</span>

Merk op dat de sluittag automatisch met span gevuld werd. Dit wordt bereikt met weer een andere functie van plaatshouders, spiegelen. Spiegelen betekent gewoon dat als u een plaatshouder meer dan eens specificeert in een sjabloon, de tweede en alle verdere posities automatisch gevuld worden met wat u in de eerste intikte als u op Control+J drukt. De definitie voor dit fragment is

Trigger: <<
Template: <$1>${2*}</$1>$3

Zoals u ziet werd de eerste plaatshouder ($1) twee keer gespecificeerd, de tweede keer in de sluittag, wat gewoon kopieert eerder wat u in de openingstag intikt.

Een willekeurige HTML tag invoegen met een class attribuut [<c]

Lijkt sterk op het willekeurige tag invoegen voorbeeld boven, veronderstelt enkel dat u een class voor de tag wil specificeren:

Trigger: <c
Template: <$1 class="${2:classname}">${3*}</$1>$4

Dit laat u toe eerst de tagnaam in te tikken, druk Control+J, tik de class naam, druk Control+J, tik de inhoud van de tag en druk een laatste keer Control+J om uit de tag te springen. De sluittag wordt automatisch ingevuld.

Uw eigen fragmenten maken

Fragmenten zijn echt tof omdat u ze voor uzelf kan maken volgens uw bewerkstijl. Om eigen fragmenten te maken, ga naar Bewerken → Voorkeuren → Editor instellingen → Beheer fragmenten in de editor. U krijgt een gemakkelijk te gebruiken pop-up dialoog om uw eigen fragmenten te creëren. Klik gewoon op de Add snippet en u ziet deze dialoog:

Geef uw fragment eerst een naam, iets beschrijvend om het later te helpen identificeren. Specificeer dan de trigger. De trigger is gewoon de tekst die u in de editor in tikt vooraleer Control+J te drukken om het fragment uit te breiden.

Specificeer dan de fragment sjabloon. Begin best met een voorbeeld boven en pas het aan uw noden aan. Als laatste, specificeer voor welke bestandstypes het fragment moet dienen. Zo kan u verschillende fragmenten hebben met dezelfde trigger die anders werken in verschillende bestandstypes.

De volgende stap is uw nieuw fragment testen. Gebruik de Test ruimte onderaan. Tik de triggertekst in en druk Control+J om het fragment uit te breiden en tussen plaatshouders te springen.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- De calibre Content server

De calibre Content server

De calibre: guilabel: inhoudsserver geeft u toegang tot uw calibre bibliotheken en leest boeken direct in een browser op uw favoriete mobiele telefoon of tablet. Als gevolg hiervan hoeft u geen speciale apps voor het lezen/beheren van boeken op uw telefoon te installeren. Gebruik gewoon de browser. De server downloadt en bewaart het boek dat u leest in een off-line buffer, zodat u het kunt lezen, zelfs als er geen internetverbinding is.

Inhoud

- Toegang krijgen tot de Content server vanaf andere toestellen

- Toegang krijgen tot de server vanaf apparaten in uw thuisnetwerk

- Toegang krijgen tot de server van overal op het internet

- De server interface

- De boekenlijst

- De boekreader

- Browserondersteuning

- Offline support inschakelen

- Gebruikersaccounts enkel beheren vanop de commandoregel

- Integratie van de Calibre Content-server in andere servers

- Een volledig virtuele host gebruiken

- Met een URL prefix

- Een dienst creëren voor de calibre server op een modern Linux systeem

Om de server te starten, klikt u op de: guilabel: verbinden/delen ` knop en kiest: guilabel: Start inhoudsserver`. Mogelijk ontvangt u een bericht van de firewall of het antivirusprogramma van uw computer met de vraag of het OK is om toegang te verlenen tot `` calibre.exe``. Klik op de knop `` Toestaan`` of `` OK``. Open vervolgens een browser (bij voorkeur Chrome of Firefox) op uw computer en typ het volgende adres:

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.

Notitie

Het adres dat boven `` http: //127.0.0.1: 8080`` wordt gebruikt, werkt alleen op de computer die calibre draait. Om toegang te krijgen tot de server vanaf andere computers/telefoons/tablets/enz. u zult wat meer werk moeten doen, zoals beschreven in de volgende sectie.

Toegang krijgen tot de Content server vanaf andere toestellen

Er zijn twee typen van externe apparaat toegang die u normaal gesproken nodig heeft. De eerste, eenvoudigere soort is vanuit uw thuisnetwerk. Als u calibre draait op een computer in uw thuisnetwerken en ook uw andere apparaten op hetzelfde thuisnetwerk hebt aangesloten, moet u gemakkelijk toegang hebben tot de server op die apparaten.

Toegang krijgen tot de server vanaf apparaten in uw thuisnetwerk

Na starten van de server in calibre zoals boven beschreven, klik terug op de Verbinden/delen knop. In plaats van de Start Content server zou u een Stop Content server actie moeten zien. Rechts van deze actie staat een IP adres en een poort nummer. Deze lijken op een hoop getallen gescheiden door punten. Bijvoorbeeld:

Stop Content server [192.168.1.5, port 8080]

Deze getallen zeggen u welk adres te gebruiken om te verbinden met de server in uw toestel. Volgens het voorbeeld boven wordt het adres:

http://192.168.1.5:8080

Het eerste deel van het adres is altijd http:// het volgende deel is het IP adres, de nummers voor de komma, en tenslotte het poortnummer dat wordt toegevoegd aan het IP adres met een dubbel punt (:). Als u geluk hebt, is dit alles dat u nodig hebt en kijkt u nu naar de calibre bibliotheken op uw toestel. Indien niet, lees verder.

Problemen oplossen voor de thuisnetwerkverbinding

Als u niet met de server kan verbinden vanop uw toestel, probeer volgende stappen:

1. Controleer of de server draait door naar http://127.0.0.1:8080 te gaan in een browser op dezelfde computer als de server.

2. Controleer of uw firewall en anti-virus verbindingen toelaten naar uw computer op poort 8080 en het calibre programma. De gemakkelijkste manier om firewall en anti-virus als bron van problemen te elimineren is ze tijdelijk uit te schakelen en dan proberen te verbinden. U moet eerst de internet verbinding verbreken vóór de firewall uit te schakelen om uw computer niet onbeschermd te laten.

3. Controleer of uw computer en toestel op hetzelfde netwerk zitten. Dit betekent dat ze beiden met dezelfde draadloze router verbonden zijn. Vooral, ze mogen geen mobiele telefoon of ISP geleverde WiFi verbinding gebruiken.

4. Als u een niet-standaard netwerkopstelling hebt, kan het zijn dat het IP adres, getoond in het Verbinden/delen menu fout is. In zulk geval moet u zelf uitzoeken wat het correcte te gebruiken IP adres is. Spijtig genoeg is het onmogelijk u daarvoor een stappenplan te geven gezien het oneindige aantal mogelijke configuraties.

5. Als u een gebruikersnaam en wachtwoord hebt ingesteld, probeer eerst zonder om te zien of dat problemen oplevert. Sommige e-inkt toestellen hebben browsers die geen authenticatie kunnen afhandelen. U kan dit soms omzeilen door gebruikersnaam en wachtwoord in te sluiten in de URL, bv.: http://gebruikersnaam:wachtwoord@192.168.1.2:8080.

6. Als u vast zit kan u altijd hulp vragen in de calibre gebruikersforums [https://www.mobileread.com/forums/forumdisplay.php?f=166].

Toegang krijgen tot de server van overal op het internet

Waarschuwing

Alvorens dit te doen, moet u gebruikersnaam/wachtwoord bescherming op de server aanzetten anders heeft iedereen ter wereld toegang tot uw boeken. Ga naar Voorkeuren → Delen → Delen via (inter)net en schakel de optie Gebruikersnaam en wachtwoord vereisen voor toegang tot de content server in.

Terwijl de specifieke details om internet toegang in te stellen variëren afhankelijk van de netwerkconfiguratie en het type computer u gebruikt, is het basisschema als volgt.

1. Vind het externe IP adres van de computer waar u de server zal op draaien. Dit kan door de site What is my IP address [https://www.whatismyip.com/] te bezoeken met een browser op de computer.

2. Als de computer achter een router zit, schakel port forwarding in om poort 8080 (of eender welke poort die u kiest om de calibre Content server op te draaien) te forwarden.

3. Zorg ervoor dat de calibre server wordt doorgelaten door alle firewall/anti-virus programma’s op uw computer.

4. Nu zou u toegang moeten hebben tot de server via elk met internet verbonden toestel met het IP adres dat u vond in de eerste stap. Bv. als het gevonden IP adres 123.123.123.123 was en de poort die u gebruikt voor de calibre server 8080, het adres om te gebruiken op toestel wordt: http://123.123.123.123:8080.

5. Als optie kan u een dienst gebruiken als no-ip [https://www.noip.com/free] om een gemakkelijk te herinneren adres in te stellen i.p.v. het adres dat u vond in de eerste stap.

Notitie

Voor maximale veiligheid moet u ook HTTPS inschakelen op de Content server. U kan dit doen ofwel direct op de server door het pad te voorzien naar het te gebruiken HTTPS certificaat in de geavanceerde configuratie opties voor de server ofwel kan u een omgekeerde proxy instellen zoals onder beschreven, om een bestaande HTTPS setup te gebruiken.

De server interface

De server interface is een vereenvoudigde versie van de calibre hoofdinterface, geoptimaliseerd voor gebruik met aanraakschermen. Het startscherm toont u boeken die u aan het lezen bent en geeft de mogelijkheid een calibre bibliotheek te kiezen om te doorbladeren. De server in calibre geeft u toegang tot al uw bibliotheken, niet maar een enkele zoals vroeger.

De boekenlijst

De server boekenlijst is een eenvoudig raster met boeken. Tik op een omslag om de detail metadata voor een boek te zien. Als u een meer gedetailleerde lijst verkiest, kan u het standaard beeld veranderen door op de drie verticale punten rechtsboven te klikken.

Sorteren en doorzoeken van de boekenlijst zou bekend moeten zijn voor calibre gebruikers. Starten kan door klikken op hun iconen rechtsboven. Ze werken beiden exact hetzelfde als in het calibre hoofdprogramma. De zoekpagina laat u zelfs zoekqueries maken te klikken op auteurs/labels.enz., net zoals in de Labelbrowser in het hoofdprogramma.

Een geliefde functie in het hoofdprogramma, Virtuele bibliotheken is ook aanwezig in de server interface. Klik op de drie verticale punten rechtsboven om een Virtuele bibliotheek te kiezen.

De boekreader

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.

Als u de server laat draaien, kan u zelfs hetzelfde boek op verschillende toestellen openen en het herinnert uw laatste leespositie. Indien niet kan u een synchronisatie afdwingen door tikken in het bovenste kwart en Sync kiezen.

Browserondersteuning

De nieuwe calibre server maakt veel gebruik van geavanceerde HTML 5 en CSS 3 functies. Daarom heeft het een up-to-date browser nodig. Het is getest op Android Chrome en iOS Safari en op Chrome en Firefox op de desktop.

De server is oplettend om functionaliteit te gebruiken die al standaard is of op weg ernaar toe. Als hij dus momenteel niet werkt met uw favoriete browser, zal hij dat waarschijnlijk wel doen zo gauw uw browser bijgebeend is.

Als u een bijzonder oude of beperkte browser gebruikt of u houdt niet van Javascript kan u de mobiele versie gebruiken door simpelweg /mobile aan het server adres toe te voegen.

Notitie

Op iOS laat Apple slechts één browser engine toe, dus Firefox, Chrome en Safari zijn allemaal hetzelfde onder de motorkap. De nieuwe server interface vereist iOS 10.3.2 of nieuwer. Op Android is de server getest met Chrome versie 58 en nieuwer.

Offline support inschakelen

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.

Gebruikersaccounts enkel beheren vanop de commandoregel

Het calibre programma heeft een leuke sectie in Voorkeuren dat u gebruikersaccounts laat beheren voor de server. Echter, als u de server zelfstandig wilt draaien en het calibre hoofdprogramma niet op dezelfde computer/gebruikersaccount kunt draaien, kunt u ook gebruikers beheren via de commandoregel.

U kan gebruikersaccounts beheren met de --manage-users optie van het zelfstandige calibre-server programma. Veronderstel dat u de gebruikersdatabase wilt opslaan in de map /srv/calibre , dan creëert u die door het draaien van:

calibre-server --userdb /srv/calibre/users.sqlite --manage-users

Volg gewoon de prompts om gebruikersaccounts te creëren, hun toelatingen in te stellen, enz. Als u klaar bent, kan u de server draaien als volgt:

calibre-server --userdb /srv/calibre/users.sqlite --enable-auth

Het gebruikt de gebruikersaccounts die u creëerde in de vorige stap.

Integratie van de Calibre Content-server in andere servers

Hier laten we u zien hoe de calibre Content server te integreren in een andere server. Dit gebeurt meestal om gebruik te maken van SSL of om de calibre bibliotheek te gebruiken als deel van een grotere site. De basistechniek is om de calibre server te draaien en een omgekeerde proxy in te stellen van de hoofdserver.

Een omgekeerde proxy betekent dat uw gewone server binnenkomende verzoeken accepteert en die doorgeeft aan de calibre server. Hij leest dan de antwoorden van de calibre server en stuurt die door naar de cliënt. Dit betekent dat u de calibre server gewoon als altijd kunt draaien zonder hem te moeten integreren met uw hoofdserver.

Een volledig virtuele host gebruiken

De eenvoudigste configuratie is om een volledig virtuele host aan de calibre server toe te wijden. In dit geval, draai de calibre server als:

calibre-server

Stel nu uw viruele host in op uw hoofdserver, bijvoorbeeld voor 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;
}
}

Of voor 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>

Met een URL prefix

Als u geen volledige virtuele host wil toewijden aan calibre kan u hem een URL prefix laten gebruiken. Start de calibre server als:

calibre-server --url-prefix /calibre --port 8080

De sleutel parameter hier is --url-prefix /calibre. Dit laat de Content server alle URL’s voorafgegaan door /calibre bedienen. Om dit in actie te zien, bezoek http://localhost:8080/calibre in uw browser. U zou de normale Content server website moeten zien maar hij draait nu onder /calibre.

Met nginx is de vereiste configuratie:

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;
}

Voor Apache, activeer eerst de proxy modules in Apache door het volgende toe te voegen aan httpd.conf:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

De exacte techniek om de proxy modules in te schakelen hangt af van uw Apache installatie. Als u de proxy modules hebt ingeschakeld, voeg de volgende regels toe aan httpd.conf (of als u virtuele hosts gebruikt, aan het conf bestand voor die virtuele host):

AllowEncodedSlashes On
RewriteEngine on
RewriteRule ^/calibre/(.*) http://127.0.0.1:8080/calibre/$1 [proxy]
RedirectMatch permanent ^/calibre$ /calibre/

Dat is alles, u hebt nu toegang tot de calibre Content server via de /calibre URL in uw hoofdserver. De bovenstaande regels geven alle verzoeken onder /calibre door aan de calibre server draaiend op poort 8080 en dankzij de --url-prefix optie boven, handelt de calibre server ze transparant af.

Notitie

Bij gebruik van een reverse proxy moet u de calibre Content server vertellen om enkel naar localhost te luisteren via --listen-on 127.0.0.1. Op deze manier luistert de server enkel voor verbindingen vanop dezelfde computer, d.w.z. van de reverse proxy.

Notitie

Als u SSL hebt ingesteld voor uw hoofdserver, moet u de calibre server zeggen basis authenticatie te gebruiken i.p.v. digest authenticatie, dat is sneller. Geef hiertoe de --auth-mode=basic optie mee aan de calibre-server.

Een dienst creëren voor de calibre server op een modern Linux systeem

U kan gemakkelijk een dienst creëren om calibre te starten bij booten op een modern (systemd [https://www.freedesktop.org/wiki/Software/systemd/]) gebaseerd Linux systeem. Creëer gewoon het bestand /etc/systemd/system/calibre-server.service met de inhoud onder getoond:

[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

Verander mylinuxuser en mylinuxgroup naar eender welke gebruiker en groep die u op de server wil. Dit zou dezelfde gebruiker en groep moeten zijn als de eigenaar van de bestanden in de calibre bibliotheekmap. Merk po dat het gewoonlijk geen goed idee is de server als root te draaien. Verander ook het pad naar de calibre bibliotheekmap volgens uw systeem. U kan meerdere bibliotheken toevoegen indien nodig. Bekijk de help voor het calibre-server commando.

Draai nu:

sudo systemctl start calibre-server

om de server te starten. Controleer z’n status met:

sudo systemctl status calibre-server

Om hem te laten starten bij het booten, draai:

sudo systemctl enable calibre-server

Notitie

De calibre server heeft geen draaiende X server nodig maar wel de X geïnstalleerde bibliotheken omdat sommige componenten die hij nodig heeft eraan linken.

Notitie

De calibre server ondersteunt ook systemd socket activatie, u kan dat dus ook gebruiken indien nodig.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- E-books vergelijken

E-books vergelijken
calibre bevat een geïntegreerde tool voor het vergelijken van e-boeken die kan worden gebruikt om te zien wat er in een e-book is veranderd nadat het is bewerkt of geconverteerd. Het kan boeken vergelijken in de formaten EPUB en AZW3.

Om het te gebruiken, opent u het e-book in de tool voor E-boeken bewerken en klikt u op: guilabel:bestand->vergelijk met ander boek of gebruikt u het: ref: book_details paneel. Als u een conversie uitvoert van EPUB naar EPUB, wordt het oorspronkelijke EPUB-bestand opgeslagen als ORIGINAL_EPUB. Klik met de rechtermuisknop op het ORIGINAL_EPUB-item in het deelvenster Boek details en kies: guilabel:Vergelijk met EPUB-indeling.

De vergelijkende tool die wordt geopend, ziet eruit als de onderstaande schermafdruk. Het toont u de verschillen in tekst, stijlen en afbeeldingen in de gekozen boeken.

De vergelijking weergave begrijpen

Zoals te zien is in de bovenstaande schermafdruk, toont de vergelijkingsweergave de verschillen tussen de twee boeken naast elkaar. Alleen de verschillen, met een paar regels context om hen heen, worden getoond. Hierdoor is het eenvoudig om in één oogopslag te zien wat er is veranderd in een groot document zoals een boek.

Toegevoegde tekst met een groene, verwijderde tekst met rode en de gewijzigde tekst met blauwe achtergrond weergeven.

De regelnummers van alle gewijzigde tekst worden aan de zijkant weergegeven, zodat u gemakkelijk naar een bepaalde wijziging in de editor kunt gaan. Wanneer u het vergelijkingshulpmiddel vanuit de editor opent, kunt u ook dubbelklikken op een regel in het rechterpaneel om automatisch naar die regel in de editor te gaan.

Een handige techniek bij het vergelijken van boeken is om de vergelijkingsfunctie te vertellen om de tekst- en stijlbestanden te verfraaien voordat de verschillen worden berekend. Dit kan vaak resulteren in schonere en gemakkelijker te volgen verschillen. Hiertoe klikt u op de knop: guilabel: Opties’ rechtsonder en kiest u: guilabel: ‘Verfraai bestanden voordat u ze vergelijkt’. Merk op dat verfraaien soms ongewenste effecten kan hebben, omdat het ongeldige markeringen kan veranderen om het geldig te maken. Je kunt ook het aantal regels van de context rond verschillen veranderen via de: guilabel: `Opties knop.

U kunt zoeken naar tekst in de verschillen via de zoekbalk onderaan. U moet aangeven naar welk paneel u wilt zoeken, de: guilabel: Links of de: guilabel:` Rechts`.

Lancering van de vergelijkingstool

Het vergelijkingshulpmiddel is vooral handig wanneer u twee versies van hetzelfde boek hebt en wilt zien wat er anders is. Daartoe zijn er verschillende manieren om de tool te starten.

Twee e-book bestanden vergelijken

Open het eerste bestand in de: ref: edit tool. Klik nu op: guilabel: Bestand->Vergelijk met een ander boek en kies het tweede bestand (het moet in hetzelfde formaat zijn als het eerste). De vergelijkingsweergave wordt geopend met het bestand dat aan de rechterkant wordt bewerkt en het tweede bestand aan de linkerkant.

Vergelijk de ORIGINAL_FMT met FMT

Wanneer u een conversie in Calibre van een FMT naar zichzelf uitvoert, wordt het oorspronkelijke bestand opgeslagen als ORIGINAL_FMT. U kunt zien wat er is gewijzigd door de conversie door met de rechtermuisknop te klikken op het item ORIGINAL_FMT in het venster: ref: book_details in het hoofdvenster en te selecteren: guilabel:` Vergelijk met FMT`. De vergelijkingsweergave wordt geopend met ORIGINAL_FMT aan de linkerkant en FMT aan de rechterkant.

Vergelijk een checkpoint met de huidige staat van het boek gedurende het bewerken

De: ref: edit tool heeft een zeer nuttige functie, genaamd: ref:` checkpoints`. Hiermee kunt u de huidige status van het boek opslaan als een named * ijkpunt *, waarnaar u kunt terugkeren als u de wijzigingen die u hebt aangebracht sinds het maken van het ijkpunt niet leuk vindt. IJkpunten worden ook automatisch aangemaakt wanneer u verschillende geautomatiseerde acties uitvoert in de editor. U kunt de lijst met ijkpunten bekijken door naar: guilabel: View-> Checkpoints te gaan en vervolgens de: guilabel:` vergelijk`-knop gebruiken om het boek bij het geselecteerde ijkpunt te vergelijken met de huidige staat. De vergelijkingstool toont het controlepunt aan de linkerkant en de huidige status aan de rechterkant.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Metadata van e-boeken aanpassen

Metadata van e-boeken aanpassen

Inhoud

- Metadata bewerken van één boek tegelijk

- Metadata aan het downloaden

- Boekformaten beheren

- Alles over de omslagen

- Metadate van meerdere boeken tegelijkertijd bewerken

- Zoek en vervangen

- Metadata aan het bulk downloaden

- Adding extra data files to a book

E-boeken zijn er in alle soorten en maten en vaker wel dan niet, zijn hun metadata (dingen zoals titel/auteur/serie/uitgever) onvolledig of onjuist. De eenvoudigste manier om metadata in calibre te wijzigen, is door gewoon op een item te dubbelklikken en de juiste vervanging in te voeren. Voor meer geavanceerde, “power editing”, gebruik de metadata bewerken tools onder besproken.

Metadata bewerken van één boek tegelijk

Klik op het boek dat u wilt bewerken en vervolgens op de :guilabel: Metadata bewerken’ knop of druk op de``E` toets. Een dialoog opent waarin u alle aspecten van de metadata kunt bewerken. Het heeft verschillende functies om bewerken sneller en efficiënter te maken. Een lijst met veel gebruikte tips:

- U kan op de knop tussen titel en auteurs klikken om ze automatisch om te wisselen.

- U kunt op de knop naast auteurs sorteren klikken om calibre automatisch in te laten vullen met de sorteerwaarden bij elke auteur opgeslagen. Gebruik de :guilabel: `Auteurs beheren’ dialoog om de sorteerwaarden van de auteurs te bekijken/wijzigen. Deze dialoog wordt geopend door klikken op/ingedrukt houden van de knop naast auteurssortering.

- U kan op de knop naast tags klikken om de Tag editor te gebruiken om de tags van een boek te beheren.

- Gebruik het ISBN veld om een ISBN in te voeren (en veel andere ID types), het krijgt een rode achtergrond als u een ongeldig nummer invoert. Het kleurt groen bij geldige ISBNs.

- Het auteursorteerveld zal rood zijn als de auteursorteerwaarde verschilt van wat calibre denkt dat het moet zijn.

Metadata aan het downloaden

De leukste functie van de bewerk metadata dialoog is zijn talent om automatisch vele metadata in te vullen door ze van verschillende webstekken te halen. Momenteel gebruikt calibre Google Books en Amazon. Het kan Title, auteur, series, tags, beoordeling, beschrijving en ISBN voor u invullen.

Om de download te gebruiken, vul de titel en auteur velden in en klik op de Haal metadata knop. calibre geeft u een lijst met boeken die meest overeenkomen met de titel en auteur. Als u het ISBN veld invult, wordt dit bij voorkeur gebruikt over titel en auteur. Geen overeenkomsten? Maak uw zoeken minder specifiek door slechts enkele sleutelwoorden uit de titel te gebruiken en enkel de auteur’s achternaam.

Boekformaten beheren

In calibre kan een enkel boek gekoppeld zijn aan verschillende formaten. Bv. u kwam in bezit van de Verzamelde Werken van Shakespeare in EPUB formaat en converteerde dit naar MOBI om op uw Kindle te lezen. calibre beheert automatisch verschillende formaten voor u. In de Beschikbare formaten sectie van de Bewerk metadata dialoog kan u deze formaten beheren. U kan een nieuw formaat toevoegen, een bestaand formaat verwijderen en calibre vragen de metadata en omslag van het boek in te stellen uit een van de formaten.

Alles over de omslagen

U kan calibre vragen boekomslagen voor u te downloaden, indien het boek een ISBN heeft. Als alternatief kan u een bestand op uw PC opgeven om als omslag te gebruiken. calibe kan zelfs een standaard omslag met basis metadata erop voor u genereren. U kan afbeeldingen slepen en neerzetten op de omslag om hem te veranderen en ook rechtsklikken om omslagafbeeldingen te knippen/plakken.

Bijkomend is er een knop om automatisch de randen van de omslag bij te snijden als uw afbeelding een lelijke rand heeft.

Metadate van meerdere boeken tegelijkertijd bewerken

Selecteer eerst de boeken die u wil bewerken door Ctrl of Shift in te drukken en erop te klikken. Als u meer dan één boek selecteert, laat klikken op de Metadata bewerken knop de Bulk metadata bewerken dialoog verschijnen. Via deze dialoog kan u snel auteur/uitgever/beoordeling/labels/series enz van een hoop boeken op dezelfde waarde instellen. Dit is bijzonder nuttig als u net een aantal boeken hebt geïmporteerd die metadata gemeen hebben. Deze dialoog is heel krachtig, hij heeft bv. een Zoeken en vervangen tab die u kan gebruiken voor bulkoperaties op metadata en zelfs metadata kopiëren van een kolom naar een andere.

De normale metadata bewerken dialoog heeft ook Volgende en Vorige knoppen die u kan gebruiken om de metadata te bewerken van verschillenede boeken het een na het ander.

Zoek en vervangen

De Metadata bewerken voor veel boeken dialoog laat u willekeurig krachtige zoek- en vervangoperaties uitvoeren op de geselecteerde boeken. Het gebruikt standaard een eenvoudige tekst zoek en vervang maar het ondersteunt ook reguliere expressies. Voor meer over reguliere expressies, bekijk Alles over het gebruik van reguliere expressies in calibre.

Zoals boven vermeld zijn er twee zoek en vervang modi: karakterovereenkomst en reguliere expressies. Karakterovereenkomst kijkt in het Zoekveld dat u koos voor de karakters die u intikte in het zoek naar vak en vervangt deze karakters door wat u intikte in het vervang door vak. Elke voorkomen van de zoekkarakters in het veld wordt vervangen. Bv. veronderstel dat het te doorzoeken veld a bad cat bevat. Als u zoekt voor a om te vervangen door HELLO, zal het resultaat HELLO bHELLOd cHELLOt zijn.

Als het veld dat u doorzoekt een meervoudig veld is zoals labels, dan wordt elk label apart behandeld. Bv. als uw label Horror, Scary bevat, zal de zoekexpressie r, met niets overeenkomen omdat de expressie eerst toegepast wordt op Horror en dan op Scary.

Als u het zoeken hoofd/kleine letters wil laten negeren, ontkruis het Hoofdlettergevoelig vakje.

U kan calibre het hoofdlettergebruik van het resultaat laten veranderen (informatie na het vervangen) door een van de functies uit het Pas functie toe na vervangen te kiezen. De beschikbare operaties zijn:

- Kleine letters – verander alle karakters in het veld in kleine letters

- Hoofdletters – verander alle karakters in het veld in hoofdletters

- Title case – hoofdletter voor elk woord in het resultaat.

Het Uw test vak is voorzien om tekst in te voeren om te controleren of zoek/vervang doet wat u wilt. In de meeste gevallen zijn de boektestvakken voldoende maar het is mogelijk dat er een geval is dat u wil controleren dat niet getoond wordt in deze vakken. Voer dat geval in in Uw test.

Reguliere expressie modus verschilt wat van karaktermodus, buiten (natuurlijk) het gebruik van reguliere expressies. Ten eerste dat functies worden toegepast op de delen van de string overeenkomend met de zoekstring en niet het hele veld. Verder zijn functies van toepassing op de vervangstring, niet op het hele veld.

Het derde en belangrijkste is dat de vervangingsstring kan refereren naar delen van de zoekstring door backreferenties te gebruiken. Een backreferentie is \\n waar n een geheel getal is dat verwijst naar de n’de groep tussen haakjes in de zoekexpressie. Bv. met hetzelfde voorbeeld als boven, a bad cat, een zoekexpressie a (…) (…), en een vervangexpressie a \2 \1, zal het resultaat a cat bad zijn. Bekijk a.u.b. de Alles over het gebruik van reguliere expressies in calibre voor meer informatie over backreferenties.

Een nuttig patroon: veronderstel dat u het hoofdlettergebruik wilt veranderen in een heel veld. De gemakkelijkste manier is de karaktermodus gebruiken maar laten we aannemen dat u reguliere expressie modus wilt gebruiken. De zoekexpressie moet (^.*$) zijn, de vervangexpressie \1, en de gewenste hoofdlettervervangfunctie moet geselecteerd worden.

Tenslotte, in reguliere expressie modus kan u waardes van een veld naar een ander kopiëren. Maak gewoon het bron- en doelveld verschillend. De kopie kan het doelveld vervangen of voor-/achteraan bij het veld toevoegen . Het ‘gebruik komma’ aankruisvakje zegt calibre wel of niet een komma toe te voegen tussen de tekst en het doelveld in voor-/achteraan toevoegen modus. Als het doel meervoudig is, (bv. labels) kan u dit vakje niet ontkruisen.

Zoeken en vervangen wordt gedaan nadat alle andere metadataveranderingen in de andere tabs toegepast zijn. Dit kan tot enige verwarring leiden omdat de testvakken de informatie tonen voor de andere veranderingen maar de operatie wordt uitgevoerd na de andere veranderingen. Als u twijfelt over way gaat gebeuren, meng zoeken/vervangen niet met andere veranderingen.

Metadata aan het bulk downloaden

Als u metadata voor meerdere boeken ineens wilt downloaden, rechtsklik op de Metadata bewerken knop en selecteer Metadata downloaden. U kan kiezen tussen enkel metadata downloaden, enkel omslagen, of beiden.

Adding extra data files to a book
calibre can store any number of extra data files associated to a book. These can be alternate covers, supplementary material, etc. They cannot be viewed directly or used as conversion sources. Nor are they indexed by the Full text search engine in calibre. To view/add/delete them select the book and right click the Edit metadata button and choose Manage data files. This will pop-up a window where you can perform operations on these files. Alternately, you can right click the Add books button and choose Add data files to selected book records to more quickly add data files.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Veel gestelde vragen

Veel gestelde vragen

Inhoud

- E-book formaat conversie

- Toestel integratie

- Library management

- Diversen

E-book formaat conversie

Inhoud

- Welke conversie formaten worden ondersteund door Calibre?

- Wat zijn de beste bronformaten voor omzetting?

- Ik heb een PDF geconverteerd, maar het bestand heeft verschillende soorten problemen?

- Hoe converteer ik mijn bestand met niet-Engelse karakters of smart quotes?

- Hoe zit het met inhoudsopgaven in MOBI bestanden?

- Hoe zet ik een set HTML-bestanden om in een specifieke volgorde?

- De EPUB die ik maakte met Calibre is ongeldig?

- Hoe maak ik gebruik van enkele van de geavanceerde features van de conversietools?

Welke conversie formaten worden ondersteund door Calibre?

Calibre ondersteunt de conversie van vele invoerformaten naar vele uitvoerformaten. Conversie is mogelijk van elk invoerformaat in de volgende lijst, naar elk uitvoerformaat.

Invoerformaten: 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

Uitvoerformaten: AZW3, EPUB, DOCX, FB2, HTMLZ, OEB, LIT, LRF, MOBI, PDB, PMLZ, RB, PDF, RTF, SNB, TCR, TXT, TXTZ, ZIP

Notitie

PRC is een generiek formaat, Calibre ondersteunt PRC-bestanden met TextRead en MOBIBook-headers. PDB is ook een generiek formaat. Calibre ondersteunt eReader, Plucker (alleen invoer), PML en zTxt PDB-bestanden. DJVU-ondersteuning is alleen voor het converteren van DJVU-bestanden die ingesloten tekst bevatten. Deze worden meestal gegenereerd door OCR-software. MOBI-boeken kunnen van twee typen Mobi6 en KF8 zijn. Calibre ondersteunt beide volledig. MOBI-bestanden hebben vaak de bestandsextensies .azw of .azw3. DOCX-bestanden van Microsoft Word 2007 en nieuwer worden ondersteund.

Wat zijn de beste bronformaten voor omzetting?

In volgorde van afnemende voorkeur: LIT, MOBI, AZW, EPUB, AZW3, FB2, FBZ, DOCX, HTML, PRC, ODT, RTF, PDB, TXT, PDF

Ik heb een PDF geconverteerd, maar het bestand heeft verschillende soorten problemen?

PDF is een vreselijk formaat om te converteren. Voor een lijst voor de mogelijke problemen die kunnen optreden bij het converteren van een PDF, zie: PDF-documenten omzetten

Hoe converteer ik mijn bestand met niet-Engelse karakters of smart quotes?

Er zijn twee aspecten aan dit probleem:

1. Knowing the encoding of the source file: calibre tries to guess what character encoding your source files use, but often, this is impossible, so you need to tell it what encoding to use. This can be done in the GUI via the Input character encoding field in the Look & feel → Text section of the conversion dialog. The command-line tools have an ebook-convert-txt-input --input-encoding option.

2. When adding HTML files to calibre, you may need to tell calibre what encoding the files are in. To do this go to Preferences → Advanced → Plugins → File type and customize the HTML to ZIP plugin, telling it what encoding your HTML files are in. Now when you add HTML files to calibre they will be correctly processed. HTML files from different sources often have different encodings, so you may have to change this setting repeatedly. A common encoding for many files from the web is cp1252 and I would suggest you try that first. Note that when converting HTML files, leave the input encoding setting mentioned above blank. This is because the HTML to ZIP plugin automatically converts the HTML files to a standard encoding (UTF-8).

Hoe zit het met inhoudsopgaven in MOBI bestanden?

Het eerste wat je je moet realiseren is, dat de meeste e-books twee inhoudsopgaves hebben. De eerste is de traditionele inhoudsopgave, zoals bekend in gedrukte boeken. Deze inhoudsopgave is deel van de hoofd document ‘flow’ en kan naar wens gestijld worden. Dit is de eigenlijke inhoudsopgave.

Dan is er de metadata Inhoudsopgave. Zo’n Inhoudsopgave is geen deel van de boektekst en is normaal toegankelijk via een speciale knop op een reader. Bv. in de calibre e-boekviewer gebruikt u de Inhoudsopgave Tonen knop om het te zien. Het kan niet gestijld worden door de ontwerper van het boek. Hoe het eruit ziet hangt af van het viewer programma.

In het MOBI formaat is de situatie een beetje verward. Dat is omdat het MOBI formaat als enige onder de gangbare geen ondersteuning heeft voor een metadata Inhoudsopgave. EenMOBI boek simuleert die door een extra inhoud Inhoudsopgave aan het eind van het boek te zetten. Als u klikt op Ga Naar Inhoudsopgave op uw Kindle, is het hier dat uw Kindle u naartoe brengt.

Now it might well seem to you that the MOBI book has two identical ToCs. Remember that one is semantically a content ToC and the other is a metadata ToC, even though both might have exactly the same entries and look the same. One can be accessed directly from the Kindle’s menus, the other cannot.

When converting to MOBI, calibre detects the metadata ToC in the input document and generates an end-of-file ToC in the output MOBI file. You can turn this off by an option in the MOBI Output settings. You can also tell calibre whether to put it at the start or the end of the book via an option in the MOBI Output settings. Remember this ToC is semantically a metadata ToC, in any format other than MOBI it cannot not be part of the text. The fact that it is part of the text in MOBI is an accident caused by the limitations of MOBI. If you want a ToC at a particular location in your document text, create one by hand. So we strongly recommend that you leave the default as it is, i.e. with the metadata ToC at the end of the book. Also note that if you disable the generation of the end-of-file ToC the resulting MOBI file may not function correctly on a Kindle, since the Kindle’s use the metadata ToC for many things, including the Page Flip feature.

If you have a hand edited ToC in the input document, you can use the ToC detection options in calibre to automatically generate the metadata ToC from it. See the conversion section of the User Manual for more details on how to use these options.

Finally, I encourage you to ditch the content ToC and only have a metadata ToC in your e-books. Metadata ToCs will give the people reading your e-books a much superior navigation experience (except on the Kindle, where they are essentially the same as a content ToC).

Notitie

The newer AZW3 format has proper support for a metadata ToC. However, the Kindle firmware tends to malfunction if you disable the generation of the end-of-file inline ToC. So it is recommended that you leave the generated ToC alone. If you create an AZW3 file with a metadata ToC and no end-of-file generated ToC, some features on the Kindle will not work, such as the Page Flip feature.

Hoe zet ik een set HTML-bestanden om in een specifieke volgorde?

Als u een set HTML-bestanden in een specifieke volgorde wilt omzetten, dient u een inhoudsopgavebestand aan te maken, d.w.z. nog een HTML-bestand, dat links naar alle andere bestanden bevat, geplaatst in de gewenste volgorde. Zo’n bestand ziet er als volgt uit:

<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>

Voeg dan dit HTML bestand toe aan de GUI en gebruik de Converteren knop om uw e-boek te creëren. U kan de optie in het Inhoudsopgave onderdeel in de conversie dialoog gebruiken voor beheer van hoe de Inhoudsopgave gegenereerd wordt.

Notitie

By default, when adding HTML files, calibre follows links in the files in depth first order. This means that if file A.html links to B.html and C.html and D.html, but B.html also links to D.html, then the files will be in the order A.html, B.html, D.html, C.html. If instead you want the order to be A.html, B.html, C.html, D.html then you must tell calibre to add your files in breadth first order. Do this by going to Preferences → Advanced → Plugins → File type and customizing the HTML to ZIP plugin.

De EPUB die ik maakte met Calibre is ongeldig?

Calibre garandeert niet dat een door Calibre gemaakte EPUB geldig is. De enige garantie die Calibre geeft is dat het een geldige EPUB uitvoert als u geldige XHTML 1.1 + CSS 2.1 aanlevert. Calibre doet zijn best om ervoor te zorgen dat de door Calibre gemaakte EPUB’s daadwerkelijk op een breed scala aan apparaten functioneren zoals bedoeld. Deze doelstelling is onverenigbaar met het maken van geldige EPUB’s en is voor de overgrote meerderheid van de gebruikers van veel groter belang. Als u een tool nodig heeft die altijd geldige EPUB’s maakt, is Calibre niet geschikt voor u. Dit houdt in dat als u een door Calibre gemaakte EPUB naar een online winkel wilt verzenden die de validiteit van EPUB’s controleert, u zelf dient te controleren of de EPUB geldig is. Calibre doet dit namens u niet, m.a.w. u dient bij Calibre geldige XHTML + CSS als de invoerdocumenten aan te leveren.

Hoe maak ik gebruik van enkele van de geavanceerde features van de conversietools?

You can get help on any individual feature of the converters by mousing over it in the GUI or running ebook-convert dummy.html .epub -h at a terminal. A good place to start is to look at the following demo file that demonstrates some of the advanced features html-demo.zip [https://calibre-ebook.com/downloads/html-demo.zip].

Toestel integratie

Inhoud

- Welke apparaten worden door Calibre ondersteund?

- Hoe kan ik helpen om mijn apparaat te laten ondersteunen door Calibre?

- Mijn apparaat wordt niet herkend door Calibre?

- Mijn apparaat is niet-standaard of ongebruikelijk. Hoe kan ik verbinding ermee maken?

- Hoe gebruik ik Calibre op mijn iPad/iPhone/iPod touch?

- How do I use calibre with my Android phone/tablet or Kindle Fire?

- Heb ik toegang tot mijn calibre boeken via de browser op mijn Kindle of ander leestoestel?

- Ik kan geen e-mails verzenden vanuit 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?

- Ik krijg steeds een foutmelding wanneer ik Calibre gebruik met de 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?

- De omslagen voor mijn MOBI bestanden worden niet meer getoond in Kindle voor PC/Kindle voor Android/iPad enz.

- Ik heb boeken overgezet op mijn Kindle, gebruik makend van Calibre. Waarom worden deze niet getoond?

Welke apparaten worden door Calibre ondersteund?

calibre can directly connect to all the major (and most of the minor) e-book reading devices, smartphones, tablets, etc. In addition, using the Connect to folder function you can use it with any e-book reader that exports itself as a USB disk. Finally, you can connect wirelessly to any device that has a web browser using the calibre Content server.

Hoe kan ik helpen om mijn apparaat te laten ondersteunen door Calibre?

If your device appears as a USB disk to the operating system, adding support for it to calibre is very easy. We just need some information from you:

- Complete lijst van e-boek formaten die uw toestel ondersteunt

- Is er een speciale map op het toestel om alle e-boekbestanden te plaatsen? En detecteert het toestel bestanden geplaatst in submappen?

- We also need information about your device that calibre will collect automatically. First, if your device supports SD cards, insert them. Then connect your device to the computer. In calibre go to Preferences → Miscellaneous and click the “Debug device detection” button. This will create some debug output. Copy it to a file and repeat the process, this time with your device disconnected from your computer.

- Stuur beide uitvoeren naar ons met de andere informatie en we schrijven een stuurprogramma voor uw toestel.

Once you send us the output for a particular operating system, support for the device in that operating system will appear in the next release of calibre. To send us the output, open a bug report and attach the output to it. See how to report bugs [https://calibre-ebook.com/nl/bugs].

Mijn apparaat wordt niet herkend door Calibre?

Volg deze stappen om het probleem te vinden:

- Zorg ervoor dat u slechts één apparaat tegelijk met uw computer heeft verbonden. U kunt niet tegelijkertijd met een ander door Calibre ondersteund apparaat, zoals een iPhone/iPad, enz. verbonden zijn.

- If you are connecting an Apple iDevice (iPad, iPod Touch, iPhone), Apple no longer allows third party software to connect to their devices using a USB cable. Instead use a wireless connection, via the calibre Content server.

- If you are connecting a Kindle Fire or other Android device, read the note under Een USB kabel gebruiken.

- On macOS if you get permission errors when connecting a device to calibre, you can fix that by looking under System Preferences > Security and Privacy > Privacy > Files and Folders.

- Make sure you are running the latest version of calibre (currently 7.9.0). The latest version can always be downloaded from the calibre website [https://calibre-ebook.com/nl/download]. You can tell what version of calibre you are currently running by looking at the bottom line of the main calibre window.

- Zorg ervoor dat uw besturingssysteem het toestel ziet, d.w.z. het toestel hoort zichtbaar te zijn in Windows Verkenner (in Windows) of Finder (in macOS).

- Ga in calibre naar Voorkeuren → Delen → Genegeerde toestellen en controleer of uw toestel niet genegeerd wordt

- Als alle bovenstaande stappen mislukken, ga naar Voorkeuren → Geavanceerd → Diversen en klik op Foutzoeken toesteldetectie met uw toestel aangesloten en post de uitvoer als een ticket op de calibre bug tracker [https://bugs.launchpad.net/calibre].

Mijn apparaat is niet-standaard of ongebruikelijk. Hoe kan ik verbinding ermee maken?

In addition to the Connect to folder function found under the Connect/share button, calibre provides a User defined device plugin that can be used to connect to any USB device that shows up as a disk drive in your operating system. Note: on Windows, the device must have a drive letter for calibre to use it. See the device plugin Preferences -> Plugins -> Device plugins -> User defined and Preferences -> Miscellaneous -> Get information to setup the user defined device for more information. Note that if you are using the user defined plugin for a device normally detected by a builtin calibre plugin, you must disable the builtin plugin first, so that your user defined plugin is used instead.

Hoe gebruik ik Calibre op mijn iPad/iPhone/iPod touch?

Een gemakkelijke manier om uw calibre collectie te doorbladeren vanop uw Apple toestel is via gebruik van De calibre Content server, wat uw verzameling beschikbaar maakt via het net. Voer eerst volgende stappen uit in calibre

- Stel het Voorkeur Uitvoer Formaat in calibre in op EPUB (Het uitvoerformaat kan ingesteld worden in Voorkeuren → Interface → Gedrag)

- Stel het uitvoerprofiel in op iPad (dit werkt ook voor iPhone/iPads) in Voorkeuren → Conversie → Algemene opties → Pagina opmaak

- Converteer de boeken die u wilt lezen op uw iDevice naar EPUB formaat door ze te selecteren en te klikken op de Converteren knop.

- Zet de Content server aan door te klikken op de Verbinden/delen knop en laat calibre draaien. U kan calibre ook zeggen om de Content server automatisch te starten via Voorkeuren → Delen → Delen over (inter)net.

De Content server laat u toe boeken direct in Safari lezen. Bijkomend zijn er veel apps voor uw iDevice die met de calibre Content server kunnen verbinden. Voorbeelden: Marvin, Mapleread en iBooks zelf.

De Content server gebruiken.

Start de Safari browser en tik het IP adres en de poort van de computer die de calibre server draaien in, zoals dit:

http://192.168.1.2:8080/

Vervang 192.168.1.2 door het lokale IP adres van de computer die calibre draait. Bekijk De calibre Content server voor details over het draaien van de server en het correcte IP adres vinden om te gebruiken

U zal een lijst met boeken zien in Safari, tik op een boek en u krijgt de optie ofwel het te downloaden ofwel het te lezen in de browser. Als u kiest voor downloaden, vraagt Safari of u het wilt openen met iBooks.

Many reading apps support browsing the calibre library directly via its OPDS support. In such apps you can go to the online catalog screen and add the IP address of the calibre server to browse and download books from your calibre library within the app.

How do I use calibre with my Android phone/tablet or Kindle Fire?

There are two ways that you can connect your Android device to calibre. Using a USB cable – or wirelessly, over the air. The first step to using an Android device is installing an e-book reading application on it. There are many free and paid e-book reading applications for Android: Some examples (in no particular order): 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].

Een USB kabel gebruiken

Simply plug your device into the computer with a USB cable. calibre should automatically detect the device and then you can transfer books to it by clicking the Send to device button. Note that on macOS and Linux only a single program can connect to an Android device at a time, so make sure the device is not opened in the OS File manager, or the Android File Transfer utility, etc.

Notitie

With newer Android devices, you might have to jump through a few hoops to get the connection working, as Google really does not want you to be independent of its cloud. First, unlock the screen before plugging in the USB cable. When you plugin in the USB cable you will get a popup notification. Make sure it says some thing like “Transferring Media files” or “MTP (Media Transfer mode)”. If it does not, tap the notification, and change the mode to Media Transfer (MTP). You may need to restart calibre at this point in order for your device to be recognized. Finally, you might get a popup on the device every time calibre or the operating system actually tries to connect to it, asking for permission, tap OK.

Notitie

With the Kindle Fire 8 or newer there is an icon that shows up when the USB cable is plugged in, showing that the device is charging. Tap that and switch the device to data transfer mode, and then start calibre, it should then be detected.

Door de lucht
calibre has a builtin web server, the De calibre Content server. It makes your calibre collection available over the net. You can browse it on your device using a simple browser or a dedicated application. First perform the following steps in calibre:

- Set the Preferred Output Format in calibre to EPUB for normal Android devices or MOBI for Kindles (The output format can be set under Preferences → Interface → Behavior)

- Converteer de boeken die u wilt lezen op uw toestel naar EPUB/MOBI formaat door ze te selecteren en te klikken op de Converteren knop.

- Zet de Content server aan in calibre’s voorkeuren en laat calibre draaien.

Op je Android toestel, open de browser en surf naar

http://192.168.1.2:8080/

Vervang 192.168.1.2 door het lokale IP adres van de computer die calibre draait. Bekijk De calibre Content server voor details over het draaien van de server en het correcte IP adres vinden om te gebruiken

U kan nu uw boekencollectie doorbladeren en boeken downloaden van calibre naar uw toestel om te openen met eender welke e-boek leessoftware dat u hebt op uw Android toestel.

Many reading apps support browsing the calibre library directly via its OPDS support. In such apps you can go to the online catalog screen and add the IP address of the calibre server to browse and download books from your calibre library within the app.

Heb ik toegang tot mijn calibre boeken via de browser op mijn Kindle of ander leestoestel?

calibre heeft een Content server die de boeken in calibre exporteert als webpagina. Bekijk De calibre Content server voor 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.

Ik kan geen e-mails verzenden vanuit 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:

- Creëer een gratis GMX account op GMX [https://www.gmx.com].

- Ga naar Voorkeuren → Delen → Boeken delen via e-mail in calibre en klik op de Gebruik GMX knop en vul de gevraagde informatie in.

- 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.

Notitie

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.

Notitie

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.

Notitie

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].

Notitie

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.

Ik krijg steeds een foutmelding wanneer ik Calibre gebruik met de 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.

- Koppel de Kobo direct aan je computer, niet via een USB hub.

- Probeer een andere USB kabel en een andere USB poort op je computer.

- Meld je af op de Kobo en meld je opnieuw aan, dit zorgt ervoor dat de gegevensbank opnieuw opgebouwd wordt en de gegevensbank corruptie fouten verholpen.

- Probeer de laatste firmware te upgraden op je Kobo Touch.

- Probeer je Kobo te resetten (soms lost dit het probleem op voor een tijdje, maar dan duikt dit probleem weer op en moet je weer opnieuw resetten)

- Probeer één of twee boeken te zetten op de Kobo per keer en hou je collectie klein op de 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.

Notitie

If the workaround is not working for you make sure you Kindle firmware is at least version 5.12.5, released in April 2020.

De omslagen voor mijn MOBI bestanden worden niet meer getoond in Kindle voor PC/Kindle voor Android/iPad enz.

Dit wordt veroorzaakt door een bug in de Amazon software. U kan hier rond werken door naar Voorkeuren → Conversie → Uitvoer Opties → MOBI uitvoer te gaan en de optie Delen van boekinhoud inschakelen in te stellen. Als u een vroeger geconverteerd boek weer converteert, moet u deze optie ook inschakelen in de conversie dialoog voor dat individueel boek (omdat per boek conversie instellingen worden opgeslagen en voorrang hebben).

Note that doing this will mean that the generated MOBI will show up under personal documents instead of Books on the Kindle Fire and Amazon whispersync will not work, but the covers will. It’s your choice which functionality is more important to you. I encourage you to contact Amazon and ask them to fix this bug.

The bug in Amazon’s software is that when you put a MOBI file on a Kindle, unless the file is marked as a Personal document, Amazon assumes you bought the book from it and tries to download the cover thumbnail for it from its servers. When the download fails, it refuses to fallback to the cover defined in the MOBI file. This is likely deliberate on Amazon’s part to try to force authors to sell only through them. In other words, the Kindle only displays covers for books marked as Personal Documents or books bought directly from Amazon.

If you send a MOBI file to an e-ink Kindle with calibre using a USB connection, calibre works around this Amazon bug by uploading a cover thumbnail itself. However, that workaround is only possible when using a USB connection and sending with calibre. Note that if you send using email, Amazon will automatically mark the MOBI file as a Personal Document and the cover will work, but the book will show up in Personal Documents.

Ik heb boeken overgezet op mijn Kindle, gebruik makend van Calibre. Waarom worden deze niet getoond?

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

Inhoud

- Waar worden mijn boekbestanden opgeslagen?

- Hoe gaat Calibre om met auteursnamen en sorteren?

- Why doesn’t calibre let me store books in my own folder structure?

- Waarom heeft Calibre geen kolom voor foo?

- Kan ik ook een kolom krijgen die formaten of ISBN toont?

- Hoe kan ik de Calibre gegevens van de ene computer naar de andere overzetten?

- De boekenlijst in Calibre is leeg!

- Waarom krijg ik foutmeldingen met met Calibre bibliotheek op een netwerk map/NAS?

Waar worden mijn boekbestanden opgeslagen?

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.

Hoe gaat Calibre om met auteursnamen en sorteren?

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.

- Herstart calibre. Verander geen metadata vooraleer de volgende stappen te ondernemen.

- 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.

Notitie

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.

- Herstart calibre. Verander geen metadata vooraleer de volgende stappen te ondernemen.

- 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.

Als je nog steeds niet overtuigd bent, vrees ik ervoor dat Calibre niets is voor je. Om duidelijk te zijn, dit zal niet veranderen. Gelieve ons niet te contacteren, in een poging om onze gedachten te veranderen.

Waarom heeft Calibre geen kolom voor 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/nl/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 De calibre sjabloontaal.

Kan ik ook een kolom krijgen die formaten of ISBN toont?

Ja, het is mogelijk. Volg bovenstaande instructies om een maat gemaakte kolom toe te voegen.

Hoe kan ik de Calibre gegevens van de ene computer naar de andere overzetten?

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.

Notitie

De import/export functionaliteit is enkel beschikbaar vanaf Calibre versie 2.47. Als je een oudere versie hebt van Calibre, of als problemen tegenkomt met import/export, kun je de Calibre bibliotheek map manueel overschrijven, zoals beschreven in volgend paragraaf.

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.

De boekenlijst in Calibre is leeg!

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.

Er kunnen twee redenen zijn waarom Calibre een lege lijst toont van boeken:

- 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.

- Je metadata.db bestand was verwijderd/corrupt. In dit geval, kan calibre de metadata.db herstellen van de backups. Rechtermuisklik op het calibre icoon in de calibre toolbar en kies voor Bibliotheek onderhoud -> herstel database. calibre zal automatisch de metadata.db herstellen.

Waarom krijg ik foutmeldingen met met Calibre bibliotheek op een netwerk map/NAS?

Zet niet je Calibre bibliotheek op een netwerk map.

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.

Diversen

Inhoud

- Amazon is stopping email delivery of MOBI files?

- Ik wil dat Calibre nieuws download van mijn favoriete nieuws website.

- Vanwaar de naam Calibre?

- Why does calibre show only some of my fonts on macOS?

- Calibre start niet in Windows?

- Crashed of reageert niet Calibre regelmatig?

- 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?

- I heb het installatie bestand gedownload, waarom werkt het niet?

- Mijn antivirus programma beweert dat Calibre een virus/trojaan paars is?

- Hoe maak ik een backup van Calibre?

- Hoe kan ik gekochte EPUB boeken gebruiken met Calibre? (of wat ik doen met .ascm bestanden)?

- Ik krijg een “Toegang geweigerd” fout?

- Hoe kan ik de metadata van de metagegevens lezen op mijn lezer?

- Hoe kan ik Calibre instellen om mijn HTTP proxy te gebruiken?

- Ik wil nieuwe functionaliteit toevoegen aan Calibre. Wat kan ik doen?

- Waarom heeft Calibre geen automatische updates?

- Hoe zit het met de licentie van Calibre?

- Hoe start ik Calibre van mijn 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.

Notitie

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.

Ik wil dat Calibre nieuws download van mijn favoriete nieuws 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 Uw favoriete nieuws website toevoegen.

Otherwise, you can request a particular news site by posting in the calibre Recipes forum [https://www.mobileread.com/forums/forumdisplay.php?f=228].

Vanwaar de naam Calibre?

Maak een keuze:

- 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

- Mijn vrouw heeft hiervoor gekozen ;-)

Calibre is niet uitgesproken als cal-i-ber niet ca-li-bre. Als je het afvraagt, Calibre is de Britse/gemenebest spelling voor Calibre. Als een Indiaan, dat is de natuurlijke spelling voor mij.

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 start niet in Windows?

Er kunnen meerdere redenen hiervoor zijn:

- 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].

- Als je een foutmelding krijgt dat Calibre een bestand niet kan openen omdat het in gebruik is door een ander programma, probeer het volgende:

- deïnstalleer Calibre

- Herstart uw computer

- Herinstalleer Calibre. Maar start Calibre niet vanaf de installatie wizard.

- Probeer je anti virus programma tijdelijk uit te schakelen. (om veilig te zijn, zorg je ervoor dat je niet verbonden bent met het internet)

- Kijk in de map waar je calibre bibliotheek staat. Verwijder metadata.db.

- Start Calibre

- Vanaf nu zou Calibre normaal moeten opstarten.

- 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].

Crashed of reageert niet Calibre regelmatig?

Er zijn verschillende mogelijkheden dat ik weet van heb, dat dit kan veroorzaken:

- Je hebt recentelijk een externe monitor of TV aan je computer gekoppeld. Als dit het geval is, wanneer calibre een nieuw venster opent, zoals “metadata bewerken” & “boeken omzetten”, wordt het niet getoont op het andere scherm, daarom denk je dat er niets gebeurd. Ontkoppel je tweede monitor en herstart 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.

- Je gebruikt een Wacom USB muis/tablet. Er is een incomptabiliteit tussen de Wacom stuurprogramma’s en de grafische omgeving die Calibre gebruikt. Probeer een muis die niet van Wacom is.

- Op sommige 64 bit versies van Windows zijn er veiligheid software/instellingen die voorkomen dat de 64 bit versie van calibre normaal opstart. Als je de 64 bit versie van calibre gebruikt, probeer dan de 32 bit versie.

- 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.

Om uit te zoeken welk programma dat de schuldige is, elimineer één per één om te kijken dewelke de oorzaak is. Om een programma te stoppen: start Calibre op, controleer of er geen crash is. Als er nog steeds een crash is, probeer dan een ander programma te stoppen en probeer opnieuw.

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

Als alternatief, kun je Calibre uitvoeren als Administrator, maar sommige fuctionaliteit kan mogelijk niet werken zoals copiëren en plakken.

Eindelijk, sommige gebruikers hebben gemeld dat het uitschakelen van UAC het probleem verhelpt.

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.

Je kan de debug output achterhalen waarom Calibre niet kan starten door het Console.app te laten lopen. De debug output zal daar staan. Als de debug output een regel bevat als:

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 Omgevingsvariabelen for how to change environment variables.

I heb het installatie bestand gedownload, waarom werkt het niet?

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.

- Probeer tijdelijk je anti virus programma uit te schakelen. (Microsoft Security Essentials, of Kaspersky of Norton of McAfee of wat dan ook). Dit is wellicht de reden waarom het update proces blijft hangen.

- 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].

- Probeer een schone installatie. Dat betekent, verwijder het bestand: file:C:\Program Files\Calibre2 (of waar je de vorige keer Calibre hebt geïnstalleerd). Daarna, voer de installatie opnieuw uit. Opnieuw installeren verwijderd niet je boeken of instellingen.

- 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/nl/download_portable], which does not need an installer (it is just a ZIP file).

Mijn antivirus programma beweert dat Calibre een virus/trojaan paars is?

The first thing to check is that you are downloading calibre from the official website [https://calibre-ebook.com/nl/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.

Hoe maak ik een backup van 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.

Hoe kan ik gekochte EPUB boeken gebruiken met Calibre? (of wat ik doen met .ascm bestanden)?

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.

Ik krijg een “Toegang geweigerd” fout?

A permissie foutmelding kan gebeuren vanwege diverse redenen, geen enkel heeft te maken met Calibre.

- Je kan permissie fouten krijgen als je een SD kaart gebruikt met schrijfbeveiliging.

- On macOS if you get permission errors when connecting a device to calibre, you can fix that by looking under System Preferences > Security and Privacy > Privacy > Files and Folders.

- 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.

- Als je bestand open is in een ander programma.

- 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.

Hoe kan ik de metadata van de metagegevens lezen op mijn lezer?

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.

Hoe kan ik Calibre instellen om mijn HTTP proxy te gebruiken?

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.

Ik wil nieuwe functionaliteit toevoegen aan Calibre. Wat kan ik doen?

U heeft twee keuzes:

1. Create a patch by hacking on calibre and send it to me for review and inclusion. See Development [https://calibre-ebook.com/nl/get-involved].

2. Open a bug requesting the feature [https://calibre-ebook.com/nl/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 Uw eigen plugins schrijven om de functionaliteit van calibre uit te breiden.

Waarom heeft Calibre geen automatische updates?

Om vele redenen:

- Er is geen noodzaak om iedere week te updaten. Als je tevreden bent hoe Calibre werkt, kun je de automatische waarschuwing afzetten. Controleer of je wenst up te daten, één keer per jaar of zo. Er is een check box om de waarschuwing af te zetten, als je klikt op de waarschuwing zelf.

- 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.

- Als ik een dialoog implementeer dat de updates download en het uitvoert, in plaats van manueel van de website te downloaden, zou de meest hardnekkige Calibre updater, tenminste vijf muisklikken per week besparen. Er zijn belangrijker prioriteiten te doen met de ontwikkeling van Calibre.

- Als je echt, maar dan ook echt haat om iedere week een nieuwe versie van Calibre te downloaden, maar steeds de aller laatste versie wenst te gebruiken, raad ik je aan om de bron code te downloaden, hetgaan dat updates trivaal maakt. Instructies zijn hier beschikbaar: 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].

Hoe zit het met de licentie van Calibre?

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].

Hoe start ik Calibre van mijn USB stick?

A portable version of calibre is available here [https://calibre-ebook.com/nl/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.

Notitie
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.

U kunt de Calibre server draaien met het commando:

/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.

U kunt de gedownload nieuws e-mailen met het commando:

/opt/calibre/calibre-smtp

Ik laat het uitvogelen van de exacte commando’s als een opgave voor de lezer.

Ten slotte kunt u al het gedownloade nieuws aan de Calibre bibliotheek toevoegen met:

/opt/calibre/calibredb add --with-library /path/to/library outfile.epub

Remember to read the Opdrachtpromt section of the calibre User Manual to learn more about these, and other commands.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Lesboeken

Lesboeken

Hier vindt u lessen om te starten met het gebruik van de meer geavanceerde functies van calibre, zoals XPath en sjablonen.

- Uw favoriete nieuws website toevoegen

- Subgroepen beheren van boeken, bijvoorbeeld “genre”

- XPath handleiding

- De calibre sjabloontaal

- Alles over het gebruik van reguliere expressies in calibre

- Uw eigen plugins schrijven om de functionaliteit van calibre uit te breiden

- Wiskunde typesetten in e-boeken
- Creëren van AZW3 • EPUB • MOBI-catalogi
- Virtuele bibliotheken

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Lesboeken »

- Subgroepen beheren van boeken, bijvoorbeeld “genre”

Subgroepen beheren van boeken, bijvoorbeeld “genre”

Sommige mensen willen de boeken in hun bibliotheek indelen in subgroepen vergelijkbaar met submappen. De meest genoemde reden is om genre-hiërachieën te maken, maar er zijn meer. Een gebruiker zocht een manier om tekstboeken in de delen op onderwerp en cursusnummer. Iemand anders wilde geschenken op onderwerp bijhouden en ontvanger. Deze handleiding zal het voorbeeld genre gebruiken in deze uitleg.

Voor u verdergaat, merk op dat we het niet hebben over mappen op de harde schijf. Subgroepen zijn geen bestandsmappen. Boeken worden nergens naartoe gekopieerd. calibres bibliotheek bestandsstructuur wordt niet beïnvloed. In plaats daarvan presenteren we een manier om subgroepen van boeken te organiseren en tonen binnen een calibre bibliotheek.

- Configuratie

- Zoeken

- Restricties

- Handige sjabloonfuncties

Subgroepen zoals bijv. genres moeten voldoen aan alle onderstaande voorwaarden:

- Een subgroep (bijv. een genre) moet boeken bevatten (verwijzen naar boeken), niet boekcategorieën. Dit onderscheidt subgroepen van calibre gebruiker-categorieën.

- Een boek kan in meerdere subgroepen (genres) voorkomen. Dit onderscheidt subgroepen van fysieke bestandsmappen.

- Subgroepen (genres) moeten een hiërarchie vormen; subgroepen kunnen subgroepen bevatten.

Labels geven u de eerste twee. Als u een boek met het genre labelt, kunt u met de Labelbrowser (of zoekactie) boeken met dat genre vinden, dit geeft u de eerste. Veel boeken kunnen dezelfde label(s) hebben, dit geeft u de tweede. Het probleem is dat labels niet aan de derde vereiste voldoen. Ze bieden geen hiërarchie.

Isgtree| Calibres hiërarchie functie levert u de derde, het vermogen om de genres te zien in een ‘boomstructuur’ en de capaciteit om makkelijk te zoeken naar boeken in genre of sub-genre. Bijvoorbeeld: Aangenomen dat uw genre als volgt is gestructureerd:

Genre
. History
.. Japanese
.. Military
.. Roman
. Mysteries
.. English
.. Vampire
. Science Fiction
.. Alternate History
.. Military
.. Space Opera
. Thrillers
.. Crime
.. Horror
etc.

Door de hiërarchiefunctie te gebruiken, kunt u deze genres in de Tag-browser in boom-vorm zien, zoals weergegeven in de schermafbeelding. In dit voorbeeld is het buitenste niveau (Genre) een aangepaste kolom die de genres bevat. Genres met subgenres verschijnen met een kleine driehoek ernaast. Klikken op die driehoek opent het item en worden de subgenres weergegeven, zoals u kunt zien bij History en Science Fiction.

Klikken op een genre kan zoeken naar alle boeken met dat genre of sub-genres. Bijvoorbeeld, klikken op Science Fiction kan resulteren in alle drie sub-genres, Alternatieve Geschiedenis, Militair en Ruimtevaart. Klikken op Alternatieve Geschiedenis geeft boeken in dat genre, Militair en Ruimtevaart buiten beschouwing latend. Uiteraard kan een boek meerdere genres hebben. Als een boek in Ruimtevaart en Militair is ingedeeld, ziet u dit boek als u op een van beide genres klikt. Zoeken wordt hieronder gedetailleerd besproken.

Tevens kunt u op de afbeelding zien dat het genre Militarisme tweemal voorkomt, eenmaal onder History en eenmaal onder Science Fiction. Omdat de genres in een hiërarchie staan, zijn dit twee separate genres. Een boek kan dus voorkomen in het ene, het andere of (twijfelachtig in dit geval) in beide. Bijvoorbeeld kunnen de boeken van Winston Churcill “De Tweede Wereldoorlog” voorkomen in “History.Militarisme”. David Webers Honor Harrington boeken kunnen in “Sciende Fiction.Miltarisme” en ook in “Science Fiction.Ruimtevaart”.

Als een genre eenmaal bestaat, d.w.z. dat minstens één boek het heeft, kunt u dit makkelijk toewijzen aan andere boeken door deze te slepen vanuit de bibliotheek naar het genre dat u wilt toewijzen aan de boeken. U kunt genres ook toewijzen in de metadata-editors; meer informatie onderstaand.

Configuratie

Ondertussen wilt u vermoedelijk weten “Hoe is dit ingericht?” Er zijn drie stappen: 1) een aangepaste kolom aanmaken, 2) vertel calibre dat de nieuwe kolom hiërarchisch behandeld moet worden en 3) genres toevoegen.

U maakt de aangepaste kolom op de gebruikelijke manier, via Voorkeuren -> Voeg uw eigen kolommen toe. In dit voorbeeld wordt “#genre” gebruikt als opzoeknaam en “Genre” als kolomkop. Het kolomtype is “Door komma’s gescheiden tekst, zoals tags, weergegeven in de Tag-browser”.

Nadat u calibre hebt herstart, moet u calibre vertellen dat de kolom als een hiërarchie moet worden behandeld. Ga naar Voorkeuren  →  Look & feel  →  Tagbrowser en voer de opzoeknaam “#genre” in het vak “Categorieën met hiërarchische items” in. Druk op Toepassen en u bent klaar met instellen.

Nu staan er nog geen genres in de kolom. Blijft een laatste stap: Hoe wijzen we een genre toe aan een boek. Een genre bestaat niet in calibre totdat het verschijnt bij tenminste één boek. Om te leren hoe een genre aan een boek toe te wijzen, moeten we eerst wat dieper ingaan over hoe een genre eruit ziet in de metadata van een boek.

Een hiërarchie maakt u door uitdrukkingen of woorden gescheiden door punten in te voeren. Voortgaand in het vorbeeld genre kunnen deze zijn “History.Militarisme”, “Mysteries.Vampire”, “Science Fiction.Ruimtevaart”, etc. Om aldus een nieuw genre te maken, kiest u een boek waarvoor u een genre wilt toewijzen, bewerk de metadata en voer het nieuwe genre in de kolom in die u heeft gemaakt. In ons voorbeeld wilt u misschien het genre “Comics” met een sub-genre “Superheroes” voor een boek toewijzen; hiervoor wilt u ‘metadata bewerken’ voor dat (comic) boek, kies de Aangepaste metadat tab en vul dan “Comics.Superheroes” in zoals onderstaand te zien (negeer de andere aangepaste kolommen):

Nadat u het bovenstaande hebt gedaan, ziet u in de Tag-browser:

Vanaf nu kunt u dit nieuwe genre toewijzen aan een boek (vermoedelijk een comic) door het boek te slepen op het genre of het toevoegen met behulp van metadata bewerken precies zoals hierboven beschreven.

Notitie

Hiërarchische weergave werkt alleen als de Tag browser is ingesteld om items op naam te sorteren. Dit is de standaard en controleren kan door te klikken op de knop Instellen onder aan de Tag browser.

Zoeken

De eenvoudigste manier om naar genres te zoeken, is met behulp van de Tag-browser, door op het genre te klikken dat u wilt zien. Als u op een genre met kinderen klikt, worden boeken met dat genre en alle kindergenres weergegeven. Dit kan echter een vraag oproepen. Het feit dat een genre kinderen heeft, betekent niet dat het geen genre op zichzelf is. Een boek kan bijvoorbeeld het genre “Geschiedenis” hebben, maar niet “Geschiedenis.Militair”. Hoe zoek je naar boeken met alleen “Geschiedenis”?

Het zoekmechanisme van de Tag-browser weet of een item kinderen heeft. Als dit het geval is, klikt u op het item door 5 zoekopdrachten in plaats van de normale drie. De eerste is de normale groene plus, die je alleen boeken met dat genre laat zien (bijvoorbeeld Geschiedenis). De tweede is een dubbele plus (hierboven weergegeven), die je boeken toont met dat genre en alle subgenres (bijv. Geschiedenis en Geschiedenis.Militair). De derde is de normale rode min, die je boeken toont zonder dat exacte genre. De vierde is een dubbele min, die je boeken toont zonder dat genre of sub-genres. De vijfde is terug naar het begin, geen teken, wat betekent dat er geen zoeken is.

Restricties

Als u een genre zoekt en er dan een opgeslagen zoekopdracht voor maakt, kunt u het vak ‘beperken tot’ gebruiken om een virtuele bibliotheek met dit genre boeken te maken. Handig als u andere zoekopdrachten in het genre wilt doen of metadata voor boeken in het genre wilt beheren/bijwerken. Dit voorbeeld volgend, kunt u een Opgeslagen zoekopdracht maken met de naam ‘Geschiedenis.Japans’ door eerst op het genre Japans in de Tag-browser te klikken om een zoekopdracht in het zoekvak te krijgen, Geschiedenis.Japans in het opgeslagen zoekvak in te voeren en vervolgens op de knop ‘zoekopdracht opslaan’ (het groene vak met het witte plusteken aan de rechterkant) te drukken.

Na creatie van de opgeslagen zoekopdracht, kunt u deze als beperking gebruiken.

Handige sjabloonfuncties

Misschien wilt u de genre-informatie in een sjabloon gebruiken, bijvoorbeeld met opslaan op schijf of verzenden naar apparaat. De vraag kan dan zijn: “Hoe krijg ik de naam of namen van het buitenste genre?” Een kaliber-sjabloonfunctie, subitems, is beschikbaar om dit gemakkelijker te maken.

Stel bijvoorbeeld dat u het buitenste genre-niveau wilt toevoegen aan de opslaan-op-schijf-sjabloon om genre-mappen te maken, zoals in “Geschiedenis/De Opstekende Storm - Churchill, Winston”. Om dit te doen, moet u het eerste niveau van de hiërarchie nemen en aan de voorkant toevoegen samen met een schuine streep om aan te geven dat het een map moet maken. De onderstaande sjabloon realiseert dit:

{#genre:subitems(0,1)||/}{title} - {authors}

Zie De sjabloontaal voor meer informatie over sjablonen en de functie subitems().

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Lesboeken »
- XPath handleiding

XPath handleiding

In deze zelfstudie krijgt u een voorzichtige inleiding tot XPath <https://en.wikipedia.org/wiki/XPath> _, een querytaal die kan worden gebruikt om willekeurige delen van `HTML <https://en.wikipedia.org/wiki/HTML>`_documenten in Calibre. XPath is een veel gebruikte standaard, en googelen levert een hoop informatie op. Deze zelfstudie richt zich echter op het gebruik van XPath voor aan e-boeken gerelateerde taken, zoals het vinden van hoofdstukken in een ongestructureerd HTML-document.

Inhoud

- Selecteren op naam van label

- Selecteren op eigenschap

- Selecteren op label inhoud

- Voorbeeld e-boek

- XPath ingebouwde functies

Selecteren op naam van label

De eenvoudigste vorm van selectie is om labels op naam te selecteren. Bijvoorbeeld, stel dat u wilt alle <h2>-labels in een document wilt selecteren. De XPath query voor dit is:

//h:h2 (Selects all <h2> tags)

Het voorvoegsel // betekent zoeken op elk niveau van het document. Stel nu dat u wilt zoeken naar <span> -labels die zich binnen de ``<a>``labels bevinden. Dat kan worden bereikt met:

//h:a/h:span (Selects <span> tags inside <a> tags)

Als u naar labels wilt zoeken in een bepaald gebied van het document, verander dan de prefix:

/h:body/h:div/h:p (Selects <p> tags that are children of <div> tags that are
children of the <body> tag)

Dit komt overeen met <p>een zeer kort e-book om het gebruik van XPath.</p> in de voorbeeld_ebook aan te tonen, maar niet een van de andere <p>-labels. Het h: voorvoegsel in de bovenstaande voorbeelden is nodig om overeen te komen met XHTML-labels. Dit komt omdat intern, Calibre alle inhoud als XHTML vertegenwoordigt. In XHTML-labels hebben een naamruimte, en h: is de naamruimte prefix voor HTML-labels.

Stel nu dat u zowel de tags ``<h1> ‘’ als ``<h2> ‘’ wilt selecteren. Om dat te doen, hebben we een XPath-constructie nodig genaamd * predicaat *. Een :dfn: predicaat is gewoon een test die wordt gebruikt om tags te selecteren. Tests kunnen willekeurig krachtig zijn en naarmate deze zelfstudie vordert, zult u krachtigere voorbeelden zien. Een predicaat wordt gemaakt door de testuitdrukking tussen vierkante haken te plaatsen:

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

Er zijn verschillende nieuwe functies in deze XPath-expressie. De eerste is het gebruik van het jokerteken `` * ``. Het betekent * komt overeen met elke tag *. Kijk nu naar de testuitdrukking `` name ()=’h1’ of name ()=’h2’ ‘’. : term: name() is een voorbeeld van een * ingebouwde functie *. Het evalueert eenvoudigweg de naam van de tag. Dus door het te gebruiken, kunnen we tags selecteren waarvan de namen h1 of` h2` zijn. Merk op dat de functie name () naamruimten negeert, zodat het voorvoegsel `` h: `` niet nodig is. XPath heeft verschillende handige ingebouwde functies. Een paar meer zullen in deze zelfstudie worden geïntroduceerd.

Selecteren op eigenschap

Om tags te selecteren op basis van hun attributen, is het gebruik van predicaten vereist:

//*[@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")

Hier verwijst de operator ``@’’ naar de kenmerken van de tag. U kunt enkele van de in ‘XPath ingebouwde functies’ _ gebruiken om meer geavanceerdere vergelijkingen uit te voeren op kenmerkwaarden.

Selecteren op label inhoud

Met XPath kunt u zelfs tags selecteren op basis van de tekst die ze bevatten. De beste manier om dit te doen is om de kracht van * reguliere expressies * te gebruiken via de ingebouwde functie re:test():

//h:h2[re:test(., 'chapter|section', 'i')] (Selects <h2> tags that contain the words chapter or
section)

Hier verwijst de operator . naar de inhoud van de tag, net zoals de operator ``@’’ verwijst naar de kenmerken.

Voorbeeld e-boek

<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>

XPath ingebouwde functies

naam()

De naam van het huidige label.

bevat()

contains(s1, s2) returns true if s1 contains s2.

re:test()

re:test (src, patroon, vlaggen) geeft waar terug als de string src overeenkomt met de reguliere expressie patroon. Een bijzonder handige vlag is i, het maakt vergelijken hoofdletter ongevoelig. Een goede inleiding over de syntaxis voor reguliere expressies is te vinden op regexp syntax <https://docs.python.org/library/re.html> _

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Lesboeken »
- De calibre sjabloontaal

De calibre sjabloontaal

De calibre sjabloontaal is een calibre-eigen taal gebruikt doorheen calibre voor taken zoals specificeren van bestandspaden, opmaken van waarden en berekenen van de waarde voor gebruiker-gespecificeerde kolommen. Voorbeelden:

- Specificeer de mappenstructuur en bestandsnamen bij opslaan van bestanden van de calibre bibliotheek naar de schijf of e-boekreader.

- Definieer regels voor toevoegen van iconen en kleuren aan calibre’s boekenlijst.

- Definieer virtuele kolommen die data bevatten van andere kolommen.

- Geavanceerd bibliotheek zoeken.

- Geavanceerd metadata zoeken en vervangen.

De taal is gebouwd rond het concept van een sjabloon die specificeert welke metadata te gebruiken, berekeningen op die metadata en hoe het opgemaakt moet worden.

Basis sjablonen

Een basissjabloon bestaat uit één of meer template expressions. Een template expression bestaat uit tekst en namen tussen accolades ({}) die wordt vervangen door de overeenkomende metadata van het boek in verwerking. Bv. de standaard sjabloon in calibre gebruikt voor boeken naar toestel opslaan heeft vier template expressions:

{author_sort}/{title}/{title} - {authors}

Voor het boek “The Foundation” door “Isaac Asimov” wordt dit:

Asimov, Isaac/The Foundation/The Foundation - Isaac Asimov

De schuine strepen zijn geen template expressions omdat ze tussen {} staan. Zulke tekst blijft waar ie is. Bv., als de sjabloon is:

{author_sort} Some Important Text {title}/{title} - {authors}

dan voor “The Foundation” produceert de sjabloon:

Asimov, Isaac Some Important Text The Foundation/The Foundation - Isaac Asimov

Een template expression heeft toegang tot alle metadata in calibre, inbegrepen aangepaste kolommen (kolommen die u zelf creëerde), door de kolom z’n lookup name te gebruiken. Om een kolom z’n lookup name te vinden (soms velden genaamd), zweef met uw muis over de kolom header in calibre’s boekenlijst. Lookup names voor aangepaste kolommen beginnen altijd met #. Voor reeks type kolommen is er een bijkomend veld genaamd #lookup name_index wat de serie index is voor dat boek in de serie. Bv., als u een aangepaste serie hebt genaamd #myseries dan zal er ook een kolom zijn genaamd #myseries_index. De standaard serie kolom index heet series_index.

Naast de kolom gebaseerde velden, kunt u gebruik maken van:

- {formats} - Een lijst van formaten beschikbaar in de calibre bibliotheek voor een boek

- {identifiers:select(isbn)} - De ISBN van het boek

Als de metadata voor het veld voor een bepaald boek niet gedefinieerd is wordt het veld in de sjabloon vervangen door de lege string (''). Bv. overweeg volgende sjabloon:

{author_sort}/{series}/{title} {series_index}

Als Asimov’s boek “Second Foundation” in de serie “Foundation” is dan geeft de sjabloon:

Asimov, Isaac/Foundation/Second Foundation 3

Als er voor een boek geen serie is ingevoerd geeft de sjabloon:

Asimov, Isaac/Second Foundation

De sjabloonverwerker verwijdert automatisch meerdere schuine strepen en voorloop en volg spaties.

Geavanceerde opmaak

Bovenop metadatasubstitutie kunnen sjablonen voorwaardelijk extra tekst opnemen en de opmaak van gesubstitueerde gegevens controleren.

Voorwaardelijk tekst opnemen

Soms wilt u dat tekst enkel in de uitvoer verschijnt als een veld niet leeg is. Een veel voorkomend geval is series en series_index waar u ofwel niets wil of de twee waarden gescheiden door een streepje. calibre doet dit via een speciale template expression syntaxis.

Bv. met het Foundation voorbeeld van boven, veronderstel dat u wilt dat de sjabloon Foundation - 3 - Second Foundation geeft. Deze sjabloon geeft die uitvoer:

{series} - {series_index} - {title}

Alhoewel, als een boek geen deel van een serie is, geeft de sjabloon - - de titel, wat waarschijnlijk niet is wat u wilt. Gewoonlijk willen de mensen als resultaat de titel zonder extra streepjes. Dit kan met de volgende sjabloon syntaxis:

{field:|prefix_text|suffix_text}

Deze template expression zegt dat als field de waarde XXXX heeft, het resultaat prefix_textXXXXXsuffix_text zal zijn. Als field leeg is (heeft geen waarde) dan zal het resultaat de lege string zijn (niets) omdat voor- en achtervoegsel genegeerd worden. Het voor- en achtervoegsel kunnen lege plekken hebben.

Gebruik geen subsjablonen (`{ … }`) of functies (zie onder) in het voor- of achtervoegsel.

Met deze syntaxis kunnen we het geen-serie probleem boven oplossen met de sjabloon:

{series}{series_index:| - | - }{title}

De streepjes komen er enkel bij als het boek een serie index heeft, die het enkel heeft als het deel van een serie is. Verder weer met het Foundation voorbeeld, de sjabloon geeft Foundation - 1 - Second Foundation.

Aantekeningen:

- U moet een dubbel punt zetten na de lookup name als u een voor- of achtervoegsel gebruikt.

- U moet ofwel beide ofwel geen | karakters gebruiken. Eén gebruiken, zoals in {field:| - }, is niet toegelaten.

- Het is oké om geen tekst te voorzien voor voor- of achtervoegsel, zoals in {series:|| - }. De sjabloon {title:||} is hetzelfde als {title}.

Opmaak

Veronderstel dat u series_index opgemaakt wilt als drie cijfers met voorloopnullen. Dit doet de truc:

{series_index:0>3s} - Drie cijfers met voorloopnullen

Voor volgnullen, gebruik:

{series_index:0<3s} - Drie cijfers met volgnullen

Als u reeksindexen gebruikt met fractionele waarden, bv. 1,1, hebt u de komma’s misschien graag uitgelijnd. Bv. u hebt misschien graag dat indexen 1 en 2,5 eruit zien als 01,00 en 02,50 zodat ze correct sorteren op een toestel dat lexicaal sorteert. Om dit te doen, gebruik:

{series_index:0>5.2f} - Vijf karakters bestaande uit twee cijfers met voorloopnullen, een komma en twee cijfers na de komma.

Als u enkel de twee eerste letters van de data wil, gebruik:

{author_sort:.2} - Enkel de eerste twee letters van de auteursorteernaam

Veel van de calibre sjabloontaalvormgeving komt van Python. Voor meer details over de syntaxis van deze geavanceerde vormgevingsoperaties zie de Python documentatie [https://docs.python.org/3/library/string.html#formatstrings].

Sjablonen gebruiken om aangepaste kolommen te definiëren

Sjablonen kunnen gebruikt worden om informatie weer te geven niet in calibre’s metadata of metadata afwijkend van calibre’s gewone formaat. Bv. u wilt misschien het ISBN tonen, een veld dat calibre niet weergeeft. Dit kan door een aangepaste kolom te creëren van het type Kolom gemaakt van andere kolommen (hierna samengestelde kolommen) en een sjabloon te voorzien om de weer te geven tekst te genereren. De kolom geeft het resultaat weer van het evalueren van de sjabloon. Bv. om het ISBN weer te geven, creëer de kolom en voer {identifiers:select(isbn)} in in het sjabloon vak. Om een kolom weer te geven met de waardes van twee series aangepaste kolommen, gescheiden door een komma, gebruik {#series1:||,}{#series2}.

Samengestelde kolommen kunnen elke sjabloonoptie gebruiken, inclusief opmaak.

Nota: U kan data getoond in een samengestelde kolom niet bewerken. U moet de bronkolommen bewerken. Als u een samengestelde kolom bewerkt, bv. door erop dubbel te klikken, zal calibre de bewerksjabloon openen, niet de onderliggende 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:

- een adapter met een exacte overeenkomst op formaat en toestel, bv. EPUB en ANDROID

- a plugboard with an exact match on format and the special any device choice, e.g., EPUB and any device

- een adapter met de speciale any format keuze en een exacte overeenkomst op toestel, bv. any format en ANDROID

- a plugboard with any format and any device

De labels en auteurs velden hebben een speciale behandeling omdat beide velden meer dan één item kunnen vasthouden. Een boek kan vele labels en meerdere auteurs hebben. Als u opgeeft dat één van deze twee velden gewijzigd gaat worden, wordt de template resultaat onderzocht om te zien of meer dan één item is. Voor labels wordt het resultaat gescheiden waar Calibre een komma vindt. Bijvoorbeeld, als de template de waarde `` Thriller, Horror`` produceert, dan zal het resultaat twee labels zijn, Thriller en Horror. Er is geen manier om een komma in het midden van een tag te zetten.

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.

Functies gebruiken in sjablonen - Eén Functie Modus

Veronderstel dat u de waarde van een veld in hoofdletters wil tonen wanneer dat veld normaal elk woord met beginhoofdletter toont. Dit kan met sjabloonfuncties. Bv., om de titel in hoofdletters te tonen, gebruik de uppercase functie, zoals in {title:uppercase()}. Om met beginhoofdletters te tonen, gebruik {title:titlecase()}.

Functies gaan in het formaat deel van de sjabloon, na de : en voor de eerste | of de sluit } als geen voor- of achtervoegsel is gebruikt. Als u zowel een formaat als een functie referentie hebt, komt de functie na een tweede :. Functies geven de waarde terug van de kolom gespecificeerd in de sjabloon, gepast gewijzigd.

De syntaxis voor gebruik van functies is een van:

{lookup_name:function(arguments)}
{lookup_name:format:function(arguments)}
{lookup_name:function(arguments)|prefix|suffix}
{lookup_name:format:function(arguments)|prefix|suffix}

Functienamen moeten altijd gevolgd worden door open/sluithaakjes. Sommige functies hebben extra waarden (argumenten) nodig en deze staan tussen de haakjes. Argumenten worden gescheiden door komma’s. Letterlijke komma’s (als tekst, niet als argumenten scheiders) moeten vooraf gegaan worden door een achteroverhellend streepje (\). Het laatste (of enige) argument kan geen tekstueel sluithaakje bevatten.

Functies worden geëvalueerd voor formaat specificaties en het voor-/achtervoegsel. Kijk verder beneden voor een voorbeeld van gebruik van zowel een formaat als een functie.

Belangrijk: Als u programmeerervaring hebt, hou er a.u.b. rekening mee dat de syntaxis in Eén Functie Modus niet is wat u verwacht. Strings hebben geen aanhalingstekens en spaties zijn belangrijk. Alle argumenten worden gezien als contanten; er zijn geen expressies.

Gebruik geen subsjablonen (`{ … }`) als functie argumenten. In plaats daarvan, gebruik Sjabloon Programma Modus en Algemene Programma Modus.

Sommige functies vereisen reguliere expressies. In de sjabloontaal zijn reguliere expressie overeenkomsten niet hoofdlettergevoelig.

In de functie documentatie onder betekent de notatie [something]* dat something nul of meer keren herhaald kan worden. De notatie [something]+ betekent dat something één of meer keren herhaald wordt (moet minstens één keer bestaan).

De functies bedoeld voor gebruik in Eén Functie Modus zijn:

capitalize() – geeft de waarde terug met de eerste letter als hoofdletter en de rest klein.

contains(pattern, text if match, text if not match) – controleert of de waarde overeen komt met het reguliere expressie pattern. Geeft text if match terug als het pattern overeenkomt met de waarde, anders text if no match.

count(separator) – interpreteert de waarde als een lijst van termen gescheiden door separator en geeft het aantal items in de lijst terug. Meeste lijsten gebruiken een komma als scheiding maar authors gebruikt een ampersand (&). Bv.: {tags:count(,)}, {authors:count(&)}. Aliassen: count(), list_count()

format_number(template) – interpreteert de waarde als een getal en formateert dat getal met een Python formateersjabloon zoals {0:5.2f} of {0:,d} of ${0:5,.2f}. De formateersjabloon moet beginnen met {0: en eindigen met } zoals in de voorbeelden boven. Uitzondering: u kan de voorloop “{0:” en volg “}” weglaten als de formateersjabloon enkel een formaat bevat. Bekijk de sjabloontaal en de Python documentatie [https://docs.python.org/3/library/string.html#formatstrings] voor meer voorbeelden. Geeft de lege string terug als formateren mislukt.

human_readable() – verwacht een cijfer als waarde en geeft een string terug die dat cijfer voorstelt in KB, MB, GB, enz.

ifempty(text if empty) – als de waarde niet leeg is, geef dan de waarde van het veld terug, anders tekst als leeg.

in_list(separator, [ pattern, found_val, ]* not_found_val) – interpreteert de waarde als een itemlijst gescheiden door separator, het pattern controlerend tegen elk item in de lijst. Als het pattern overeen komt met een item komt found_val terug, anders not_found_val. Het paar pattern en found_value kan zo vaak als gewenst herhaald worden, toelatend verschillende waarden terug te geven, afhankelijk van item’s waarde. De patterns worden op volgorde gecontroleerd en de eerste overeenkomst wordt terug gegeven.

language_strings(localize) – geef de taalnamen [https://www.loc.gov/standards/iso639-2/php/code_list.php] terug voor de taalcodes [https://www.loc.gov/standards/iso639-2/php/code_list.php] ingegeven als de waarde. Bv.: {languages:language_strings()}. Als localize nul is, geef de string in het Engels terug. Als localize niet nul is, geef de string terug in de taal van de huidige locale. Lang_codes is een komma gescheiden lijst.

list_item(index, separator) – begrijp de waarde als een lijst met items gescheiden door separator, terug gevend het ‘index’th item. Het eerste item is nummer nul. Het laatste item heeft index -1 zoals in list_item(-1,separator). Als het item niet op de lijst staat, wordt een lege string terug gegeven.

lookup([ pattern, key, ]* else_key) – De patronen worden gecontroleerd tegen de waarde op volgorde. Als een patroon overeenkomt, wordt de waarde van het veld benoemd door key teruggegeven. Als geen patroon overeenkomt, wordt de waarde van het veld benoemd door else_key teruggegeven. Kijk bij switch (onder).

lowercase() – geeft de waarde van het veld terug in kleine letters.

rating_to_stars(use_half_stars) – Geeft de waardering terug als een string ster (★) karakters. De waarde moet een cijfer zijn tussen 0 en 5. Stel use_half_stars in op 1 als u halve ster karakters wilt voor fractionele getallen beschikbaar met aangepaste waarderingskolommen.

re(pattern, replacement) – geef de waarde terug na toepassen reguliere expressie. Alle exemplaren van pattern in de waarde worden vervangen door replacement. De sjabloontaal gebruikt hoofdletterongevoelige Python reguliere expressies [https://docs.python.org/3/library/re.html].

select(key) – interpreteer de waarde als een komma gescheiden lijst met items waar elk item de vorm id:value heeft (het calibre identifier formaat). De functie vindt het eerste paar met het id gelijk aan de sleutel en geeft de overeenkomende waarde terug. Als geen id overeenkomt, geeft de functie de lege string terug.

shorten(left chars, middle text, right chars) – Geeft een verkorte versie van de waarde terug, bestaande uit left chars karakters van het begin van de waarde, gevolgd door middle text, gevolgd door right chars karakters van het einde van de waarde. Left chars en right chars moeten niet-negatieve integers zijn. Bv.: veronderstel u wilt de titel tonen met een lengte van maximaal 15 karakters. Een sjabloon die dat doet is {title:shorten(9,-,5)}. Voor een boek met de titel Ancient English Laws in the Times of Ivanhoe zal het resultaat Ancient E-nhoe zijn: de eerste 9 karakters van de titel, een -, dan de laatste 5 karakters. Als de lengte van de waarde minder is dan left chars + right chars + de lengte van middle text wordt de waarde onveranderd teruggegeven. Bv. de titel The Dome wordt niet veranderd.

str_in_list(separator, [ string, found_val, ]+ not_found_val) – interpreteer de waarde als een lijst met items gescheiden door separator vergelijk dan string met elke waarde in de lijst. De string is geen reguliere expressie. Als string gelijk is aan een item (hoofdlettergebruik negerend) geef dan de bijhorende found_val terug. Bevat string separators, wordt het ook als een lijst behandeld en elke subwaarde gecontroleerd. De string en found_value paren kunnen naar believen herhaald worden, teruggave van verschillende waardes toelatend afhankelijk van de string waarde. Komt geen enkele string overeen, wordt not_found_value teruggegeven. Strings worden in volgorde gecontroleerd. De eerste overeenkomst wordt teruggegeven.

subitems(start_index, end_index) – Deze functie wordt gebruikt om lijsten van label-like hiërarchische items zoals genres op te delen. Het interpreteert de waarde als een komma gescheiden lijst label-like items, waar elk item een punt gescheiden lijst is. Geeft een nieuwe lijst terug van elk de componenten van start_index tot end_index af te trekken en vervolgens de resultaten terug te combineren. Dubbels worden verwijderd. Het eerste sub-item in een punt gescheiden lijst heeft een index van nul. Als een index negatief is, dan telt het vanaf het einde van de lijst. Als speciaal geval wordt een end_index van nul verondersteld de lengte van de lijst te zijn.

Voorbeelden:

- Een #genre kolom veronderstellend met A.B.C:

- {#genre:subitems(0,1)} geeft “A” terug

- {#genre:subitems(0,2)} geeft “A.B” terug

- {#genre:subitems(1,0)} geeft “B.C” terug

- Een #genre kolom veronderstellend met “A.B.C, D.E”:

- {#genre:subitems(0,1)} geeft “A, D” terug

- {#genre:subitems(0,2)} geeft “A.B, D.E” terug

sublist(start_index, end_index, separator) – interpreteert de waarde als een itemlijst gescheiden door separator, geeft een nieuwe lijst terug, gemaakt van de items van start_index tot end_index. Het eerstet item is nummer zero. Als een index negatief is, dan telt hij van het einde van de lijst. Als een speciaal geval: een end_index van nul is wordt verondersteld de lengte van de lijst te zijn.

Voorbeelden veronderstellen dat de labelkolom (die komma gescheiden is) “A, B ,C” bevat:

- {tags:sublist(0,1,\,)} geeft “A” terug

- {tags:sublist(-1,0,\,)} geeft “C” terug

- {tags:sublist(0,-1,\,)} geeft “A, B” terug

swap_around_articles(separator) – geeft de waarde terug met artikelen naar het einde verplaatst. De waarde kan een lijst zijn, in welk geval elk item van de lijst verwerkt is. Als de waarde een lijst is dan moet u de separator voorzien. Als geen separator voorzien is, wordt de waarde behandeld als één enkele waarde, niet een lijst. De artikelen zijn diegenen gebruikt door calibre om de title_sort te genereren.

swap_around_comma() – gegeven een waarde in de vorm B, A, wordt A B. Dit is heel nuttig voor het omzetten van namen in AN, VN formaat naar VN AN. Als er geen komma is, wordt de waarde onveranderd teruggegeven.

switch([pattern, value,]+ else_value) – voor ieder pattern, value paar, controleert of het waarde overeen komt met de reguliere expressie pattern en indien zo geeft de geassocieerde``value`` terug. Als geen pattern overeenkomt, wordt else_value teruggegeven. U kan zoveel pattern, value paren hebben als u wilt. De eerste overeenkomst wordt teruggegeven.

test(text if not empty, text if empty) – geef text if not empty terug als de waarde niet leeg is, geef anders text if empty terug.

titlecase() – geeft de waarde van het veld terug in elk woord met hoofdletter.

transliterate() – Geeft een string in een Latijns alfabet terug, gevormd door benaderen van de klank van de woorden in het bron veld. Bij voorbeeld, wanneer het bron veld Фёдор Миха́йлович Достоевский is, geeft deze functie Fiodor Mikhailovich Dostoievskii terug.

uppercase() – geeft de waarde van het veld terug in hoofdletter.

Functies en opmaak gebruiken in dezelfde sjabloon

Veronderstel, u hebt een aangepaste kolom met gehele getallen #myint die u wilt weergeven met voorloopnullen, zoals in 003. Een manier om dit te doen is met het formaat 003. Hoewel, standaard, als een getal (geheel of decimaal) gelijk is aan nul wordt de waarde getoond als de lege string dus nul waardes geven de lege string, niet 000. Als u 000 waardes wilt zien, gebruikt u zowel de formaat string als de ifempty functie om de lege waarde terug op nul te zetten. De sjabloon wordt:

{#myint:0>3s:ifempty(0)}

Merk op dat u zowel het voor- als het achtervoegsel kan gebruiken. Als u wil dat het getal verschijnt als [003] of [000], gebruik dan de sjabloon:

{#myint:0>3s:ifempty(0)|[|]}

Algemene Programma Modus

Algemene Programma Modus (APM) vervangt sjabloonexpressies door een programma geschreven in de sjabloontaal. De syntaxis van de taal is gedefinieerd door de volgende grammatica:

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

Aantekeningen:

- een top_expression heeft altijd een waarde. De waarde van een expression_list is de waarde van de laatste top_expression in de lijst. Bv. de waarde van de expressielijst 1;2;'foobar';3 is 3.

- In een logische context is elke niet-lege waarde True

- In een logische context is de lege waarde False

- Strings en getallen kunnen door elkaar gebruikt worden. Bv. 10 en '10' zijn identiek.

- Commentaar zijn regels die beginnen met ‘#’. Commentaar verder in de regel wordt niet ondersteund.

Operator voorrang

De operator voorrang (volgorde van evalueren) van hoogste (eerst geëvalueerd) naar laagste (laatst geëvalueerd) is:

- Functie aanroep, constanten, uitdrukkingen tussen haakjes, instructie-expressies, opdracht-expressies, veldreferenties.

- Unary plus (+) en min (-). Deze operatoren evalueren van rechts naar links.

Deze en alle andere rekenkundige operatoren geven integers terug als het resultaat van de expressie een fractioneel deel gelijk aan nul is. Bv. als een expressie 3.0 terug geeft, wordt dit veranderd in 3.

- Vermenigvuldigen (*) en delen (/). Deze operatoren zijn associatief en evalueren van links naar rechts. Gebruik haakjes als u de volgorde van evaluatie wilt veranderen.

- Optellen (+) en aftrekken (-). Deze operatoren zijn associatief en evalueren van links naar rechts.

- Numerieke en string vergelijkingen. Deze operatoren geven '1' terug als de vergelijking door gaat, anders de lege string (''). Vergelijkingen zijn niet associatief: a < b < c is een syntaxis fout.

- 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 logisch niet (!). Deze operator geeft '1' terug als de expressie Vals is (evalueert naar de lege string), anders ''.

- Logisch en (&&). Deze operator geeft ‘1’ terug als zowel de linker- als de rechter-kant expressies Waar zijn, of de lege string '' als een van beide Vals is. Het is associatief, evalueert van links naar rechts en doet aan kort-sluiten [https://chortle.ccsu.edu/java5/Notes/chap40/ch40_2.html].

- Logische of (||). Deze operator geeft '1' terug als ofwel de linker- ofwel de rechterkant expressie Waar is, of '' als beide Vals zijn. Hij is associatief, evalueert van links naar rechts en doet aan kort-sluiten [https://chortle.ccsu.edu/java5/Notes/chap40/ch40_2.html]. Het is een inclusieve of, geeft '1' terug als zowel de linker- en rechterkant expressies Waar zijn.

Veldreferenties

Een field_reference evalueert naar de waarde van het metadata veld genoemd bij lookup name dat volgt op $ or $$. Gebruik van $ is equivalent aan gebruik van de field() functie. Gebruik van $$ is equivalent aan gebruik van de raw_field functie. Bv.:

* $authors ==> field('authors')
* $#genre ==> field('#genre')
* $$pubdate ==> raw_field('pubdate')
* $$#my_int ==> raw_field('#my_int')

If expressies

If expressies evalueren eerst de condition. Als de condition Waar is (een niet-lege waarde) wordt de expression_list in de then clausule geëvalueerd. Als ze Onwaar is wordt, indien aanwezig, de expression_list in de elif of else clausule geëvalueerd. De elif en else delen zijn optioneel. De woorden if, then, elif, else, en fi zijn gereserveerd; U kan ze niet gebruiken als identificator namen. U kan nieuwe regels en spaties zetten overal waar het zin heeft. De condition is een top_expression geen expression_list; puntkomma’s zijn niet toegelaten. De expression_lists zijn puntkomma gescheiden reeksen met top_expressions. Een if expressie geeft het resultaat terug van de laatste top_expression in de geëvalueerde expression_list, of de lege string als geen expressie lijst werd geëvalueerd.

Voorbeelden:

* 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)

Genest if voorbeeld:

program:
if field('series') then
if check_yes_no(field('#mybool'), '', '', '1') then
'yes'
else
'no'
fi
else
'no series'
fi

Zoals boven gezegd, een if geeft een waarde. Dat betekent dat al de volgenden equivalent zijn:

* 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

Als laatste voorbeeld, dit programma geeft de waarde van de series kolom terug als het boek van een serie is, anders de waarde van de title kolom:

program: field(if field('series') then 'series' else 'title' fi)

For expressies

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.

Voorbeeld: Deze sjabloon verwijdert de eerste hiërarchische naam voor elke waarde in Genre (#genre), een lijst opbouwend met de nieuwe namen:

program:
new_tags = '';
for i in '#genre':
j = re(i, '^.*?\.(.*)$', '\1');
new_tags = list_union(new_tags, j, ',')
rof;
new_tags

Als het originele Genre History.Military, Science Fiction.Alternate History, ReadMe is dan geeft de sjabloon Military, Alternate History, ReadMe terug. U kan deze sjabloon gebruiken in calibre’s Metadata bewerken in bulk  →  Zoeken & vervangen met Zoeken naar ingesteld op template om het eerste niveau van de hiërarchie te verwijderen en de resulterende waarde aan Genre toe te wijzen.

Merk op: de laatste regel in de sjabloon, new_tags, is niet strikt nodig in dit geval omdat for de waarde van de laatste top_expression terug geeft in de expressie lijst. De waarde van een opdracht is de waarde van zijn expressie, dus de waarde van het for bericht is wat was toegewezen aan 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')

Relationele operatoren

Relationele operatoren geven '1' terug als de vergelijking waar is, anders de lege string (‘’).

Er zijn twee soorten relationele operatoren: string vergelijkingen en numerieke vergelijkingen.

String vergelijkingen doen hoofdletterongevoelige string vergelijking in lexicale volgorde. De ondersteunde vergelijkingsoperatoren zijn ==, !=, <, <=, >, >=, in, en inlist. Voor de in operator wordt het resultaat van de linkse expressie geïnterpreteerd als een reguliere expressie patroon. De in operator is Waar als de waarde van de linkse reguliere expressie overeenkomt met de waarde van de rechtse reguliere expressie. De inlist operator is Waar als de linkse reguliere expressie overeenkomt met eender welk item in de rechtse lijst waar de items in de lijst gescheiden zijn door komma’s. De overeenkomsten zijn hoofdletterongevoelige.

De numerieke vergelijkingsoperatoren zijn ==#, !=#, <#, <=#, >#, >=#. De links en rechts expressies moeten evalueren naar numerieke waarden met twee uitzonderingen: zowel de string waarde “None” (niet gedefinieerd veld) en de lege string evalueren naar de waarde nul.

Voorbeelden:

- program: field('series') == 'foo' geett '1' terug als het boek z’n serie ‘foo’ is, anders ''.

- program: 'f.o' in field('series') geeft '1' terug als het boek z’n serie overeenkomt met de reguliere expressie f.o (bv., foo, Off Onyx, enz.), anders ''.

- 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') geeft '1' terug als één van de boeken z’n genres exact overeenkomt met de reguliere expressie ^science$, bv., Science. De genres History of Science en Science Fiction komen niet overeen. Bij geen overeenkomst wordt '' teruggegeven.

- program: if field('series') != 'foo' then 'bar' else 'mumble' fi geeft 'bar' terug als het boek z’n serie niet foo is. Anders geeft het 'mumble' terug.

- program: if field('series') == 'foo' || field('series') == '1632' then 'yes' else 'no' fi geeft 'yes' terug als de serie ofwel``’foo’`` is ofwel '1632', anders 'no'.

- program: if '^(foo|1632)$' in field('series') then 'yes' else 'no' fi geeft 'yes' terug als de serie ofwel 'foo' is ofwel '1632', anders 'no'.

- program: if 11 > 2 then 'yes' else 'no' fi geeft 'no' omdat de > operator een lexicale vergelijking doet.

- program: if 11 ># 2 then 'yes' else 'no' fi geeft 'yes' terug omdat de ># operator een numerieke vergelijking doet.

Extra beschikbare functies

Volgende functies zijn beschikbaar bovenop die beschreven in Eén Functie Modus.

In APM hebben de functies beschreven in Eén Functie Modus allemaal een extra eerste parameter nodig die de waarde specificeert waarop te werken. Alle parameters zijn expression_lists (zie de grammatica boven).

add(x [, y]*) – geeft de som van z’n argumenten terug. Gooit een uitzondering als een argument geen getal is. In de meeste gevallen kan u de + operator gebruiken i.p.v. deze functie.

and(value [, value]*) – geeft de string “1” terug als geen enkele waarde leeg is, anders de lege string. U kan zoveel waardes hebben als u wil. In de meeste gevallen kan u de && operator gebruiken i.p.v. deze functie. Een reden om and niet te vervangen door && is als short-circuiting de resultaten kan veranderen door bijwerkingen. Bv. and(a='',b=5) zal altijd beide opdrachten doen terwijl de && operator de tweede niet zal doen.

assign(id, val) – wijst val toe aan id, geeft dan val terug. id moet een identificator zijn, geen expressie. Meestal kan u de = operator gebruiken i.p.v. deze functie.

approximate_formats() – geeft een komma gescheiden lijst terug met formaten geassocieerd met het boek. Er is garantie dat de lijst correct is maar waarschijnlijk wel. Deze en andere nul-parameter functies kunnen aangeroepen worden in Sjabloon Programma Modus (zie onder) met de sjabloon {:'approximate_formats()'}. Merk op dat de resulterende formaatnamen altijd in hoofdletters zijn, zoals in EPUB. De approximate_formats() functie is beduidend sneller dan de formats_... functies besproken onder.

author_links(val_separator, pair_separator) – geeft een string terug met een lijst van auteurs en deze auteurs’ hun link waarde in de vorm:

author1 val_separator author1_link pair_separator author2 val_separator author2_link etc.

Een auteur wordt gescheiden van z’n link waarde door de val_separator string zonder extra spaties. author:linkvalue paren worden gescheiden door het pair_separator string argument zonder extra spaties. Het is aan u om separator strings te kiezen die niet in de auteursnaam of -links voorkomen. Een auteur wordt inbegrepen zelfs als de auteur link leeg is.

author_sorts(val_separator) – geeft een string terug die een lijst van auteur sorteer waarden bevat voor de auteurs van het boek. De sortering is diegene in de auteur’s metadata (anders dan de auteur_sort in boeken). De teruggegeven lijst heeft de vorm auteur author sort 1 val_separator author sort 2 enz. De author_sort waarden in de lijst zijn in dezelfde volgorde als de auteurs van het boek. Indien u spaties wilt rond val_separator zet deze in de 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() – geeft de waarde van het calibre ‘grootte’ veld terug. Geeft ‘’ terug als er geen formaten zijn.

check_yes_no(field_name, is_undefined, is_false, is_true) – controleert of de waarde van het ja/nee veld genoemd door de opzoeknaam field_name is een van de waardes gespecificeerd door de parameters, 'yes' teruggevend als een overeenkomst werd gevonden anders de lege string. Stel de parameter is_undefined, is_false, of is_true in op 1 (het cijfer) om die voorwaarde te controleren, anders op 0. Bv.:

check_yes_no("#bool", 1, 0, 1) returns 'Yes' if the yes/no field #bool is either True or undefined (neither True nor False).

Meer dan een van is_undefined, is_false, of is_true kan op 1 gezet worden.

ceiling(x) – geeft de kleinste integer terug groter dan of gelijk aan x. Gooit een uitzondering als x geen getal is.

character(character_name) – geeft het karakter terug genoemd door character_name. Bv.: character('newline') geeft een nieuwe regel karakter ('\n'). De ondersteunde karakternamen zijn newline, return, tab en backslash.

cmp(x, y, lt, eq, gt) – vergelijkt x en y na converteren van beiden naar getallen. Geetf lt if x <# y, eq if x ==# y terug, anders gt. Deze functie kan gewoonlijk vervangen worden door een van de numerieke vergelijkingsoperatoren (==#, <#, >#, enz).

connected_device_name(storage_location_key) – als een toestel verbonden is, geef het toestel’s naam terug, anders de lege string. Elke opslaglocatie op een toestel heeft zijn eigen toestelnaam. De storage_location_key locatienamen zijn 'main', 'carda' en 'cardb'. Deze functie werkt enkel in de GUI.

connected_device_uuid(storage_location_key) – als een toestel verbonden is, geef het toestel’s uuid (unique id) terug, anders de lege string. Elke opslaglocatie op een toestel heeft een ander uuid. De storage_location_key locatienamen zijn 'main', 'carda' en 'cardb'. Deze functie werkt enkel in de GUI.

current_library_name() – geeft de laatste naam op het pad naar de huidige calibre bibliotheel terug.

current_library_path() – geeft het volledige pad naar de huidige calibre bibliotheek terug.

current_virtual_library_name() – geeft de naam van de huidige virtuele bibliotheek als er een is, anders de lege string. Bibliotheeknaam hoofdlettergebruik wordt behouden. Bv.: program: current_virtual_library_name(). Deze functie werkt enkel in de GUI.

date_arithmetic(date, calc_spec, fmt) – Bereken een nieuwe datum van date met calc_spec. Geef de nieuwe datum terug geformatteerd volgens optioneel fmt: indien niet voorzien zal het resultaat in ISO formaat zijn. De calc_spec is een string gevormd door aaneengeregen paren van vW (valueWhat) waar v een mogelijk-negatief getal is en W een van de volgende letters:

- s: voeg v seconden toe aan date

- m: voeg v minuten toe aan date

- h: voeg v uren toe aan date

- d: voeg v dagen toe aan date

- w: voeg v weken toe aan date

- y: voeg v jaren toe aan date, waar een jaar is 365 dagen.

Voorbeeld: '1s3d-1m' voegt 1 seconde, 3 dagen toe en trekt 1 minuut af van date.

days_between(date1, date2) – geeft het aantal dagen tussen date1 en date2 terug. Het aantal is positief als date1 groter is dan date2, anders negatief. Als ofwel date1 ofwel date2 geen datum is, wordt de lege string terug gegeven.

divide(x, y) – returns x / y. Gooit een uitzondering als ofwel x ofwel y geen getallen zijn. Deze functie kan gewoonlijk vervangen worden door de / operator.

eval(string) – evalueert de string als een programma, de lokale variabelen doorgevend. Dit laat toe complexe resultaten te bouwen van lokale variabelen met de sjabloonprocessor. In Template Program Mode, omdat de { en } karakters geïnterpreteerd worden voor de sjabloon wordt geëvalueerd, moet u [[ gebruiken voor het { karakter en ]] voor het } karakter. Ze worden automatisch omgezet. Merk ook op dat voor- en achtervoegsel (de |prefix|suffix syntaxis) niet gebruikt kunnen worden in het argument bij deze functie bij gebruik van 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) – geeft de waarde terug van het metadata veld met opzoeknaam lookup_name.

field_exists(field_name) – controleert of een veld (kolom) met de lookup naam field_name bestaat, geeft '1' indien ja en de lege string indien niet.

finish_formatting(val, fmt, prefix, suffix) – pas het formaat, voor- en achtervoegsel toe op een waarde op dezelfde manier als in een sjabloon zoals {series_index:05.2f| - |- }. Deze functie is voorzien om conversie te vergemakkelijken van ingewikkelde één-functie- of sjabloon-programma-modus sjablonen naar GPM Sjablonen. Bv. het volgende programma geeft dezelfde output als bovenstaande sjabloon:

program: finish_formatting(field("series_index"), "05.2f", " - ", " - ")

Nog een voorbeeld: voor de sjabloon {series:re(([^\s])[^\s]+(\s|$),\1)}{series_index:0>2s| - | - }{title} gebruik:

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) – vergelijkt val < cmp in volgorde, het geassocieerde resultaat teruggevend voor de eerste vergelijking die klopt. Geeft else_result terug als geen vergelijking klopt. Bv:

i = 10;
first_matching_cmp(i,5,"small",10,"middle",15,"large","giant")

geeft "large" terug. Hetzelfde voorbeeld met een eerste warde van 16 geeft "giant" terug.

first_non_empty(value [, value]*) – geeft de eerste value terug welke niet leeg is. Als alle waardes leeg zijn, wordt de lege string terug gegeven. U kan zo veel waardes opgeven als u wilt.

floor(x) – geeft de grootste integer terug kleiner dan of gelijk aan x. Gooit een uitzondering als x geen getal is.

format_date(val, format_string) – maak de waarde op, die een datumstring moet zijn, met behulp van format_string, geef een string terug. De opmaakcodes zijn:

- d    : de dag als getal zonder voorloopnul (1 to 31)

- dd   : de dag als getal met voorloopnul (01 to 31)

- ddd  : de afgekorte gelokaliseerde dag naam (bv. “Maa” tot “Zon”).

- dddd : de voluit gelokaliseerde dag naam (bv. “Maandag” tot “Zondag”).

- M    : de maand als getal zonder voorloopnul (1 to 12)

- MM   : de maand als getal met voorloopnul (01 to 12)

- MMM  : de afgekorte gelokaliseerde maandnaam (bv. “jan” tot “dec”).

- MMMM : de voluit gelokaliseerde maandnaam (bv. “januari” tot “december”).

- yy   : het jaar als tweecijferig getal (00 tot 99).

- yyyy : het jaar als viercijferig getal.

- h    : de uren zonder voorloopnul (0 tot 11 of 0 tot 23, afhankelijk van am/pm)

- hh   : de uren met voorloopnul (00 tot 11 of 00 tot 23, afhankelijk van am/pm)

- m    : de minuten zonder voorloopnul (0 tot 59)

- mm   : de minuten met voorloopnul (00 tot 59)

- s    : de seconden zonder voorloopnul (0 tot 59)

- ss   : de seconden met voorloopnul (00 tot 59)

- ap   : gebruik een 12-uur klok i.p.v. een 24-uur klok, met ‘ap’ vervangen door de gelokaliseerde string voor am of pm.

- AP   : gebruik een 12-uur klok i.p.v. een 24-uur klok, met ‘AP’ vervangen door de gelokaliseerde string voor AM of PM.

- iso  : de datum met tijd en tijdzone. Moet als enige formaat aanwezig zijn.

- to_number   : converteer de datum & tijd in een floating point getal (een tijdstempel)

- from_number : converteer een floating point getal (een tijdstempel) in een iso opgemaakte datum. Als u een ander datumformaat wilt, voeg de gewenste opmaakstring toe na from_number en een dubbel punt (:). Bv: from_number:MMM dd yyyy

U kan onverwachte resultaten krijgen bij opmaken van een datum met gelokaliseerde maandnamen. Dit kan gebeuren als u de datumopmaak tweaks veranderde om MMMM te bevatten. In dit geval, i.p.v. de field() functie te gebruiken zoals in:

format_date(field('pubdate'), 'yyyy')

gebruik de raw_field() functie zoals 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) – geeft een komma-gescheiden lijst van door streepjes gescheiden items terug die de wijzigingsdata van de formaten van een boek weergeven. De date_format_string parameter geeft aan hoe de datum geformatteerd moet worden. Zie de format_date() functie voor details. U kunt de select functie gebruiken om de wijzigingsdatum van een specifiek formaat op te vragen. Let op dat formaatnamen altijd in hoofdletters zijn, zoals EPUB.

formats_paths() – geeft een komma gescheiden lijst van streepjes gescheiden FMT:PATH items terug met het volledige pad naar de formaten van een boek. U kunt de select functie gebruiken om het pad naar een specifiek formaat te krijgen. Let op dat formaatnamen altijd in hoofdletters zijn, zoals EPUB.

formats_sizes() – geeft een komma gescheiden lijst van streepjes gescheiden FMT:PATH items met de grootte van de formaten van een boek in bytes. U kunt de select functie gebruiken om de grootte van een specifiek formaat te krijgen. Let op dat formaatnamen altijd in hoofdletters zijn, zoals EPUB.

fractional_part(x) – geeft de waarde na het decimale punt. Bv. fractional_part(3.14) geeft 0.14 terug. Gooit een uitzondering als x geen getal is.

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() – geeft 'Yes' terug als het boek een omslag heeft, anders de lege 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() – controleer of het boek gemarkeerd is in calibre. Indien ja, geef de waarde van de markering terug, ofwel 'true' (kleine letters) of een kommagescheiden lijst van benoemde markeringen. Geeft '' (de lege string) terug als het boek niet gemarkeerd is. Deze functie werkt enkel in de GUI.

language_codes(lang_strings) – geef de taalcodes [https://www.loc.gov/standards/iso639-2/php/code_list.php] terug voor de taalnamen doorgegeven in lang_strings. De strings moeten in de taal van de huidige locale zijn. Lang_strings is een kommagescheiden lijst.

list_contains(value, separator, [ pattern, found_val, ]* not_found_val) – (Alias van in_list) Interpreteert de waarde als een lijst met items gescheiden door separator, evalueert het pattern tegen elke waarde in de lijst. Als het pattern overeen komt met een waarde komt found_val terug, anders not_found_val. De pattern en found_value kunnen zo vaak als gewenst herhaald worden, toelatend verschillende waarden terug te geven, afhankelijk van de zoekopdracht. De patterns worden op volgorde gecontroleerd. De eerste overeenkomst wordt terug gegeven. Aliassen: in_list(), list_contains()

list_count(value, separator) – interpreteert value als een lijst met items gescheiden door separator, geeft het aantal items in de lijst terug. Aliassen: count(), list_count()

list_count_matching(list, pattern, separator) – interpreteert lijst als een lijst met items gescheiden door separator, geeft het aantal items in de lijst terug die overeenkomen met de reguliere expressie pattern. Aliassen: list_count_matching(), count_matching()

list_difference(list1, list2, separator) – geeft een lijst terug gemaakt door uit list1 alle item te verwijderen gevonden in list2 waarbij niet hoofdlettergevoelig wordt vergeleken. De items in list1 en list2 zijn gescheiden door separator, net zoals de items in de teruggegeven lijst.

list_equals(list1, sep1, list2, sep2, yes_val, no_val) – geef yes_val terug als list1 en list2 dezelfde items bevatten, anders no_val. De items worden bepaald door iedere lijst te splitsen met het juiste scheidingsteken (sep1 or sep2). De volgorde van items in de lijst is niet relevant. De vergelijking is hoofdletterongevoelig.

list_intersection(list1, list2, separator) – geeft een lijst terug gemaakt door uit list1 alle item te verwijderen niet gevonden in list2 waarbij niet hoofdlettergevoelig wordt vergeleken. De items in list1 en list2 zijn gescheiden door separator, net zoals de items in de teruggegeven lijst.

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.

Voorbeeld:

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) – maak een lijst door eerst src_list te scheiden in items met het separator karakter. Voor elk item in de lijst, controleer of het overeenkot met include_re. Indien ja, voeg het toe aan de terug te geven lijst. Als opt_replace niet de lege string is, pas de vervanging dan toe vóór het item aan de teruggegeven lijst toe te voegen.

list_re_group(src_list, separator, include_re, search_re [, template_for_group]*) – zoals list-re behalve vervanging is niet optioneel. Het gebruikt re_group(item, search_re, template ...) bij het vervangen.

list_remove_duplicates(list, separator) – geef een lijst terug gemaakt door uit list alle duplicaten te verwijderen. Als items enkel verschillen in hoofdlettergebruik wordt het laatste teruggegeven. De items in lijst zijn gescheiden door separator, net zoals de items in de teruggegeven lijst.

list_sort(list, direction, separator) – geeft list terug gesorteerd met een hoofdletter ongevoelige lexicale sortering. Als direction nul is, wordt list oplopend gesorteerd, anders aflopend. De lijstitems worden gescheiden door separator, zowel als de items in de teruggegeven lijst.

list_split(list_val, sep, id_prefix) – splitst list_val in aparte waardes met sep, wijst dan de waardes toe an lokale variabelen genaamd id_prefix_N waar N de positie is van de waarde in de lijst. Het eerste item heeft positie 0 (zero). De functie geeft het laatste element in de lijst.

Voorbeeld:

list_split('one:two:foo', ':', 'var')

is gelijkwaardig aan:

var_0 = 'one';
var_1 = 'two';
var_2 = 'foo

list_union(list1, list2, separator) – geeft een lijst terug gemaakt door het samenvoegen van de items in list1 en list2, waarbij dubbels worden verwijderd (hoofdletterongevoelig). Als items verschillen in hoofdletter-gebruik, zal de versie van list1 gebruikt worden. De items in list1 en list2 worden gescheiden door separator, zo ook de items in de teruggegeven lijst. Aliassen: merge_lists(), list_union()

mod(x, y) – geeft de vloer van rest van x / y terug. Gooit een uitzondering als ofwel x ofwel y geen getal is.

multiply(x [, y]*) – geeft het product van z’n argumenten terug. Gooit een uitzondering als eender welk argument geen getal is. Deze functie kan gewoonlijk vervangen worden door de * operator.

not(value) – geeft de string “1” terug als de waarde leeg is, anders de lege string. Deze functie kan gewoonlijk vervangen worden door de unary niet (!) operator.

ondevice() – geeft de string 'Yes' terug als ondevice is ingesteld, anders de lege 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]) – geeft het metadata veld terug genoemd door lookup_name zonder toepassen van opmaak. Het evalueert en geeft het optionele tweede argument optional_default terug als de waarde va het veld niet gedefinieerd (None).

raw_list(lookup_name, separator) – geeft de metadatalijst terug benoemd door lookup_name zonder toepassen van formatering of sortering, met items gescheiden door 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 }.

Het volgende voorbeeld zoekt naar een reeks met meer dan één woord zet het eerste woord in hoofdletters:

program: re_group(field('series'), "(\S* )(.*)", "{$:uppercase()}", "{$}")'}

round(x) – geeft de dichtbijgelegen integer van x terug. Gooit een uitzondering als x geen getal is.

series_sort() – geeft de reekssorteerwaarde terug.

strcat(a [, b]*) – kan een willekeurig aantal parameters verwerken. Geeft een string terug bestaande uit de samengevoegde parameters

strcat_max(max, string1 [, prefix2, string2]*) – Geeft een string terug gevormd door samenvoegen van de argumenten. De teruggegeven waarde wordt aan string1` toegewezen. Paren van ``prefix, string worden aan het einde van die waarde toegevoegd, zolang de daaruit volgende string niet langer dan max is. Prefixen kunnen leeg zijn. Geeft string1` terug, zelfs als ``string1` langer is dan ``max. U kunt zoveel prefix, string paren opgeven als u wil.

strcmp(x, y, lt, eq, gt) – doet een hoofdletterongevoelige lexicale vergelijking van x en y. Geeft lt terug als x < y, eq als x == y, anders gt. Deze functie kan dikwijls vervangen worden door een van de lexicale vergelijkingsoperatoren (==, >, <, 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) – Geeft de lengte van de string value terug.

substr(str, start, end) – geeft de start’th tot de end’th karakters van str terug. Het eerste teken in str is het 0-de karakter. Als end negatief is betekent dit ‘tot zoveel karakters vanaf rechts’. Als end nul is betekent dit het laatste karakter. Bijvoorbeeld: substr(‘12345’, 1, 0) geeft ‘2345’, en substr(‘12345’, 1, -1) geeft ‘234’.

subtract(x, y) – geeft x - y terug. Gooit een uitzondering als ofwel x ofwel y geen getallen zijn. Deze functie kan gewoonlijk vervangen worden door de - 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() – geeft een datum+tijd string voor vandaag (nu) terug. Deze waarde is gemaakt voor gebruik in format_date of days_between maar kan aangepast worden als elke andere string. De datum is in ISO [https://en.wikipedia.org/wiki/ISO_8601] datum/tijd formaat.

template(x) – evalueert x als een sjabloon. De evaluatie wordt in z’n eigen context gedaan, variabelen worden dus niet gedeeld door de caller en de sjabloon evaluatie.

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.

Meer complexe programma’s in sjabloonexpressies - Sjabloon Programma Modus

Sjabloon Programma Modus (SPM) is een mengeling van Algemene Programma Modus en Eén Functie Modus. TPM verschilt van Eén Functie Modus in dat het toelaat sjabloon expressies te schrijven die verwijzen naar andere metadata velden, geneste functies gebruikt, variabelen wijzigt, en rekenkunde doet. Het verschilt van Algemene Programma Modus in dat de sjabloon gevat wordt tussen { en } karakters en niet begint met het woord program:. Het programma deel van de sjabloon is een Algemene Programma Modus expressie lijst.

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'))'}

Het voorbeeld toont meerdere dingen:

- TPM is used if the expression begins with :' and ends with '}. Anything else is assumed to be in Single Function Mode.

- de variabele $ staat voor het veld benoemd in de sjabloon: de expressie werkt op, #series in dit geval.

- functies moeten al hun argumenten krijgen. Er is geen standaard waarde. Bv. de standaard ingebouwde functies moeten een extra initiële parameter krijgen die het bronveld aangeeft.

- spaties worden genegeerd en kunnen eender waar in de expressie gebruikt worden

- constante strings zijn omringd door overeenkomende aanhalingstekens, ofwel ' ofwel ".

Alle functies opgelijst onder Eén Functie Modus en Algemene Programma Modus kunnen gebruikt worden in TPM.

In TPM, met { en } karakters in string literals kan leiden tot fouten of onverwachte resultaten omdat ze de sjabloonprocessor in verwarring brengen. Het probeert ze te behandelen als sjabloon expressie grenzen, niet karakters. In sommige maar niet alle gevallen kan u een { vervangen door [[ en een } door ]]. In het algemeen, als uw programma { en } karakters bevat, moet u Algemene Programma Modus gebruiken.

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:

Opgeslagen sjablonen

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() – roep de sjabloon aan zonder argumenten door te geven.

- foo(a, b) roep de sjabloon aan met doorgeven van de waardes van de twee variabelen a en b.

- foo(if field('series') then field('series_index') else 0 fi) – als het boek een series heeft, passeer de series_index, anders de waarde 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')

Voorbeelden, er weer vanuit gaand dat de opgeslagen sjabloon foo heet:

- foo('#myseries') – argument key krijgt de waarde 'myseries' toegewezen en het argument alternate krijgt de standaard waarde 'series' toegewezen.

- foo('series', '#genre') de variabele key krijgt de waarde 'series' toegewezen en de variabele alternate de waarde '#genre'.

- foo() – de variabele key krijgt de lege string toegewezen en de variabele alternate de waarde '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.

Bijkomende informatie voor sjablonen voorzien

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.

Ontwikkelaar: hoe bijkomende informatie doorgeven

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={})

Sjabloon schrijver: hoe toegang krijgen tot de bijkomende informatie

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.

Verschillen:

- 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)}

De soortgelijke sjabloon in SPM is:

{series:'ifempty($, 'no series')'}

De soortgelijke sjabloon in APM 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.

Extra opmerkingen voor opslaan/versturen van sjablonen

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.

Om dit te bereiken:

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.

Functie referentie

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(value [, value]*)
- not(value)
- or(value [, value]*)

Date functions

- date_arithmetic(date, calc_spec, fmt)
- days_between(datum1, datum2)
- today()

Formatting values

- finish_formatting(val, fmt, prefix, suffix)

- format_date(val, format_string)

- format_date_field(field_name, format_string)

- format_number(v, template)
- human_readable(w)
- rating_to_stars(value, use_half_stars)

- urls_from_identifiers(identifiers, sort_results)

Get values from metadata

- annotation_count()

- approximate_formats()

- author_links(waarde_afscheider, paar_afscheider)

- author_sorts(waarde_afscheider)
- 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(date_format)
- formats_paths()
- formats_sizes()
- has_cover()
- is_marked()
- language_codes(language_strings)
- language_strings(lang_codes, vertaling)
- ondevice()
- raw_field(lookup_name [, optional_default])
- raw_list(lookup_name, separator)
- series_sort()
- user_categories()
- virtual_libraries()

If-then-else

- check_yes_no(field_name, is_undefined, is_false, is_true)

- contains(val, pattern, text if match, text if not match)

- field_exists(field_name)
- ifempty(waarde, tekst indien leeg)
- test(val, text if not empty, text if empty)

Iterating over values

- first_non_empty(value [, value]*)
- lookup(val, [pattern, field,]+ else_field)
- switch(val, [pattern, value,]+ else_value)

- switch_if([test_expression, value_expression,]+ else_expression)

List lookup

- identifier_in_list(val, id_name [, found_val, not_found_val])

- in_list(val, separator, [ pattern, found_val, ]+ not_found_val)

- list_item(waarde, index, scheiding)

- select(val, key)

- str_in_list(val, separator, [string, found_val, ]+ not_found_val)

List manipulation

- count(val, separator)

- list_count_matching(list, pattern, separator)

- list_difference(list1, list2, separator)

- list_equals(list1, sep1, list2, sep2, yes_val, no_val)

- list_intersection(lijst1, lijst2, scheiding)

- list_join(with_separator, list1, separator1 [, list2, separator2]*)

- list_re(bron_lijst, scheidingsteken, bevat_re, opt_vervang)

- list_re_group(src_list, separator, include_re, search_re [, group_template]+)

- list_remove_duplicates(list, separator)
- list_sort(lijst, richting, scheiding)
- list_split(list_val, sep, id_prefix)
- list_union(list1, list2, separator)
- range(start, stop, step, limit)
- subitems(val, start_index, end_index)

- sublist(val, start_index, end_index, separator)

Other

- arguments(id[=expression] [, id[=expression]]*)

- assign(id, val)

- globals(id[=expression] [, id[=expression]]*)

- 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(val)
- lowercase(val)
- titlecase(val)
- uppercase(val)

String manipulation

- character(character_name)

- re(val, pattern, replacement)

- re_group(val, pattern [, template_for_group]*)

- shorten(val, left chars, middle text, right chars)

- strcat(a [, b]*)

- strcat_max(max, string1 [, prefix2, string2]*)

- strlen(a)
- substr(str, begin, eind)
- swap_around_articles(val, separator)
- swap_around_comma(waarde)
- to_hex(val)
- transliterate(a)

Template database functions

- book_count(query, use_vl)

- book_values(column, query, sep, use_vl)

- extra_file_modtime(file_name, format_string)

- extra_file_names(sep [, pattern])
- extra_file_size(file_name)
- get_link(field_name, field_value)

- get_note(field_name, field_value, plain_text)

- has_extra_files([pattern])

- has_note(field_name, field_value)

other

- set_globals(id[=expression] [, id[=expression]]*)

API of the Metadata objects

- Metadata

- STANDARD_METADATA_FIELDS

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Lesboeken »
- De calibre sjabloontaal »

- 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(value [, value]*)

- not(value)

- or(value [, value]*)

Date functions

- date_arithmetic(date, calc_spec, fmt)

- days_between(datum1, datum2)

- today()

Formatting values

- finish_formatting(val, fmt, prefix, suffix)

- format_date(val, format_string)

- format_date_field(field_name, format_string)

- format_number(v, template)

- human_readable(w)

- rating_to_stars(value, use_half_stars)

- urls_from_identifiers(identifiers, sort_results)

Get values from metadata

- annotation_count()

- approximate_formats()

- author_links(waarde_afscheider, paar_afscheider)

- author_sorts(waarde_afscheider)

- 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(date_format)

- formats_paths()

- formats_sizes()

- has_cover()

- is_marked()

- language_codes(language_strings)

- language_strings(lang_codes, vertaling)

- ondevice()

- raw_field(lookup_name [, optional_default])

- raw_list(lookup_name, separator)

- series_sort()

- user_categories()

- virtual_libraries()

If-then-else

- check_yes_no(field_name, is_undefined, is_false, is_true)

- contains(val, pattern, text if match, text if not match)

- field_exists(field_name)

- ifempty(waarde, tekst indien leeg)

- test(val, text if not empty, text if empty)

Iterating over values

- first_non_empty(value [, value]*)

- lookup(val, [pattern, field,]+ else_field)

- switch(val, [pattern, value,]+ else_value)

- switch_if([test_expression, value_expression,]+ else_expression)

List lookup

- identifier_in_list(val, id_name [, found_val, not_found_val])

- in_list(val, separator, [ pattern, found_val, ]+ not_found_val)

- list_item(waarde, index, scheiding)

- select(val, key)

- str_in_list(val, separator, [string, found_val, ]+ not_found_val)

List manipulation

- count(val, separator)

- list_count_matching(list, pattern, separator)

- list_difference(list1, list2, separator)

- list_equals(list1, sep1, list2, sep2, yes_val, no_val)

- list_intersection(lijst1, lijst2, scheiding)

- list_join(with_separator, list1, separator1 [, list2, separator2]*)

- list_re(bron_lijst, scheidingsteken, bevat_re, opt_vervang)

- list_re_group(src_list, separator, include_re, search_re [, group_template]+)

- list_remove_duplicates(list, separator)

- list_sort(lijst, richting, scheiding)

- list_split(list_val, sep, id_prefix)

- list_union(list1, list2, separator)

- range(start, stop, step, limit)

- subitems(val, start_index, end_index)

- sublist(val, start_index, end_index, separator)

Other

- arguments(id[=expression] [, id[=expression]]*)

- assign(id, val)

- globals(id[=expression] [, id[=expression]]*)

- 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(val)

- lowercase(val)

- titlecase(val)

- uppercase(val)

String manipulation

- character(character_name)

- re(val, pattern, replacement)

- re_group(val, pattern [, template_for_group]*)

- shorten(val, left chars, middle text, right chars)

- strcat(a [, b]*)

- strcat_max(max, string1 [, prefix2, string2]*)

- strlen(a)

- substr(str, begin, eind)

- swap_around_articles(val, separator)

- swap_around_comma(waarde)

- to_hex(val)

- transliterate(a)

Template database functions

- book_count(query, use_vl)

- book_values(column, query, sep, use_vl)

- extra_file_modtime(file_name, format_string)

- extra_file_names(sep [, pattern])

- extra_file_size(file_name)

- get_link(field_name, field_value)

- get_note(field_name, field_value, plain_text)

- has_extra_files([pattern])

- has_note(field_name, field_value)

other

- set_globals(id[=expression] [, id[=expression]]*)

API of the Metadata objects

Arithmetic

add(x [, y]*)

class calibre.utils.formatter_functions.BuiltinAdd[broncode]

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.

ceiling(x)

class calibre.utils.formatter_functions.BuiltinCeiling[broncode]

ceiling(x) – returns the smallest integer greater than or equal to x. Throws an exception if x is not a number.

divide(x, y)

class calibre.utils.formatter_functions.BuiltinDivide[broncode]

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.

floor(x)

class calibre.utils.formatter_functions.BuiltinFloor[broncode]

floor(x) – returns the largest integer less than or equal to x. Throws an exception if x is not a number.

fractional_part(x)

class calibre.utils.formatter_functions.BuiltinFractionalPart[broncode]

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.

mod(x)

class calibre.utils.formatter_functions.BuiltinMod[broncode]

mod(x) – returns floor(remainder of x / y). Throws an exception if either x or y is not a number.

multiply(x [, y]*)

class calibre.utils.formatter_functions.BuiltinMultiply[broncode]

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.

round(x)

class calibre.utils.formatter_functions.BuiltinRound[broncode]

round(x) – returns the nearest integer to x. Throws an exception if x is not a number.

subtract(x, y)

class calibre.utils.formatter_functions.BuiltinSubtract[broncode]

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.

Boolean

and(value [, value]*)

class calibre.utils.formatter_functions.BuiltinAnd[broncode]

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.

not(value)

class calibre.utils.formatter_functions.BuiltinNot[broncode]

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.

or(value [, value]*)

class calibre.utils.formatter_functions.BuiltinOr[broncode]

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.

Date functions

date_arithmetic(date, calc_spec, fmt)

class calibre.utils.formatter_functions.BuiltinDateArithmetic[broncode]

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(datum1, datum2)

class calibre.utils.formatter_functions.BuiltinDaysBetween[broncode]

days_between(datum1, datum2) – geeft het aantal dagen tussen datum1 en datum2 terug. Het aantal is positief als datum1 groter is dan datum2, anders negatief. Als ofwel datum1 ofwel datum2 geen datum is, wordt een lege string terug gegeven.

today()

class calibre.utils.formatter_functions.BuiltinToday[broncode]

today() – geeft een datumstring voor vandaag. Deze waarde is gemaakt voor gebruik in de format_date of days_between, maar kan worden aangepast in iedere andere string. De datum is in ISO-formaat.

Formatting values

finish_formatting(val, fmt, prefix, suffix)

class calibre.utils.formatter_functions.BuiltinFinishFormatting[broncode]

finish_formatting(val, fmt, prefix, suffix) – toepassing van formaat, voor- en achtervoegsel op een waarde op dezelfde manier als in een sjabloon zoals`{series_index:05.2f| - |- }`. Bijvoorbeeld, het volgende programma produceert dezelfde uitvoer als bovenstaand sjabloon: programma: finish_formatting(field(“series_index”), “05.2f”, “ - “, “ - “)

format_date(val, format_string)

class calibre.utils.formatter_functions.BuiltinFormatDate[broncode]

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

format_date_field(field_name, format_string)

class calibre.utils.formatter_functions.BuiltinFormatDateField[broncode]

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[broncode]

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.

human_readable(w)

class calibre.utils.formatter_functions.BuiltinHumanReadable[broncode]

human_readable(w) – geeft een string terug die de waarde van getal w representeerd in KB, MB, GB, enz.

rating_to_stars(value, use_half_stars)

class calibre.utils.formatter_functions.BuiltinRatingToStars[broncode]

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.

urls_from_identifiers(identifiers, sort_results)

class calibre.utils.formatter_functions.BuiltinUrlsFromIdentifiers[broncode]

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.

Get values from metadata

annotation_count()

class calibre.utils.formatter_functions.BuiltinAnnotationCount[broncode]

annotation_count() – return the total number of annotations of all types attached to the current book. This function works only in the GUI.

approximate_formats()

class calibre.utils.formatter_functions.BuiltinApproximateFormats[broncode]

approximate_formats() – geeft een komma-gescheiden lijst van formaten die ooit zijn geassocieerd met het boek. Het is niet gegarandeerd dat de lijst correct is, alhoewel dat wel waarschijnlijk is. Deze functie kan worden aangeroepen in sjabloon programma modus met gebruik van het sjabloon “{:’approximate_formats()’}”. Merk op dat de namen van de formaten altijd in hoofdletters staan, zoals in EPUB. Deze functie werkt alleen in de GUI. als u deze waarden wilt gebruiken in bewaren-op-schijf of verzenden-naar-toestel sjablonen dan moet een aangepaste “Kolom gebouwd van andere kolommen” gemaakt worden, met gebruik van de functies in dat kolomsjabloon en waarde van die kolom gebruiken in uw bewaren/versturen sjablonen

author_links(waarde_afscheider, paar_afscheider)

class calibre.utils.formatter_functions.BuiltinAuthorLinks[broncode]

author_links(waarde_afscheider, paar_afscheider) – Geeft een string terug die een lijst van auteurs bevat en diens verband waarden in de vorm van auteur1 waarde_afscheider auteur1link paar_afscheider auteur2 waarde_afscheider auteur2link enz. Een auteur wordt afgescheiden van zijn link waarde door de waarde_afscheider zonder toegevoegde spaties. auteur:linkwaarde paren zijn afgescheiden door het paar_afscheider string arguemten zonder toegevoegde spaties. Het is aan u om de afscheider strings te kiezen die niet voor kunnen komen in de auteur naam of linken. Een auteur is bijgevoegd zelfs als de auteur link leeg is.

author_sorts(waarde_afscheider)

class calibre.utils.formatter_functions.BuiltinAuthorSorts[broncode]

author_sorts(waarde_afscheider) – geeft een string terug die een lijst van auteur sorteer waarden bevat voor de auteurs van het boek. Het sorteren is diegene in de auteurs metadata (anders dan de auteur_sort bij boeken). De teruggegeven lijst heeft de vorm auteur sort 1 waarde_afscheider auteur sort 2 enz. De author_sort waarden in de lijst zijn in dezelfde volgorde als de auteurs van het boek. Indien u spaties wilt rond waarde_afscheider zet deze in de afscheider string

booksize()

class calibre.utils.formatter_functions.BuiltinBooksize[broncode]

booksize() – geeft de waarde van het grootte veld. Deze functie werkt alleen in de GUI. als u deze waarden wilt gebruiken in bewaren-op-schijf of verzenden-naar-toestel sjablonen dan moet een aangepaste “Kolom gebouwd van andere kolommen” gemaakt worden, met gebruik van de functies in dat kolomsjabloon en waarde van die kolom gebruiken in uw bewaren/versturen sjablonen

connected_device_name(storage_location)

class calibre.utils.formatter_functions.BuiltinConnectedDeviceName[broncode]

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.

connected_device_uuid(storage_location)

class calibre.utils.formatter_functions.BuiltinConnectedDeviceUUID[broncode]

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.

current_library_name()

class calibre.utils.formatter_functions.BuiltinCurrentLibraryName[broncode]

current_library_name() – Geeft de laatste naam in het pad van de huidige calibre bibliotheek. Deze functie kunt u aanroepen in sjabloon programmeer modus met “{:’current_library_name()’}”.

current_library_path()

class calibre.utils.formatter_functions.BuiltinCurrentLibraryPath[broncode]

current_library_path() – geef het pad naar de huidige calibre bibliotheek terug. Deze functie kan worden aangeroepen in sjabloon programmeer modus door het sjabloon “{:’current_library_path()’}” te gebruiken.

current_virtual_library_name()

class calibre.utils.formatter_functions.BuiltinCurrentVirtualLibraryName[broncode]

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()”.

field(lookup_name)

class calibre.utils.formatter_functions.BuiltinField[broncode]

field(lookup_name) – returns the metadata field named by lookup_name

formats_modtimes(date_format)

class calibre.utils.formatter_functions.BuiltinFormatsModtimes[broncode]

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.

formats_paths()

class calibre.utils.formatter_functions.BuiltinFormatsPaths[broncode]

formats_paths() – retourneert een door komma’s gescheiden lijst van door dubbele punt gescheiden items die het volledige pad naar de formaten van een boek geven. U kunt gebruikmaken van de select-functie om het pad van een specifiek formaat op te vragen. Let erop dat formaatnamen altijd in hoofdletters zijn, zoals in EPUB.

formats_sizes()

class calibre.utils.formatter_functions.BuiltinFormatsSizes[broncode]

formats_sizes() – retourneert een door komma’s gescheiden lijst van door dubbele punten gescheiden items die de grootte van formaten van een boek in bytes weergeven. U kunt de select-functie gebruiken om de grootte van een specifiek formaat op te vragen. Let erop dat formaatnamen altijd in hoofdletters zijn, zoals EPUB.

has_cover()

class calibre.utils.formatter_functions.BuiltinHasCover[broncode]

has_cover() – Ja teruggeven als het boek een omslag heeft, anders een lege string

is_marked()

class calibre.utils.formatter_functions.BuiltinIsMarked[broncode]

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.

language_codes(language_strings)

class calibre.utils.formatter_functions.BuiltinLanguageCodes[broncode]

language_codes(language_strings) – geeft de taalcodes terug voor de string die is meegegeven in lang_strings. De strings moeten in de huidig gebruikte vertaling worden ingevoerd. Lang_strings is een kommagescheiden lijst.

language_strings(lang_codes, vertaling)

class calibre.utils.formatter_functions.BuiltinLanguageStrings[broncode]

language_strings(lang_codes, vertaling) – geeft de strings voor de taalcode meegegeven in lang_codes. Indien vertaling nu is, wordt de string in het Engels terug gegeven. Als de vertaling niet nul is, geef de strings terug in de huidige vertaling. Lang_codes is een kommagescheiden lijst.

ondevice()

class calibre.utils.formatter_functions.BuiltinOndevice[broncode]

ondevice() – geeft Ja als ondevice is ingesteld, anders een lege tekenreeks. Deze functie werkt alleen in de GUI. als u deze waarden wilt gebruiken in bewaren-op-schijf of verzenden-naar-toestel sjablonen dan moet een aangepaste “Kolom gebouwd van andere kolommen” gemaakt worden, met gebruik van de functies in dat kolomsjabloon en waarde van die kolom gebruiken in uw bewaren/versturen sjablonen

raw_field(lookup_name [, optional_default])

class calibre.utils.formatter_functions.BuiltinRawField[broncode]

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’).

raw_list(lookup_name, separator)

class calibre.utils.formatter_functions.BuiltinRawList[broncode]

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.

series_sort()

class calibre.utils.formatter_functions.BuiltinSeriesSort[broncode]

series_sort() – de reekssorteerwaarde teruggeven

user_categories()

class calibre.utils.formatter_functions.BuiltinUserCategories[broncode]

user_categories() – geeft een komma-gescheiden lijst van gebruikers categorieën waarin dit boek voorkomt. Deze functie werkt alleen in de GUI. als u deze waarden wilt gebruiken in bewaren-op-schijf of verzenden-naar-toestel sjablonen dan moet een aangepaste “Kolom gebouwd van andere kolommen” gemaakt worden, met gebruik van de functies in dat kolomsjabloon en waarde van die kolom gebruiken in uw bewaren/versturen sjablonen

virtual_libraries()

class calibre.utils.formatter_functions.BuiltinVirtualLibraries[broncode]

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

If-then-else

check_yes_no(field_name, is_undefined, is_false, is_true)

class calibre.utils.formatter_functions.BuiltinCheckYesNo[broncode]

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.

contains(val, pattern, text if match, text if not match)

class calibre.utils.formatter_functions.BuiltinContains[broncode]

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

field_exists(field_name)

class calibre.utils.formatter_functions.BuiltinFieldExists[broncode]

field_exists(field_name) – checks if a field (column) named field_name exists, returning ‘1’ if so and ‘’ if not.

ifempty(waarde, tekst indien leeg)

class calibre.utils.formatter_functions.BuiltinIfempty[broncode]

ifempty(waarde, tekst indien leeg) – geeft waarde terug als het veld niet leeg is, anders ‘tekst indien leeg’

test(val, text if not empty, text if empty)

class calibre.utils.formatter_functions.BuiltinTest[broncode]

test(val, text if not empty, text if empty) – return text if not empty if val is not empty, otherwise return text if empty

Iterating over values

first_non_empty(value [, value]*)

class calibre.utils.formatter_functions.BuiltinFirstNonEmpty[broncode]

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.

lookup(val, [pattern, field,]+ else_field)

class calibre.utils.formatter_functions.BuiltinLookup[broncode]

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

switch(val, [pattern, value,]+ else_value)

class calibre.utils.formatter_functions.BuiltinSwitch[broncode]

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

switch_if([test_expression, value_expression,]+ else_expression)

class calibre.utils.formatter_functions.BuiltinSwitchIf[broncode]

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.

List lookup

identifier_in_list(val, id_name [, found_val, not_found_val])

class calibre.utils.formatter_functions.BuiltinIdentifierInList[broncode]

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(val, separator, [ pattern, found_val, ]+ not_found_val)

class calibre.utils.formatter_functions.BuiltinInList[broncode]

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()

list_item(waarde, index, scheiding)

class calibre.utils.formatter_functions.BuiltinListitem[broncode]

list_item(waarde, index, scheiding) – verwerk de waarde als een lijst van items gescheiden door ‘scheiding’ en geef het ‘index’de item terug. Het eerste item heeft index nul. Het laatste item kan worden teruggegeven door gebruik te maken van list_item(-1,scheiding). Als het item niet in de lijst zit zal een lege waarde worden terug gegeven. De scheiding heeft de zelfde functie als in de count functie.

select(val, key)

class calibre.utils.formatter_functions.BuiltinSelect[broncode]

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.

str_in_list(val, separator, [string, found_val, ]+ not_found_val)

class calibre.utils.formatter_functions.BuiltinStrInList[broncode]

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.

List manipulation

count(val, separator)

class calibre.utils.formatter_functions.BuiltinCount[broncode]

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()

list_count_matching(list, pattern, separator)

class calibre.utils.formatter_functions.BuiltinListCountMatching[broncode]

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)

class calibre.utils.formatter_functions.BuiltinListDifference[broncode]

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)

class calibre.utils.formatter_functions.BuiltinListEquals[broncode]

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(lijst1, lijst2, scheiding)

class calibre.utils.formatter_functions.BuiltinListIntersection[broncode]

list_intersection(lijst1, lijst2, scheiding) – geeft een lijst terug, gemaakt door uit lijst1 alle items te verwijderen die niet in lijst2 aanwezig zijn, niet hoofdlettergevoelig vergeleken. De items in lijst1 en lijst2 worden gescheiden door scheiding, zo ook de items in de teruggegeven lijst.

list_join(with_separator, list1, separator1 [, list2, separator2]*)

class calibre.utils.formatter_functions.BuiltinListJoin[broncode]

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. Example:

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, ‘&’)

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(bron_lijst, scheidingsteken, bevat_re, opt_vervang)

class calibre.utils.formatter_functions.BuiltinListRe[broncode]

list_re(bron_lijst, scheidingsteken, bevat_re, opt_vervang) – maakt een lijst door de bon_lijst te scheiden in items, gebruikmakend van het schiedingsteken. Voor ietder item in de lijst wordt gecontroleerd of het overeenkomst met bevat_re. Als dat zo is, wordt het toegevoerd aan de terug te geven lijst. Als opt_vervang geen lege string is, wordt deze vervanging toegepast voor het item wordt toegevoegd aan de teruggegeven lijst.

list_re_group(src_list, separator, include_re, search_re [, group_template]+)

class calibre.utils.formatter_functions.BuiltinListReGroup[broncode]

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.

list_remove_duplicates(list, separator)

class calibre.utils.formatter_functions.BuiltinListRemoveDuplicates[broncode]

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.

list_sort(lijst, richting, scheiding)

class calibre.utils.formatter_functions.BuiltinListSort[broncode]

list_sort(lijst, richting, scheiding) – geef een lijst, gesorteerd zonder op hoofdletters te letten. Als richting nul is wordt de lijst oplopend gesorteerd, anders aflopend. De items in lijst worden gescheiden door scheiding, zo ook de items in de teruggegeven lijst.

list_split(list_val, sep, id_prefix)

class calibre.utils.formatter_functions.BuiltinListSplit[broncode]

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’.

list_union(list1, list2, separator)

class calibre.utils.formatter_functions.BuiltinListUnion[broncode]

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()

range(start, stop, step, limit)

class calibre.utils.formatter_functions.BuiltinRange[broncode]

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).

subitems(val, start_index, end_index)

class calibre.utils.formatter_functions.BuiltinSubitems[broncode]

subitems(val, start_index, end_index) – Deze functie wordt gebruikt om lijsten van items zoals genres op te delen. Het interpreteert de waarde als een door komma’s gescheiden lijst met items, waarbij elk item een door punten gescheiden lijst is. Geeft een nieuwe lijst door eerst alle met punten gescheiden items te vinden, vervolgens voor elk item de waarde van de start_index tot end_index componenten te extraheren, en dan de resultaten wederom terug te combineren. De eerste component in een punt gescheiden lijst heeft een index van nul. Als een index negatief is, dan telt het vanaf het einde van de lijst. Als speciaal geval wordt een end_index van nul verondersteld de lengte van de lijst te zijn. Voorbeeld met gebruik van de basis sjabloon mode en uitgaande van een #genre waarde van “A.B.C”: {#genre:subitems(0,1)} retourneert “A”. {#genre:subitems(0,2)} retourneert “A.B”. {#genre:subitems(1,0)} retourneert “B.C”. Uitgaande van een #genre waarde van “A.B.C, D.E.F”, {#genre:subitems(0,1)} retourneert “A, D”. {#genre:subitems(0,2)} retourneert “A.B, D.E”

sublist(val, start_index, end_index, separator)

class calibre.utils.formatter_functions.BuiltinSublist[broncode]

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”.

Other

arguments(id[=expression] [, id[=expression]]*)

class calibre.utils.formatter_functions.BuiltinArguments[broncode]

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.

assign(id, val)

class calibre.utils.formatter_functions.BuiltinAssign[broncode]

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.

globals(id[=expression] [, id[=expression]]*)

class calibre.utils.formatter_functions.BuiltinGlobals[broncode]

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.

print(a[, b]*)

class calibre.utils.formatter_functions.BuiltinPrint[broncode]

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.

Recursion

eval(template)

class calibre.utils.formatter_functions.BuiltinEval[broncode]

eval(template) – evalueert het sjabloon, geeft lokale variabelen door (die ‘assign’ed zijn) in plaats van de metadata van het boek. Hierdoor kan met behulp van de sjabloon processor complexe resultaten geconstrueerd worden van lokale variabelen. Omdat de { en } karakters speciaal zijn, dient u [[ voor het { karakter en ]] voor het } karakter te gebruiken; deze worden automatisch omgezet. Merk ook op dat voor- en achtervoegsels (de |prefix|suffix syntaxis) niet gebruikt kunnen worden in het argument voor deze functie bij gebruik in sjabloon programmeer modus.

template(x)

class calibre.utils.formatter_functions.BuiltinTemplate[broncode]

template(x) – evalueert x als sjabloon. De evaluatie gebeurt in haar eigen samenhang, hetgeen betekent dat variabelen niet gedeeld worden tussen de aanroeper van de functie en de sjabloon evaluatie. Omdat de { en } karakters speciaal zijn, moet u [[ voor het { karakter en ]] voor het } karakter gebruiken; zij worden automatisch omgezet. Bijvoorbeeld, template(‘[[title_sort]]’) evalueert het sjabloon {title_sort} en geeft haar waarde terug. Merk op dat ook voorvoegsels en achtervoegsels (de |prefix|suffix syntaxis) niet gebruikt kunnen worden in het meegegeven argument naar deze functie wanneer u de sjabloon programmeer modus gebruikt.

Relational

cmp(x, y, lt, eq, gt)

class calibre.utils.formatter_functions.BuiltinCmp[broncode]

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.

first_matching_cmp(val, [cmp1, result1,]+, else_result)

class calibre.utils.formatter_functions.BuiltinFirstMatchingCmp[broncode]

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”.

strcmp(x, y, lt, eq, gt)

class calibre.utils.formatter_functions.BuiltinStrcmp[broncode]

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.

strcmpcase(x, y, lt, eq, gt)

class calibre.utils.formatter_functions.BuiltinStrcmpcase[broncode]

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. 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.

String case changes

capitalize(val)

class calibre.utils.formatter_functions.BuiltinCapitalize[broncode]

capitalize(val) – retourneert val met hoofdletters

lowercase(val)

class calibre.utils.formatter_functions.BuiltinLowercase[broncode]

lowercase(val) – retourneert val in kleine letters

titlecase(val)

class calibre.utils.formatter_functions.BuiltinTitlecase[broncode]

titlecase(val) – retourneert alles in val met beginhoofdletters

uppercase(val)

class calibre.utils.formatter_functions.BuiltinUppercase[broncode]

uppercase(val) – retourneert val in hoofdletters

String manipulation

character(character_name)

class calibre.utils.formatter_functions.BuiltinCharacter[broncode]

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’.

re(val, pattern, replacement)

class calibre.utils.formatter_functions.BuiltinRe[broncode]

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

re_group(val, pattern [, template_for_group]*)

class calibre.utils.formatter_functions.BuiltinReGroup[broncode]

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()]]”, “[[$]]”)’}

shorten(val, left chars, middle text, right chars)

class calibre.utils.formatter_functions.BuiltinShorten[broncode]

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.

strcat(a [, b]*)

class calibre.utils.formatter_functions.BuiltinStrcat[broncode]

strcat(a [, b]*) – can take any number of arguments. Returns the string formed by concatenating all the arguments

strcat_max(max, string1 [, prefix2, string2]*)

class calibre.utils.formatter_functions.BuiltinStrcatMax[broncode]

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.

strlen(a)

class calibre.utils.formatter_functions.BuiltinStrlen[broncode]

strlen(a) – Geeft de lengte van de string die als argument wordt meegegeven

substr(str, begin, eind)

class calibre.utils.formatter_functions.BuiltinSubstr[broncode]

substr(str, begin, eind) – geeft de tekens uit str die tussen begin en eind voorkomen. Het eerste teken in str is het 0-de teken. Als einde negatief is betekend dit ‘tot zoveel teken vanaf rechts’. Als einde nul is betekend dit het laatste teken. Bijvoorbeeld: substr(‘12345’, 1, 0) geeft ‘2345’, en substr(‘12345’, 1, -1) geeft ‘234’.

swap_around_articles(val, separator)

class calibre.utils.formatter_functions.BuiltinSwapAroundArticles[broncode]

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.

swap_around_comma(waarde)

class calibre.utils.formatter_functions.BuiltinSwapAroundComma[broncode]

swap_around_comma(waarde) – gegeven een waarde in de vorm “B, A”, wordt “A B” terug gegeven. Dit is heel nuttig voor het omzetten van namen in de vorm AN, VN naar VN AN. Als er geen komma is, wordt de waarde onveranderd teruggegeven

to_hex(val)

class calibre.utils.formatter_functions.BuiltinToHex[broncode]

to_hex(val) – returns the string encoded in hex. This is useful when constructing calibre URLs.

transliterate(a)

class calibre.utils.formatter_functions.BuiltinTransliterate[broncode]

transliterate(a) – Retourneert een regel in een Latijns alfabet gevormd door de benadering van de klank van de woorden in de bron-regel. Bij voorbeeld, wanneer de bron “Фёдор Миха́йлович Достоевский” is, de functie retourneert “Fiodor Mikhailovich Dostoievskii”.

Template database functions

book_count(query, use_vl)

class calibre.utils.formatter_functions.BuiltinBookCount[broncode]

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.

book_values(column, query, sep, use_vl)

class calibre.utils.formatter_functions.BuiltinBookValues[broncode]

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.

extra_file_modtime(file_name, format_string)

class calibre.utils.formatter_functions.BuiltinExtraFileModtime[broncode]

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(sep [, pattern])

class calibre.utils.formatter_functions.BuiltinExtraFileNames[broncode]

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. This function can be used only in the GUI.

extra_file_size(file_name)

class calibre.utils.formatter_functions.BuiltinExtraFileSize[broncode]

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.This function can be used only in the GUI.

get_link(field_name, field_value)

class calibre.utils.formatter_functions.BuiltinGetLink[broncode]

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[broncode]

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([pattern])

class calibre.utils.formatter_functions.BuiltinHasExtraFiles[broncode]

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.

has_note(field_name, field_value)

class calibre.utils.formatter_functions.BuiltinHasNote[broncode]

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[=expression] [, id[=expression]]*)

class calibre.utils.formatter_functions.BuiltinSetGlobals[broncode]

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.

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=('Onbekend',), other=None, template_cache=None, formatter=None)[broncode]

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)[broncode]

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>>)[broncode]

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()[broncode]

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)[broncode]

Set all identifiers. Note that if you previously set ISBN, calling this method will delete it.

set_identifier(typ, val)[broncode]

If val is empty, deletes identifier of type typ

standard_field_keys()[broncode]

return a list of all possible keys, even if this book doesn’t have them

custom_field_keys()[broncode]

return a list of the custom fields in this book

all_field_keys()[broncode]

All field keys known by this instance, even if their value is None

metadata_for_field(key)[broncode]

return metadata describing a standard or custom field.

all_non_none_fields()[broncode]

Return a dictionary containing all non-None metadata fields, including the custom ones.

get_standard_metadata(field, make_copy)[broncode]

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)[broncode]

return a dict containing all the standard field metadata associated with the book.

get_all_user_metadata(make_copy)[broncode]

return a dict containing all the custom field metadata associated with the book.

get_user_metadata(field, make_copy)[broncode]

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)[broncode]

store custom field metadata into the object. Field is the key name not the label

set_user_metadata(field, metadata)[broncode]

store custom field metadata for one column into the object. Field is the key name not the label

remove_stale_user_metadata(other_mi)[broncode]

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)[broncode]

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)[broncode]

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)[broncode]

Returns the tuple (display_name, formatted_value)

to_html()[broncode]

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

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Lesboeken »

- Alles over het gebruik van reguliere expressies in calibre

Alles over het gebruik van reguliere expressies in calibre

Reguliere expressies zijn functies die op veel plaatsen van calibre worden gebruikt om geavanceerde manipulatie van e-book inhoud en metadata uit te voeren. Deze handleiding is een voorzichtige inleiding om u op weg te helpen met het gebruik van reguliere uitdrukkingen in calibre.

Inhoud

- Allereerst een waarschuwing en bemoediging

- Waar in calibre kan je reguliere expressies gebruiken?

- Wat is een reguliere expressie eigenlijk?

- Iets meer uitleg graag?

- Dat klinkt niet heel moeilijk. Ga door.

- Prachtig! Nu begint het ergens op te lijken!

- Nou, deze speciale karakters zijn erg netjes en zo maar wat als ik een overeenkomst met een punt of vraagteken wil?

- Wat zijn dan de meest nuttige sets?

- Maar als ik een paar verschillende strings had die ik wilde laten overeenkomen worden dingen ingewikkeld?

- U miste…

- In het begin zei u dat er een manier was om een reguliere expressie hoofdletterongevoelig te maken?

- Ik denk dat ik deze reguliere expressies begin te begrijpen nu … hoe gebruik ik ze in calibre?

- Omzettingen

- Boeken toevoegen

- Massabewerking van metadata

- Snelle referentie

- Credits

Allereerst een waarschuwing en bemoediging

Dit gaat onvermijdelijk enigszins technisch worden, - reguliere expressies zijn immers een technisch hulpmiddel voor het doen van technische zaken. Ik zal wat jargon en concepten moeten gebruiken die ingewikkeld of ingewikkeld lijken. Ik ga proberen die concepten zo duidelijk mogelijk uit te leggen, maar kan echt niet zonder ze helemaal te gebruiken. Dat gezegd zijnde, wees niet ontmoedigd door enig jargon, omdat ik heb geprobeerd om alles nieuw uit te leggen. En terwijl reguliere expressies zelf misschien een mysterieuze, zwarte magie lijken (of, om prozaïscher te zijn, een willekeurige reeks mumbo-jumbo letters en tekens), beloof ik dat ze niet zo gecompliceerd zijn. Zelfs degenen die reguliere uitdrukkingen echt goed begrijpen, hebben moeite met het lezen van de meer gecompliceerde uitdrukkingen, maar het schrijven ervan is niet zo moeilijk - je construeert de uitdrukking stap voor stap. Dus, neem een stap en volg me naar het konijnenhol.

Waar in calibre kan je reguliere expressies gebruiken?

Er zijn een paar plaatsen waar calibre reguliere expressies gebruikt. Daar is de :guilabel :Zoeken & vervangen in conversie-opties, detectie van metadata uit bestandsnamen in de importinstellingen en Zoeken & vervangen bij het bewerken van de metadata van boeken in bulk. De calibre boek editor kan ook reguliere expressies gebruiken in zijn Zoek en vervang functie. Tenslotte kan u reguliere expressies gebruiken bij doorzoeken van de calibre boekenlijst en bij zoeken in de calibre e-boekviewer.

Wat is een reguliere expressie eigenlijk?

Een reguliere expressie is een manier om een string set te beschrijven. Een enkele reguliere expressie kan overeenkomen met een aantal verschillende strings. Dit maakt reguliere expressie zo krachtig – het is een beknopte manier om een mogelijk groot aantal variaties te beschrijven.

Notitie

Ik gebruik string hier op de manier dat het gebruikt wordt in programmeertalen: een reeks van één of meerdere karakters, inbegrepen letters, cijfers, interpunctie en zogenaamde witruimte (regeleindes, tabs, enz.). Merk a.u.b. op dat in het algemeen hoofd- en kleine letters niet als identiek beschouwd worden, “a” is dus iets anders als “A” en zo verder. In calibre zijn reguliere expressies hoofdletterongevoelig in de Zoekbalk maar niet in de conversie-opties. Er is een manier om elke reguliere expressie hoofdletterongevoelig te maken maar dat bespreken we later. Het wordt gecompliceerd omdat reguliere expressies variaties toelaten in de strings waar ze mee overeen komen, dus een expressie kan met verschillende strings overeen komen, reden dat ze gebruikt worden. Meer daarover later.

Iets meer uitleg graag?

Wel, daarom zijn we hier. Ten eerste, dit is het belangrijkste concept in reguliere expressies: Een string is zelf een reguliere expressie die met zichzelf overeenkomt. Dat wil zeggen, als ik met de string "Hello, World!" wou overeenkomen met een reguliere expressie, de te gebruiken reguliere expressie zou Hello, World! zijn. Ja, het is echt zo simpel. U zal nochtans opmerken dat dit enkel overeenkomt met de exacte string "Hello, World!", niet met bv. "Hello, wOrld!" of "hello, world!" of elke andere variatie.

Dat klinkt niet heel moeilijk. Ga door.

Nu komt het echt leuke gedeelte. Herinnert u zich dat ik zei dat reguliere expressies met meerdere strings kunnen overeenkomen? Nu wordt het een beetje ingewikkelder. Neem, als een iets meer praktische oefening, dat het e-boek dat u wou converteren een gemene voettekst had om de pagina’s te tellen, zoals “Pagina 5 van 423”. Duidelijk dat het pagina nummer zal stijgen van 1 tot 423, dus u zou met 423 verschillende strings moeten overeenkomen, klopt? Fout, eigenlijk: reguliere expressies laten u karaktersets definiëren die overeenkomen: Om een set te definiëren, zet u alle karakters die u in de set wilt tussen vierkante haken. Dus, bv., de set [abc] komt overeen met ofwel karakter “a”, “b” of “c”. Sets komen altijd maar overeen met een van de karakters in de set. Ze “begrijpen” karakterbereiken, dat wil zeggen, als u met alle kleine letters wil overeenkomen, gebruikt u de set [a-z], voor hoofd- en kleine letters gebruikt u [a-zA-Z] en zo voort. Bent u mee? Dus, duidelijk, gebruik makend van de expressie Page [0-9] of 423 komt u overeen met de eerste 9 pagina’s, daarbij de vereiste expressies tot drie reducerend: De tweede expressie Page [0-9][0-9] of 423  komt overeen met alle tweecijferige paginanummers en ik ben er zeker van dat u kan raden hoe de derde expressie eruit zou zien. Ja, vooruit, schrijf het op.

Prachtig! Nu begint het ergens op te lijken!

Ik hoopte al dat u dat zou zeggen. Maar hou u vast, het wordt nog beter! We zagen net dat met sets we konden zoeken naar één of meerdere karakters ineens. Maar men kan zelfs een karakter of set herhalen en zo het aantal expressies nodig voor het paginanummer voorbeeld boven verminderen tot één. Ja, ÉÉN! Spannend? Absoluut! Zo werkt het: Bepaalde zogenaamde speciale karakters, “+”, “?” en “*”, herhalen het voorafgaande element. (Element betekent ofwel een enkel karakter, ofwel een karakterset, ofwel een escape sequentie ofwel een groep (we leren later meer over die twee laatste)- kortom, elke eenheid in een reguliere expressie). Deze karakters heten wildcards of quantifiers. Nauwkeuriger, “?” komt overeen met 0 of 1 van het voorafgaande element, “*” komt overeen met 0 of meer van het voorafgaande element en “+” komt overeen met 1 of meer van het voorafgaande element. Een paar voorbeelden: De expressie a? komt overeen met ofwel “” (de lege string, niet echt bruikbaar in dit geval) ofwel “a”, de expressie a* komt overeen met “”, “a”, “aa” of elk aantal a’s op een rij, en, tenslotte, de expressie a+ komt overeen met “a”, “aa” of elk aantal a’s op een rij (Merk op: het komt niet overeen met de lege string!). Zelfde geval met sets: De expressie [0-9]+ komt overeen met elk bestaand geheel getal! Ik weet wat u denkt en u hebt gelijk: Als u dat in het geval boven met overeenkomende pagina nummers gebruikt, zou dat niet die enkele expressie zijn die met alle paginanummers overeen komt? Ja, de expressie Page [0-9]+ of 423 komt overeen met elk paginanummer in dat boek!

Notitie

Iets over die quantifiers: Ze proberen gewoonlijk met zoveel mogelijk tekst overeen te komen, voorzichtig gebruiken dus. Dit wordt “gulzig gedrag” genoemd - Ik ben er zeker van dat u dat snapt. Het wordt problematisch als u, bv. probeert met een tag overeen te komen. Beschouw bv. de string "<p class="calibre2">Title here</p>" en dat u met de openingstag wilt overeenkomen (het deel tussen het eerste paar punthaakjes, meer over tags later). Men zou denken dat de expressie <p.*> dat doet maar eigenlijk komt dat overeen met de hele string! (Het karakter “.” is nog een speciaal karakter dat met alles overeenkomt behalve een nieuwe ijn, dus de expressie .* komt overeen met elke regel die u zich kan inbeelden). I.p.d.v. gebruik <p.*?> wat de quantifier "*" niet-gulzig maakt. Die expressie komt enkel overeen met de openingstag, zoals bedoeld. Er is nog een manier om dit te bereiken: De expressie <p[^>]*> komt met dezelfde tag overeen - u ziet waarom na het volgende onderdeel. Merk gewoon op dat er dikwijls meerdere manieren zijn om een reguliere expressie te schrijven.

Nou, deze speciale karakters zijn erg netjes en zo maar wat als ik een overeenkomst met een punt of vraagteken wil?

U kan natuurlijk dit doen: Zet gewoon een backslash voor elk speciaal karakter en het wordt geïnterpreteerd als letterlijk dat karakter, zonder speciale betekenis. Dit paar backslashes gevolgd door een enkel karakter wordt een escape sequentie genoemd en een backslash voor een speciaal karakter zetten heet dat karakter escapen. Een escape sequentie wordt geïnterpreteerd als een enkel element. Er zijn natuurlijk escape sequenties die meer doen dan speciale karakters escapen, "\t" bv. betekent een tabulator. We zien later enkele escape sequenties. Oh, enne, wat betreft die speciale karakters: Beschouw elk karakter dat we bespreken in deze introductie als hebbende een zekere functie die speciaal kan zijn en dus een nood om te escapen als u het letterlijke karakter wilt hebben.

Wat zijn dan de meest nuttige sets?

Ik wist dat u het zou vragen. Enkele nuttige sets zijn [0-9] komt overeen met een enkel getal, [a-z] met een enkele kleine letter, [A-Z] met een enkele hoofdletter, [a-zA-Z] met een enkele letter en [a-zA-Z0-9] komt overeen met een enkele letter of cijfer.

\d

is gelijkwaardig aan [0-9]

\w

is gelijkwaardig aan [a-zA-Z0-9_]

\s

is gelijkwaardig aan elke spatie

Notitie

“Witruimte” is een term voor alles dat niet afgedrukt wordt. Deze karakters zijn onder andere spatie, tab, regelinvoerteken (LF), paginainvoerteken (FF), regeleinde (CR), niet-afbrekende spaties, enz.

Notitie

De hoofdletter- en kleinelettersets kunnen overeenkomen met zowel hoofdletters als kleine letters als de instelling om zoekopdrachten hoofdletterongevoelig te maken is ingeschakeld. Dergelijke instellingen zijn bijvoorbeeld te vinden in Voorkeuren->Zoeken in Calibre zelf en op het zoekpaneel in de calibre E-book viewer evenals de calibre Edit book tool.

Al laatste woord over sets, u kan een set ook definiëren als elk karakter behalve die in de set. U doet dat door het karakter "^" als allereerste karakter in de set te zetten. Dus, [^a] komt overeen met elk karakter behalve “a”. Dat heet de set aanvullen. Die verkorte escapereeksen die we eerder zagen kunnen ook aangevuld worden: "\D" betekent elk niet-numeriek karakter, equivalent dus met [^0-9]. De andere verkortingen kunnen aangevuld worden door, u raadde het, gebruik van de respectieve hoofdletter i.p.v. de kleine letter. Dus, terug naar het voorbeeld <p[^>]*> van de vorige sectie, u ziet nu dat de gebruikte karakterset probeert overeen te komen met elk karakter behalve een sluitend vierkant haakje.

Maar als ik een paar verschillende strings had die ik wilde laten overeenkomen worden dingen ingewikkeld?

Vrees niet, ‘t leven is nog altijd eenvoudig en goed. Overweeg dit voorbeeld: In het boek dat converteert staat “Titel” op elke oneven pagina en “Auteur” op elke even pagina. Ziet er goed uit in druk, niet? Maar in e-boeken is het vervelend. U kan hele expressies groeperen tussen aanhalingstekens en het karakter "|" laat overeenkomen met ofwel de expressie rechts ofwel de expressie links. Combineer deze en u bent klaar. Te snel voor u? Oké, om te beginnen groeperen we de expressies voor even en oneven pagina’s en krijgen (Title)(Author) als de twee benodigde expressies. Nu maken we het simpeler met gebruik van het pijp symbool ("|" wordt het pijp of sluissymbool genoemd): Als u de expressie (Title|Author) gebruikt krijgt u ofwel een overeenkomst voor “Titel” (op de oneven pagina’s) ofwel “Auteur” (op de even pagina’s). Wel, was dat niet gemakkelijk?

U kan natuurlijk het pijp symbool ook zonder groeperende haakjes gebruiken. Weet u nog dat ik zei dat quantifiers het element voor zich herhalen? Wel, het pijp symbool werkt enigszins anders: De expressie “Titel|Auteur” komt ook overeen met ofwel de string “Titel” ofwel de string “Auteur”, net zoals het voorbeeld boven met groeperen. Het pijp symbool selecteert tussen de volledige expressie ervoor en erna. Dus als u met de strings “Calibre” en “calibre” wou overeenkomen en enkel wou selecteren tussen de hoofd- en kleine letter “c”, moet u de expressie (c|C)alibre gebruiken, waar de groepering ervoor zorgt dat enkel de “c” wordt geselecteerd. Als u c|Calibre zou gebruiken, zou u een overeenkomst krijgen op de string “c” of op de string “Calibre”, wat niet is wat we wilden. Samengevat: Bij twijfel, gebruik groeperen met het pijp symbool.

U miste…

…wacht nog even, er is nog één, heel tof ding dat u kan doen met groepen. Als u een groep hebt die vroeger overeenkwam, kan u verder in de expressie verwijzingen naar die groep gebruiken: Groepen worden genummerd vanaf 1, en u verwijst ernaar door het getal van de groep waarnaar u wilt verwijzen te escapen, dus naar de vijfde groep wordt verwezen met \5. Dus, als u zocht naar ([^ ]+) \1 in de string “Test Test”, komt dat overeen met de hele string!

In het begin zei u dat er een manier was om een reguliere expressie hoofdletterongevoelig te maken?

Inderdaad, bedankt voor uw aandacht en om me eraan te herinneren. U kan calibre zeggen hoe u bepaalde dingen gedaan wilt hebben met vlaggen. U voegt vlaggen in in uw expressie via de speciale constructie (?vlaggen hier) waar u, natuurlijk, “vlaggen hier” vervangt door de specifieke vlag die u wilt. Om hoofdlettergebruik te negeren is de vlag i dus u voegt (?i) in in uw expressie. Dus, (?i)test komt overeen met “Test”, “tEst”, “TEst” en elke hoofd/kleine letter variatie ie u kunt bedenken.

Een andere nuttige vlag laat het punt overeenkomen met eender welk karakter, inbegrepen de newline, de vlag s. Als u meerdere vlaggen wil gebruiken in een expressie, zet ze gewoon in dezelfde verklaring: (?is) negeert hoofdlettergebruik en laat het punt met alles overeenkomen. Het maakt niet uit welke vlag u eerst zet, (?si) is gelijk aan het bovenstaande.

Ik denk dat ik deze reguliere expressies begin te begrijpen nu … hoe gebruik ik ze in calibre?

Omzettingen

Laten we beginnen met de conversie instellingen, wat echt handig is. In het Zoeken & vervangen deel kan u een regexp (afkorting voor reguliere expressie) ingeven die de string beschrijft die vervangen wordt tijdens de conversie. Het handige deel is de wizard. Klik op de toverstaf en u krijgt een voorbeeld van wat calibre “ziet” tijdens de conversie. Scrol naar de string die u wilt verwijderen, selecteer en kopieer hem, plak helm in het regexp veld boven in het venster. Als er variabele delen zijn, zoals pagina nummers of zo, gebruik daarvoor sets en quantifiers en terwijl u bezig bent, denk eraan speciale karakters te escapen, als er zijn. Klik op de Test knop en calibre accentueert de delen die het zou vervangen als u de regexp zou gebruiken. Wanneer u tevreden bent, klik op OK en converteer. Weer voorzichtig als uw conversie bron tags heeft zoals deze:

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

(schaamteloos geripped uit deze thread [https://www.mobileread.com/forums/showthread.php?t=75594"]). U moet enkele tags verwijderen ook. In dit voorbeeld raad ik aan te beginnen met de tag <b class="calibre2">, nu moet u eindigen met de overeenkomende sluittag (openingstags zijn <tag>, sluittags zijn </tag>), welke gewoon de volgende </b> in dit geval. (Gebruik een goede HTML manual of vraag in het forum als u hierover twijfelt). De openingstag kan beschreven worden met <b.*?>, de sluittag met </b>, dus we kunnen alles tussen deze tags verwijderen met <b.*?>.*?</b>. Maar deze expressie gebruiken is een slecht idee want ze verwijdert alles omsloten door <b> tags (die, by the way, de omsloten tekst vet weergeeft), en waarschijnlijk verwijderen we delen va het boek op deze manier. In plaats daarvan, geef ook het begin van de omsloten string mee, de reguliere expressie wordt dan <b.*?>\s*Generated\s+by\s+ABC\s+Amber\s+LIT.*?</b> De \s met quantifiers is inbegrepen hier in plaats van expliciet gebruik van de spaties zoals in de string om eventuele variaties van de string op te vangen. Denk eraan te controleren wat calibre gaat verwijderen zodat u zeker bent dat u geen delen verwijdert die u wilt behouden als u een nieuwe expressie test. Als u maar één voorkomen test, kan u ergens anders in de tekst een mismatch over het hoofd zien. Merk ook op dat als per ongeluk meer of minder tags verwijdert dan u eigenlijk wou dat calibre de beschadigde code probeert te herstellen na het verwijderen.

Boeken toevoegen

Iets anders waar u reguliere expressies voor kan gebruiken is metadata uit bestandsnamen halen. U vindt deze functie in “Boeken toevoegen” in de instellingen. Er is hier een speciale functie: U kan veldnamen gebruiken voor metadata velden, bv. (?P<title>) geeft aan dat calibre dit deel van de string gebruikt als boektitel. De toegelaten veldnamen zijn opgelijst in de vensters, samen met een ander leuk testveld. Bv. Stel u wilt een hoop bestanden importeren genaamd zoals Classical Texts: The Divine Comedy by Dante Alighieri.mobi. (Dit is natuurlijk al in uw bibliotheek, we houden allemaal van klassieke Italiaanse poëzie) of Science Fiction epics: The Foundation Trilogy by Isaac Asimov.epub. Dit is duidelijk een naamgevingsschema waar calibre geen nuttige data kan uithalen - z’n standaard expressie om metadata te extraheren is (?P2.+) - (?P3[^_]+). Een reguliere expressie die hier werkt zou [a-zA-Z]+: (?P4.+) by (?P5.+) zijn. Merk a.u.b. op dat, in de groep voor het metadata veld, u expressies moet gebruiken die beschrijven waar het veld met overeenkomt. En merk ook op dat bij gebruik van het door calibre voorziene testveld, u de extensie moet toevoegen aan uw testbestandsnaam of u krijgt helemaal geen overeenkomsten, ook niet met een werkende expressie.

Massabewerking van metadata

Het laatste deel is reguliere expressie Zoeken en vervangen in metadatavelden. U kunt hier toegang toe krijgen door meerdere boeken in de bibliotheek te selecteren en metadata bulksgewijs te bewerken. Wees erg voorzichtig bij het gebruik van deze laatste functie, want het kan Very Bad Things met je bibliotheek doen! Controleer nogmaals of uw uitdrukkingen doen wat u wilt met behulp van de testvelden en markeer alleen de boeken die u echt wilt wijzigen! In de zoekmodus voor reguliere expressies kunt u in één veld zoeken, de tekst door iets vervangen en zelfs het resultaat in een ander veld schrijven. Een praktisch voorbeeld: stel dat uw bibliotheek de boeken van de Dune-serie van Frank Herbert bevatte, genoemd naar de mode Dune 1 - Dune, Dune 2 - Dune Messiah enzovoort. Nu wil je Dune in het serieveld krijgen. U kunt dat doen door te zoeken naar (.*?) \d+ - .* in het titelveld en dit te vervangen door \1 in het reeksveld. Zie wat ik daar heb gedaan? Dat is een verwijzing naar de eerste groep waarmee u het serieveld vervangt. Nu je de serie helemaal klaar hebt, hoef je alleen maar nog een keer te zoeken naar .*? - in het titelveld en vervang het door "" (een lege string), nogmaals in het titelveld, en je metadata zijn allemaal netjes en opgeruimd. Is dat niet geweldig? Trouwens, in plaats van het hele veld te vervangen, kun je ook toevoegen of toevoegen aan het veld, dus als je wilde dat de boektitel wordt voorafgegaan door serie-informatie, dan zou je dat ook kunnen doen. Zoals je inmiddels ongetwijfeld hebt opgemerkt, is er een selectievakje met het label Hoofdlettergevoelig, dus je hoeft hier geen vlaggen te gebruiken om gedrag te selecteren.

Wel, tot zover de zeer korte inleiding tot reguliere expressies. Hopelijk heb ik u genoeg laten zien om u tenminste te laten beginnen en het mogelijk te maken om zelfstandig verder te leren - een goed vertrekpunt is de Python documentatie voor reguliere expressies [https://docs.python.org/library/re.html].

Toch een laatste verwittiging: Reguliere expressies zijn krachtig maar ook foutgevoelig. calibre voorziet echt super testmogelijkheden om te zien of uw expressies doen wat u verwacht van hen. Gebruik ze. Probeer uzelf niet in de voet te schieten. (Dzjee, wat een prachtige uitdrukking…). Doet u het toch ondanks de waarschuwing (of in een ander lichaamsdeel), probeer ervan te leren.

Snelle referentie

- Quick reference voor regexp syntax

- Karakter klassen
- Steno tekenklassen
- De quantifiers
- Hebzucht
- Afwisseling
- Uitsluiting
- Ankers
- Groepen
- Lookarounds
- Recursie
- Speciale karakters
- Meta-karakters
- Modes

Credits

Dank voor hulp met tips, correcties en dergelijke:

- ldolse

- kovidgoyal

- chaley

- dwanthny

- kacir

- Starson17

- Orpheu

Voor meer over reguliere expressies, bekijk The Python User Manual [https://docs.python.org/library/re.html]. De actuele reguliere expressie bibliotheek gebruikt door calibre is: regex [https://bitbucket.org/mrabarnett/mrab-regex/src/hg/] welke verscheidene nuttige verbeteringen ondersteunt bovenop de Python standaard bibliotheek.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Lesboeken »

- Alles over het gebruik van reguliere expressies in calibre »

- Quick reference voor regexp syntax

Quick reference voor regexp syntax

Deze checklist vat de meest gebruikte/moeilijk te onthouden delen van de regexp engine, beschikbaar in de meeste delen van calibre, samen.

Inhoud

- Karakter klassen

- Steno tekenklassen

- De quantifiers

- Hebzucht

- Afwisseling

- Uitsluiting

- Ankers

- Groepen

- Lookarounds

- Recursie

- Speciale karakters

- Meta-karakters

- Modes

Karakter klassen

Tekenklassen zijn handig om verschillende groepen karakters samen te stellen, kort en bondig.

Voorbeelden:

Vertegenwoordiging

Klasse

[a-z]

Kleine letters. Bevat geen tekens met accentmarkering en ligaturen

[a-z0-9]

Kleine letters van a tot z of cijfers van 0 tot 9

[A-Za-z-]

Hoofdletters of kleine letters, of een streepje. Om het streepje in een klasse op te nemen, moet u het aan het begin of aan het einde plaatsen om het niet te verwarren met het koppelteken dat een reeks tekens specificeert

[^0-9]

Elk teken behalve een cijfer. Het caret (^) aan het begin van de klasse sluit de karakters van de klasse uit (gecomplementeerde klasse)

[[a-z]--[aeiouy]]

De medeklinkers in kleine letters. Een klas kan in een klas worden opgenomen. De karakters -- sluiten uit wat hen volgt

[\w--[\d_]]

Alle letters (inclusief tekens met buitenlandse accenten). Afgekorte klassen kunnen binnen een klas worden gebruikt

Voorbeeld:

<[^<>]+> to select an HTML tag

Steno tekenklassen

Vertegenwoordiging

Klasse

\d

Een cijfer (hetzelfde als [0-9])

\D

Elk niet-numeriek karakter (hetzelfde als [^0-9])

\w

Een alfanumeriek karakter ([a-zA-Z0-9]) inbegrepen karakters met accent en ligatuur

\W

Elk “niet-woord”karakter

\s

Spatie, harde spatie, tab, nieuweregelteken

\S

Elk “niet blanco ruimte” karakter

.

Elk karakter uitgezonderd nieuwe regel. Gebruik het “dot all” aankruisvakje of de (?s) regexp modifier om het nieuwe regel karakter mee te tellen.

De quantifiers

Quantifier

Aantal keren dat de uitdrukking voorkomt voor de quantifier

?

0 of 1 keer voorkomen van de uitdrukking. Hetzelfde als {0,1}

+

1 of meer keer voorkomen van de uitdrukking. Hetzelfde als {1,}

*

0, 1 of meer keer voorkomen van de uitdrukking. Hetzelfde als {0,}

{n}

Exact n keer voorkomen van de uitdrukking

{min,max}

Aantal keer voorkomen tussen de minimum en maximum waarden inbegrepen

{min,}

Aantal keren voorkomen tussen de minimum waarde inbegrepen en oneindig

{,max}

Aantal keren voorkomen tussen 0 en de maximum waarde inbegrepen

Hebzucht

Standaard, met quantifiers, is de regular expression engine hebzuchtig: hij breidt de selectie zo ver mogelijk uit. Dit geeft dikwijls verassingen, in het begin. ? volgt een quantifier om hem lui te maken. Vermijd twee ervan in dezelfde uitdrukking, het resultaat kan onvoorspelbaar zijn.

Let op met het nesten van quantifiers, bv het patroon (a*)*, omdat het de verwerkingstijd exponentieel verhoogt.

Afwisseling

Het | karakter in een regular expression is een logische OR. Het betekent dat ofwel de voorgaande ofwel de navolgende uitdrukking kan overeenkomen.

Uitsluiting

Methode 1
pattern_to_exclude(*SKIP)(*FAIL)|pattern_to_select

Voorbeeld:

"Blabla"(*SKIP)(*FAIL)|Blabla
selecteert Blabla, in de strings Blabla of “Blabla of Blabla”, maar niet in “Blabla”.

Methode 2
pattern_to_exclude\K|(pattern_to_select)

"Blabla"\K|(Blabla)

selecteert Blabla, in de strings Blabla of “Blabla of Blabla”, maar niet in “Blabla”.

Ankers

Een anker is een manier om een logische locatie in een string te matchen, eerder dan een karakter. De nuttigste ankers voor tekstverwerking zijn:

\b

Geeft een woordgrens aan, bv een overgang van een spatie naar een niet blanco ruimte karakter. U kan bv \bsurd gebruiken om overeen te komen met the surd maar niet absurd.

^

Komt overeen met het begin van een regel (in multi-regel modus, wat standaard is)

$

Komt overeen met het einde van een regel (in multi-regel modus, wat standaard is)

\K

Reset de start positie van de selectie naar z’n positie in het patroon. Sommige regex engines (maar niet calibre) laten geen lookbehind of variabele lengte toe, zeker met quantifiers. Als u \K kan gebruiken bij deze engines, laat het u ook toe om van deze beperking vanaf te raken door het equivalent van een positieve lookbehind van variabele lengte te schrijven.

Groepen

(expression)

Vang groep, die de selectie bewaart en later terug opgeroepen kan worden in de search or replace patronen met \n, waar n het volgnummer is van de vang groep (beginnend bij 1 in leesvolgorde)

(?:expression)

Groep die de selectie niet vangt

(?>expression)

Atomic Group: Zo gauw aan de uitdrukking voldaan is, geeft de regexp engine door, en als de rest van het patroon faalt, zal hij niet terug gaan om andere combinaties met de uitdrukking te proberen. Atomic groepen vangen niet.

(?|expression)

Branch reset groep: de branches van de alternaties inbegrepen in de uitdrukking delen dezelfde groep nummers

(?<name>expression)

Groep “naam” genoemd. De selectie kan later terug opgeroepen worden in het zoek patroon door (?P=name) en in de vervang door \g<name>. Twee verschillende groepen kunnen dezelfde naam gebruiken.

Lookarounds

Lookaround

Meaning

?=

Positieve lookahead (na de selectie te plaatsen)

?!

Negatieve lookahead (na de selectie te plaatsen)

?<=

Positieve lookbehind (voor de selectie te plaatsen)

?<!

Negatieve lookbehind (voor de selectie te plaatsen)

Lookaheads en lookbehinds verbruiken geen karakters, ze hebben zero lengte en vangen niet. Het zijn atomic groepen: zo gauw aan de stelling voldaan is, geeft de regexp engine door, en als de rest van het patroon faalt, zal hij niet terug gaan in de lookaround om andere combinaties te proberen.

Bij het zoeken naar meerdere overeenkomsten in een string, kan een lookbehind, bij de startpositie van elke poging, de karakters inspecteren voor de huidige positie. Dus, in de string 123, het patroon (?<=\d)\d (een cijfer voorafgegaan door een cijfer) zou, in theorie, 2 en 3 moeten kiezen. Anderzijds, \d\K\d kan enkel 2 kiezen omdat de startpositie na de eerste selectie onmiddellijk voor 3 is en er niet genoeg cijfers zijn voor een tweede overeenkomst. Gelijkaardig, \d(\d) vangt enkel 2. In calibre’s regexp engine praktijk gedraagt de positieve lookbehind zich op dezelfde manier en kiest enkel 2, in tegenstelling tot de theorie.

Groepen kunnen in lookarounds geplaatst worden, maar vangen is zelden bruikbaar. Niettegenstaande, als het bruikbaar is, is het nodig om zeer zorgvuldig te zijn in het gebruik van een quantifier in een lookbehind: de hebzucht die komt met de afwezigheid van backtracking kan een verrassende vangst geven. Hierom, gebruik eerder \K dan een positieve lookbehind als je een quantifier hebt (of erger, meerdere) in een vang groep van de positieve lookbehind.

Voorbeeld van een negatieve lookahead:

(?![^<>{}]*[>}])

Voorkomt het selecteren binnenin een tag of stijl in een bestand indien aan het einde van het patroon geplaatst.

Wanneer mogelijk, is het altijd beter om de lookarounds the ‘ankeren”, om het aantal stappen om het resultaat te verkrijgen, te verkleinen.

Recursie

Vertegenwoordiging

Meaning

(?R)

Recursie van het gehele patroon

(?1)

Recursie van het enige patroon van de genummerde vang groep, hier groep 1

Recursie is zichzelf roepen. Dit is nuttig voor gebalanceerde queries, zoals quoted strings, die binnenin quoted strings kunnen bevatten. Dus, als tijdens het verwerken van een string tussen dubbele aanhalingstekens, we het begin van een andere string tussen dubbele aanhalingstekens tegen komen, weten we wat te doen en we roepen onszelf. Dan hebben we een patroon zoals:

start-pattern(?>atomic sub-pattern|(?R))*end-pattern

Om een string te selecteren tussen dubbele aanhalingstekens zonder te stoppen voor een string daar binnenin:

“((?>[^“”]+|(?R))*[^“”]+)”

Deze template kan ook gebruikt worden om tag paren te wijzigen die ergens binnenin staan, zoals <div> tags.

Speciale karakters

Vertegenwoordiging

Character

\t

tabulation

\n

line break

\x20

(zachte) spatie

\xa0

harde spatie

Meta-karakters

Meta-karakters zijn diegene die een speciale betekenis hebben voor de regexp engine. Van deze moeten er 12 voorafgegaan worden door een escape karakter, de backslash (\), om hun speciale betekenis te verliezen en terug een gewoon karakter te worden:

^ . [ ] $ ( ) * + ? | \

Zeven andere meta-karakters moeten niet door een backslash vooraf gegaan worden (maar het kan zonder gevolgen):

{ } ! < > = :

Speciale karakters verliezen hun status als ze in een class gebruikt worden (tussen haakjes []). Het sluitende haakje en het streepje hebben een speciale betekenis in een class. Buiten de class is het streepje gewoon een streepje, het sluitend haakje blijft een meta-karakter.

De slash (/) en het hekje (of kardinaalteken) (#) zijn geen meta-karakters, zij moeten niet voorafgegaan worden door een escape karakter.

In sommige tools, zoals regex101.com in de Python engine, hebben dubbele aanhalingstekens de speciale status van scheidingsteken, en moeten voorafgegaan worden door een escape teken, of de opties moeten aangepast worden. Dit is niet het geval in de calibre editor.

Modes

(?s)

Zorgt ervoor dat het punt (.) ook met nieuwe lijn karakters overeenkomt

(?m)

Laat de ^ en $ ankers overeenkomen met het begin en einde van regels ipv het begin en einde van de hele string.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Lesboeken »

- Uw eigen plugins schrijven om de functionaliteit van calibre uit te breiden

Uw eigen plugins schrijven om de functionaliteit van calibre uit te breiden
calibre heeft een zeer modulair ontwerp. Bijna alle functionaliteit in calibre komt als plug-ins. Plug-ins voor conversie, downloaden van nieuws (recepten genaamd), verschillende componenten van de gebruikersinterface, verbinding maken met verschillende toestellen, bestanden verwerken bij toevoegen aan calibre enz. U vidnt een volledige lijst van ingebouwde plug-ins in calibre op: guilabel: `Voorkeuren-> Geavanceerd-> Plug-ins ‘te gaan.

Hier laten we zien hoe u uw eigen plugins kunt maken om nieuwe functies toe te voegen aan calibre.

Inhoud

- Opbouw van een calibre plugin

- Een Gebruikersinterface plugin

- __init__.py

- ui.py

- main.py

- Ophalen bronnen uit het plug-in ZIP bestand

- Gebruikers-configuratie van uw plugin inschakelen

- Boek plug-ins bewerken

- main.py

- Vertalingen toevoegen aan uw plugin

- De plug-in API

- Plug-ins foutopsporing

- Meer voorbeelden van plugins

- Uw plugins delen met anderen

Notitie

Dit is enkel van toepassing op calibre releases >= 0.8.60

Opbouw van een calibre plugin

Een calibre plug-in is heel eenvoudig, gewoon een ZIP-bestand dat wat Python code bevat en andere bronnen zoals afbeelding die de plug-in nodig heeft. Genoeg, laten we een eenvoudig voorbeeld bekijken.

Stel, u heeft calibre geïnstalleerd om diverse e-documenten zelf te publiceren in EPUB en MOBI-formaten. U wilt dat alle bestanden gegenereerd door calibre als uitgever “Hello world” hebben, hier volgt hoe dit te doen. Maak een bestand genaamd __init__.py (dit is een speciale naam en moet altijd gebruikt worden voor het hoofdbestand van uw plugin) en geef de volgende Python code in:

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

Dat is alles. Om deze code als plug-in aan calibre toe te voegen, voer het volgende uit in de map waar u __init__.py:: creëerde

calibre-customize -b .

Notitie

Op macOS bevinden de commandoregelprogramma’s zich in de calibre-bundel, bijvoorbeeld, als u calibre hebt geïnstalleerd in /Applications bevinden de commandoregelprogramma’s zich in /Applications/calibre.app/Contents/MacOS/.

U kan de Hello World plugin downloaden van helloworld_plugin.zip [https://calibre-ebook.com/downloads/helloworld_plugin.zip].

Elke keer u calibre gebruikt om een boek te converteren, wordt de plugin’s run() methode geroepen en het geconverteerde boek heeft als uitgever “Hello World”. Dit is een banale plugin, op naar een ingewikkelder voorbeeld dat daadwerkelijk een component toevoegt aan de gebruikersinterface.

Een Gebruikersinterface plugin

Deze plug-in zal bestaan uit een paar bestanden (om de code schoon te houden). Het toont u hoe bronnen (afbeeldingen of databestanden) op te halen uit de plug-in’s ZIP , gebruikers toe te staan uw plug-in in te stellen, elementen in de gebruikersinterface voor calibre te maken en de database voor boeken te raadplegen en doorzoeken.

U kan deze plugin downloaden van interface_demo_plugin.zip [https://calibre-ebook.com/downloads/interface_demo_plugin.zip]

Het eerste ding om op te merken is dat dit ZIP bestand veel meer bestanden bevat, onder uitgelegd, let extra op plugin-import-name-interface_demo.txt.

plugin-import-name-interface_demo.txt

Een leeg tekstbestand om de multi-file plugin magie in te schakelen. Dit bestand moet aanwezig zijn in alle plug-ins die meer dan één .py bestand gebruiken. Het moet leeg zijn en de bestandsnaam moet volgende vorm hebben: `` plugin-import-name-**een_naam **.txt``. Met dit bestand kunt u code importeren uit de .py bestanden in het ZIP-bestand , met behulp van een statement als:

from calibre_plugins.some_name.some_module import some_object

De prefix calibre_plugins moet altijd aanwezig zijn. some_name komt van de naam van het lege tekst bestand. some_module verwijst naar some_module.py in het ZIP bestand. Merk op dat deze import even machtig is als gewone Python imports. U kan packages en subpackages van .py modules creëren in het ZIP bestand, net zoals u normaal zou doen (door __init__.py te definiëren in elke submap) en alles zou “moeten werken”.

De naam die u gebruikt voor some_name gaat in een algemene naamruimte, gedeeld door alle plug-ins, dus maak deze zo uniek mogelijk. Maar vergeet niet dat het een geldig Python-id moet zijn (alleen alfabetten, cijfers en het onderliggend streepje).

__init__.py

Zoals hiervoor, het bestand dat de plugin’s klasse definieert

main.py

Dit bestand bevat de daadwerkelijke code die iets nuttigs doet

ui.py

Dit bestand definieert de interface van de plugin

images/icon.png

Het icoon voor deze plugin

about.txt

Een tekstbestand met informatie over de plugin

vertalingen

Een map die .mo-bestanden bevat met vertalingen van de gebruikersinterface van uw plug-in in verschillende talen. Zie onder voor details.

Laten we naar de code kijken.

__init__.py

Eerst de verplichte __init__.py om de plug-in’s metadata te definiëren:

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()

De enige noemenswaardige eigenschap is het veld: attr:actual_plugin. Omdat calibre zowel opdrachtregel als GUI interfaces heeft, mogen GUI plug-ins zoals deze geen enkele GUI-bibliotheek laden in __init__.py . Het veld actual_plugin doet dit voor u, door calibre te zeggen dat de daadwerkelijke plugin in een ander bestand in uw ZIP archief te vinden is, dat alleen in een GUI context wordt geladen.

Vergeet niet dat om dit te laten werken, u een plugin-import-name-een_naam.txt bestand nodig hebt in uw ZIP plug-in, zoals boven besproken.

Er zijn meerdere methoden om gebruikersconfiguratie van de plug-in mogelijk te maken. Deze worden onder besproken.

ui.py

Laten we nu ui.py bekijken dat de eigenlijke GUI plug-in definieert. De broncode is voorzien van commentaar en zou zichzelf moeten verklaren:

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

De eigenlijke logica om de Interface Plugin Demo dialoog te implementeren.

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'])

Ophalen bronnen uit het plug-in ZIP bestand
calibres plug-in laadsysteem definieert een aantal ingebouwde functies waarmee u gemakkelijk bestanden kunt ophalen uit het plug-in ZIP bestand.

get_resources(name_or_list_of_names)

This function should be called with a list of paths to files inside the ZIP file. For example to access the file icon.png in the folder images in the ZIP file, you would use: images/icon.png. Always use a forward slash as the path separator, even on Windows. When you pass in a single name, the function will return the raw bytes of that file or None if the name was not found in the ZIP file. If you pass in more than one name then it returns a dictionary mapping the names to bytes. If a name is not found, it will not be present in the returned dictionary.

get_icons(name_or_list_of_names, plugin_name=’’)

Een wrapper voor get_resources() die QIcon objecten creëert van de rauwe bytes vanuit get_resources. Werd een naam niet gevonden in het ZIP bestand, zal de overeenkomende QIcon null zijn. Om icoon theme-ing te ondersteunen, geef de mensvriendelijke naam van uw plug-in in als plugin_name. Als de gebruiker een icoonthema gebruikt met iconen voor uw plug-in, worden deze bij voorkeur geladen .

Gebruikers-configuratie van uw plugin inschakelen

Om gebruikers uw plug-in te laten configureren, moet u drie methoden in uw basis plug-inklasse definiëren, ** is_customizable **, ** config_widget ** en ** save_settings ** zoals onder weergegeven:

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 heeft vele manieren om configuratiegegevens te bewaren (een erfenis van lange geschiedenis). De aanbevolen manier is het gebruik van de JSONConfig klasse, die de informatie bewaart in een .json bestand.

De code om configuratiegegevens te beheren in de demoplug-in is in 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()

Het prefs object is nu beschikbaar in de plug-incode met een simpele:

from calibre_plugins.interface_demo.config import prefs

U kunt het gebruik van prefs-object zien in main.py:

self.do_user_config(parent=self)
# Apply the changes
self.label.setText(prefs['hello_world_msg'])

Boek plug-ins bewerken

Laten we wat hoger schakelen en een plug-in creëren om tools toe te voegen aan de calibre boekeditor. De plug-in is hier beschikbaar: editor_demo_plugin.zip [https://calibre-ebook.com/downloads/editor_demo_plugin.zip].

De eerste stap, zoals voor alle plug-ins, is het maken van het lege import name .txt bestand zoals above beschreven. We noemen het bestand plugin-import-name-editor_plugin_demo.txt.

Nu maken we het verplichte bestand __init__.py waarin de metadata over de plug-in staan – zijn naam, maker, versie enz.

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)

Een enkel editorplug-in kan meerdere tools leveren, iedere tool correspondeert met een knop in de werkbalk en trefwoord in het Plug-ins menu in de editor. Deze kunnen sub-menu’s hebben indien de tool meerdere acties heeft.

De tools moeten allemaal gedefinieerd zijn in het bestand main.py in uw plugin. Iedere tool is een class, die erft van de calibre.gui2.tweak_book.plugin.Tool class. Laten we een kijkje nemen in main.py van de demoplug-in, de broncode is voorzien van commentaar en zou zichzelf moeten verklaren. Lees de API-documenten van de calibre.gui2.tweak_book.plugin.Tool class voor meer details.

main.py

Hier zien we de definitie van een enkele tool dat alle lettergroottes in het boek vermenigvuldigt met een getal ingesteld door de gebruiker. Deze tool demonstreert meerdere belangrijke concepten die u nodig heeft om uw eigen plug-in te ontwikkelen. Lees daarom de (goed van commentaar voorziene) broncode grondig.

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

Laten we main.py bekijken. We zien dat een enkele tool wordt gedefinieerd, Magnify fonts. Hij vraagt de gebruiker een getal en vermenigvuldigt alle lettergroottes in het boek met dit getal.

Het eerste belangrijke ding is de naam van de tool die een redelijk unieke tekenreeks moet zijn, deze wordt gebruikt als sleutel voor dit gereedschap.

Het volgende belangrijke punt is calibre.gui2.tweak_book.plugin.Tool.create_action(). Deze methode maakt de QAction objecten die op de plug-inwerkbalk en het plug-inmenu verschijnen. Optioneel wordt ook een sneltoets toegewezen die de gebruiker kan aanpassen. Het getriggerde signaal van de QAction is verbonden met de ask_user() methode die de gebruiker vraagt naar de lettergrootte vergrotingsfactor en vervolgens de vergrotingscode uitvoert.

De vergrotingscode is goed becommentarieerd en vrij eenvoudig. De belangrijkste dingen om op te merken zijn dat u een verwijzing naar het editorvenster krijgt als self.gui en de editor Boss als self.boss. De Boss is het object dat de gebruikersinterface van de editor bestuurt. Het heeft veel nuttige methoden, die zijn gedocumenteerd in de klasse calibre.gui2.tweak_book.boss.Boss.

Tenslotte is er ``self.current_container’’, een verwijzing naar het boek dat wordt bewerkt als calibre.ebooks.oeb.polish.container.Container object. Dit stelt het boek voor als een verzameling van z’n samenstellende HTML/CSS/afbeeldingsbestanden en heeft handige methoden om veel nuttige dingen te doen. Het containerobject en verschillende handige hulpprogramma’s die kunnen worden herbruikt in uw plug-incode zijn gedocumenteerd in :ref: polish_api.

Vertalingen toevoegen aan uw plugin

U kunt de teksten in het gebruikersinterface in uw plugin laten vertalen in elke taal die is ingesteld voor de calibre gebruikersinterface.

De eerste stap is door uw plug-in’s broncodes te gaan en alle voor gebruikers zichtbare teksten te markeren als vertaalbaar door ze te omgeven met _(). Bijvoorbeeld:

action_spec = (_('My plugin'), None, _('My plugin is cool'), None)

Gebruik dan een programma om .po bestanden te genereren uit uw plug-in’s broncode . Er moet één .po-bestand zijn voor elke taal waarin u wilt vertalen. Bijvoorbeeld: nl.po voor Nederlands, fr.po voor Frans enzovoort. U kunt hiervoor het `Poedit <https://poedit.net/>`_program gebruiken.

Stuur deze .po-bestanden naar uw vertalers. Zodra u ze terug krijgt, compileer ze in .mo-bestanden. U kunt opnieuw Poedit daarvoor gebruiken, of doe gewoon:

calibre-debug -c "from calibre.translations.msgfmt import main; main()" filename.po

Plaats de .mo-bestanden in de vertalingen-map in uw plug-in.

De laatste stap is om gewoon de functie load_translations() te roepen bovenaan uw plugin’s .py bestanden. Voor prestatie redenen deze functie alleen aanroepen in die .py-bestanden die echt vertaalbare teksten bevatten. Dus in een normale Gebruikersinterfaceplug-in roept u deze functie aan bovenin ui.py maar niet in __init__.py.

U kunt de vertalingen van uw plug-ins testen door de taal van de gebruikersinterface in calibre te wijzigen onder Voorkeuren → Interface → Uitstraling & gevoel of door calibre uit te voeren met de CALIBRE_OVERRIDE_LANG omgevingsvariabele ingesteld. Voorbeeld:

CALIBRE_OVERRIDE_LANG=de

Vervang de met de taalcode van de taal die u wilt testen.

For translations with plurals, use the ngettext() function instead of _(). For example:

ngettext('Delete a book', 'Delete {} books', num_books).format(num_books)

De plug-in API

Zoals u misschien heeft opgemerkt is een plug-in in calibre een class. Er zijn verschillende classes voor verschillende types plug-ins. Details van iedere class, inclusief de basis class van alle plugins zijn te vinden in API documentatie voor plugins.

Uw plugin zal bijna zeker code gebruiken van calibre. Om te leren hoe u diverse stukjes functionaliteit vindt in de calibre code basis, lees de sectie over calibre’s Code opmaak.

Plug-ins foutopsporing

De eerste, belangrijkste stap is calibre draaien in foutenopsporingsmodus. U kunt dit doen in de opdrachtregel met:

calibre-debug -g

Of vanuit calibre door met de rechts te klikken op de Preferences knop of gebruik te maken van de Ctrl+Shift+R sneltoets .

Wanneer u werkt met de opdrachtregel verschijnt de foutoplossing op de console, wanneer u werkt in calibre zal dit naar een .txt-bestand worden geschreven.

U kunt overal printinstructies invoegen in uw plug-incode, deze worden uitgevoerd in de foutopsporingsmodus. Vergeet niet, dit is Python, uzou echt niets meer nodig moeten hebben dan print commando’s om te debuggen ;) Ik heb heel calibre ontwikkeld met alleen deze foutopsporingstechniek.

U kunt snel wijzigingen aan uw plugin testen met behulp van de volgende opdrachtregel:

calibre-debug -s; calibre-customize -b /path/to/your/plugin/folder; calibre

Dit sluit een lopende calibre af, wacht totdat het programma geheel afgesloten is, dan uw plugin updaten in calibre en calibre herstarten.

Meer voorbeelden van plugins

U kan een lijst met gesofisticeerde calibre plug-ins vinden hier [https://www.mobileread.com/forums/showthread.php?t=118764].

Uw plugins delen met anderen

Als u de plugins die u gemaakt hebt wilt delen met andere gebruikers van calibre, post ze onder een nieuw onderwerp in calibre plugins forum [https://www.mobileread.com/forums/forumdisplay.php?f=237].

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Lesboeken »
- Wiskunde typesetten in e-boeken

Wiskunde typesetten in e-boeken

De calibre e-boek viewer kan wiskunde tonen ingebed in e-boeken (EPUB en HTML bestanden). U kan de wiskunde direct typesetten met TeX or MathML or AsciiMath.De calibre e-boek viewer gebruikt de uitstekende MathJax [https://www.mathjax.org] bibliotheek om dit te doen. Dit is een korte tutorial over het creëren van e-boeken met wiskunde erin die goed werken met de calibre e-boek viewer.

Een simpel HTML bestand met wiskunde

U kan wiskunde inline schrijven in een eenvoudig HTML bestand en de calibre e-boek viewer zal het in correct gezette wiskunde weergeven. In het voorbeeld onder gebruiken we TeX-notatie voor wiskunde. U zal zien dat u normale TeX opdrachten kan gebruiken, met het kleine voorbehoud dat en-teken en, minder dan en groter dan tekens moeten worden geschreven als &amp; &lt; en &gt; respectievelijk.

De eerste stap is calibre te vertellen dat dit wiskunde zal bevatten. U doet dit door het volgende stukje code aan de <head> sectie van het HTML bestand toe te voegen:

<script type="text/x-mathjax-config"></script>

Super, nu kunt u wiskunde typen net zoals u in een .tex file zou doen. Bijvoorbeeld, hier zijn Lorentz’s vergelijkingen:

<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>

Dit stukje code ziet eruit als de volgende schermafdruk in de calibre e-boek viewer.

The Lorenz Equations

Het volledige HTML bestand, met meer vergelijkingen en in-line wiskunde is onder weergegeven. U kan dit HTML bestand in calibre naar EPUB converteren om een e-boek te maken dat u gemakkelijk naar andere mensen kan verspreiden.

<!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>

Meer informatie

Daar de calibre e-boek viewer de MathJax bibliotheek gebruikt om wiskunde weer te geven, is de beste plaats om hulp en meer te vinden over wiskunde in e-boeken de MathJax webstek <https://www.mathjax.org>`_.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Lesboeken »
- Creëren van AZW3 • EPUB • MOBI-catalogi

Creëren van AZW3 • EPUB • MOBI-catalogi
calibre’s Maak catalogus functie laat u een catalogus van uw bibliotheek in verschillende formaten creëren. Deze documentatie beschrijft de opties bij het genereren van een catalogus in AZW3 , EPUB en MOBI formaten.

- Selecteer boeken voor de catalogus

- Inbegrepen afdelingen

- Voorvoegsels

- Uitgesloten boeken

- Uitgesloten genres

- Overige opties

- Aangepaste catalogus omslagen

- Extra hulpbronnen

Selecteer boeken voor de catalogus

Als u heel uw bibliotheek gecatalogeerd wilt, verwijder alle zoek- of filtercriteria in het hoofdvenster. Met één boek geselecteerd, zullen alle boeken in uw bibliotheek kandidaat zijn voor opname in de gegenereerde catalogus . Individuele boeken kunt u uitsluiten door verschillende criteria; zie: ref:` Uitgesloten_genres` hieronder voor meer informatie.

Als u slechts een deel van uw bibliotheek wil catalogeren, hebt u twee opties:

- Maak een selectie van de boeken die u wilt catalogeren. Met meer dan één boek geselecteerd in calibres hoofdvenster, worden alleen de geselecteerde boeken gecatalogeerd .

- Gebruik het Zoekveld of Tag Browser om de weergegeven boeken te filteren. Alleen de weergegeven boeken worden gecatalogeerd.

Om catalogus generatie te starten, selecteer menu-item Omzetten boeken > Creër catalogus van de boeken in uw calibre bibliotheek. U kunt ook een Maak catalogus knop aan een werkbalk toevoegen in Voorkeuren> Interface > Werkbalken & menu’s voor vlottere toegang tot de catalogus genereren dialoog.

In Catalog options , selecteer AZW3, EPUB en MOBI als Cataloog formaat. In het Cataloog titel vul de naam in die u wenst voor de gegenereerde catalogus . Als een catalogus van dezelfde naam en formaat bestaat, wordt deze vervangen door de nieuw gegenereerde catalogus.

Activeren van Stuur catalogus automatisch naar apparaat zal de gegenereerde catalogus downloaden naar een verbonden toestel na voltooiing.

Inbegrepen afdelingen

Aangevinkte onderdelen worden toegevoegd in de genereerde catalogus:

- Auteurs - alle boeken, gesorteerd op auteur, getoond in een lijst. Losse boeken worden getoond boven boekenreeksen.

- Titels - alle boeken, gesorteerd op titel, getoond in een lijst formaat.

- Reeksen - alle boeken uit een reeks, gesorteerd op reeks, getoond in een lijst.

- Genres - individuele genres getoond in een lijst, gesorteerd op Auteur en Reeks.

- Nieuw toegevoegd - alle boeken, gesorteerd in omgekeerde tijdsvolgorde. Lijst bevat boeken toegevoegd in de afgelopen 30 dagen, dan een maand-voor-maand overzicht van toegevoegde boeken.

- Beschrijvingen - gedetailleerde beschrijving van elk boek inclusief omslag-miniatuur en commentaren. Gesorteerd op auteur met losse boeken boven de boeken in reeksen.

Voorvoegsels

Met voorvoegsel-regels kunt u een voorvoegsel toevoegen aan boekenlijsten als aan bepaalde criteria is voldaan. Bv u wilt gelezen boeken markeren met een vinkje of boeken op uw wensenlijst met een X.

Het aankruisvakje in de eerste kolom activeert de regel. Naam is een regelnaam die u geeft. Veld is ofwel Tags of een aangepaste kolom uit uw bibliotheek. Waarde is de overeenkomende inhoud van Veld. Als een voorvoegsel-regel is vervuld, wordt het boek gemarkeerd met het geselecteerde Voorvoegsel.

Drie voorvoegsel-regels zijn in het bovenstaande voorbeeld gespecificeerd:

1. Boek gelezen geeft aan dat een boek met willekeurige datum in een aangepaste kolom genaamd Laatst gelezen een vinkje als voorvoegsel krijgt.

2. Wenslijst item geeft aan dat elk boek met een Wensenlijst tag een X krijgt als voorvoegsel.

3. Bibliotheek boeken geeft aan dat elk boek met een waarde Waar (of Ja) in een aangepaste kolom Beschikbaar in Bibliotheek dubbele pijltjes als voorvoegsel krijgt .

De eerste overeenkomende regel levert het voorvoegsel. Uitgeschakelde of incomplete regels worden genegeerd.

Uitgesloten boeken

Uitsluiting-regels bepalen welke boeken niet gecatalogiseerd worden.

Het vakje in de eerste kolom schakelt de regel in. Naam is een regelnaam die u opgeeft. Veld is ofwel Tags of een aangepaste kolom in uw bibliotheek. Waarde komt overeen met de inhoud van Veld. Wanneer aan een uitsluiting-regel is voldaan, wordt het boek uitgesloten van de gegenereerde catalogus.

Twee uitsluiting-regels zijn in het bovenstaande voorbeeld gespecificeerd:

1. De Catalogi regel geeft aan dat elk boek met een Catalogus tag zal worden uitgesloten van de gegenereerde catalogus.

2. De Gearchiveerd boeken regel geeft aan dat alle boeken met een waarde van Gearchiveerd in de aangepaste kolom Status worden uitgesloten van de gegenereerde catalogus.

Alle regels worden geëvalueerd voor elk boek. Uitgeschakelde of onvolledige regels worden genegeerd.

Uitgesloten genres

Wanneer de catalogus is gegenereerd worden labels in uw gegevensbank gebruikt als genres. Bijvoorbeeld gebruikt u de labels “Fictie”en “Nonfictie”. Deze labels worden genres in de gegenereerde catalogus met boeken opgevoerd onder hun betreffende genre gebaseerd op hun toegewezen labels. Een boek wordt opgevoerd in iedere genre-sectie waarvoor het een betreffende tag heeft.

U kunt bepaalde tags ook gebruiken voor andere doeleinden zoals een + om een gelezen boek aan te geven of een vierkante haakjes tag zoals [Amazon Freebie] voor de bron. Met de Uitgesloten genres regex kunt u tags opgeven die u niet wilt als genres in de gegenereerde catalogus. Het standaard regex uitsluiting patroon \[.+\]\+ sluit alle tags uit in de form [tag] evenals +, standaard tag voor gelezen boeken, van het gebruik als genres in de gegenereerde catalogus.

U kunt ook een exacte tag-naam gebruiken in een regex. Bijvoorbeeld [Amazon Freebie] of [Project Gutenberg]. Indien u meerdere extacte labels voor uitsluiting wilt opgeven zet u een verticale streep ertussen: [Amazon Freebie]|[Project Gutenberg].

Regex-resultaten toont welke labels worden uitgesloten wanneer de catalogus is gemaakt, gebaseerd op de labels in uw database en de regex-patronen die u invoert. De resultaten worden bijgewerkt als u de regex wijzigt.

Overige opties

Catalogus cover bepaalt of er een nieuwe cover wordt gegenereerd of een bestaande gebruikt. Het is mogelijk een aangepaste cover voor uw catalogus te creëren - kijk op aangepaste_catalogus_covers voor meer informatie. Als u een gecreëerde cover terug wilt gebruiken, selecteer Gebruik bestaande cover. Selecteer anders Genereer nieuwe cover.

Extra Beschrijving notitie specificeert het invoegen van aangepaste kolom inhoud in de pagina Beschrijving, naast de miniatuur cover. Bv. u wilt de datum tonen wanneer u een boek voor het laatst gelezen hebt met een Laatst gelezen aangepaste kolom. Voor geavanceerd gebruik van de Beschrijving notitie functie, lees dit bericht in het calibre forum [http://www.mobileread.com/forums/showpost.php?p=1335767&postcount=395].

Miniatuur breedte specificeert de voorkeursbreedte voor omslag-miniaturen in de pagina Beschrijving. Miniaturen worden gecached om de snelheid te verbeteren. Bij experimenteren met verschillende breedtes, probeer een catalogus met maar een paar boeken totdat u de gewenste breedte heeft, genereer dan uw volledige catalogus. Het eerste genereren van een catalogus met een nieuwe miniatuurbreedte zal trager zijn maar de volgende uitvoeringen van de catalogus hebben voordeel van de miniaturencache.

Samenvoegen met commentaar specificeert een aangepaste kolom wiens inhoud niet-destructief samengevoegd wordt met de commentaar metadata tijdens catalogusgeneratie. Bv. u hebt een aangepaste kolom Auteur bio die u wilt toevoegen aan de commentaar metadata. U kan kiezen om de inhoud van de aangepaste kolom voor of na de commentaar sectie te plaatsen, en optioneel de toegevoegde inhoud afscheiden met een horizontale streep. Mogelijke kolomtypes zijn tekst, commentaar en samengesteld.

Aangepaste catalogus omslagen

Met de Generate Cover plugin [https://www.mobileread.com/forums/showthread.php?t=124219] geïnstalleerd, kun je aa,gepaste covers toevoegen aan je catalogus. Om de plugin te installeren, ga naar Preferences > Advanced > Plugins > Get new plugins.

Extra hulpbronnen

Voor meer informatie over calibres catalogus functionaliteit, bezoek het MobileRead forum sticky Creating Catalogs - Start here [https://www.mobileread.com/forums/showthread.php?t=118556], waar u informatie kunt vinden hoe een catalogus template aan te passen aan uw wensen, en hoe u een bug report op te stellen.

Voor vragen of om calibres Catalogus eigenschap te bespreken met anderen, bezoek het MobileRead forum Library Management [https://www.mobileread.com/forums/forumdisplay.php?f=236].

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Lesboeken »
- Virtuele bibliotheken

Virtuele bibliotheken

Een virtuele bibliotheek is een manier om in calibre een bepaald deel van uw bibliotheek te openen. Bijvoorbeeld: U wilt alleen werken met boeken van een bepaalde auteur of boeken met een bepaalde tag. Het gebruik van virtuele bibliotheken is de voorkeurwijze om uw grote boekenverzameling op te splitsen in kleinere verzamelingen. Deze manier is beter dan het opdelen van uw bibliotheek in kleine bibliotheken omdat, als u uw gehele collectie wilt doorzoeken, u eenvoudig teruggaat naar de gehele bibliotheek. Er bestaat geen mogelijkheid om meerdere afzonderlijke bibliotheken tegelijk te doorzoeken.

Een Virtuele bibliotheek verschilt van een gewone zoekopdracht. Een zoekopdracht beperkt alleen de lijst met boeken getoond in de boekenlijst. Een Virtuele bibliotheek doet dat en beperkt ook de vermeldingen in de :guilabel: Tagbrowser links. De Tag-browser toont alleen labels, auteurs, series, uitgevers, enz. afkomstig van de boeken in de Virtuele bibliotheek. Een Virtuele bibliotheek gedraagt zich dus alsof de bibliotheek alleen de beperkte verzameling boeken bevat.

Virtuele bibliotheken maken

Voor gebruik van een Virtuele bibliotheek, klik op de :guilabel :Virtuele bibliotheek knop links van de zoekbalk en selecteer de :guilabel:` Creëer Virtuele Bibliotheek` optie. Laten we als eerste voorbeeld een virtuele bibliotheek maken die alleen boeken van een bepaalde auteur laat zien. Klik op de :guilabel :Auteurs link zoals in de afbeelding onder, kies de auteur die u wilt gebruiken en klik op OK.

De dialoog Virtuele bibliotheek maken is voor u ingevuld. Klik op OK en u ziet dat een nieuwe Virtuele bibliotheek is gemaakt, en ernaar toe omgeschakeld, enkel de boeken van de geselecteerde auteur weergevend. Wat calibre betreft, is het alsof uw bibliotheek alleen boeken van de geselecteerde auteur bevat.

U kan altijd terugschakelen naar de volledige bibliotheek door nogmaals op Virtuele bibliotheek te klikken en <None> te selecteren.

Virtuele bibliotheken zijn gebaseerd op zoekopdrachten. U kunt elke zoekopdracht gebruiken als basis voor een virtuele bibliotheek. De virtuele bibliotheek bevat alleen de boeken die daarmee overeenkomen. Tik eerst de zoekopdracht die u wilt gebruiken in de zoekbalk of bouw ze op met de: guilabel: Tagbrowser. Als u tevreden bent met de resultaten, klik op de knop Virtuele bibliotheek, kies: guilabel: Bibliotheek maken en voer er een naam voor in. De virtuele bibliotheek wordt dan gemaakt op basis van de zoekopdracht die u net hebt ingevoerd. Zoekopdrachten zijn zeer krachtig, voor voorbeelden van dingen die u ermee kunt doen, zie: ref: search_interface.

Voorbeelden van handige Virtuele bibliotheken

- Boeken toegevoegd aan calibre in de laatste dag:

date:>1daysago

- Boeken toegevoegd aan Calibre in de laatste maand:

date:>30daysago

- Boeken met een waardering van 5 sterren:

rating:5

- Boeken met een waardering van minimaal 4 sterren:

rating:>=4

- Boeken zonder waardering:

rating:false

- Tijdschriften gedownload door de Fetch Nieuws functie in calibre:

labels:=News and author:=Calibre

- Boeken zonder labels:

labels:false

- Boeken zonder omslag:

cover:false

Werken met virtuele bibliotheken

U kan een eerder aangemaakte Virtuele bibliotheek bewerken of verwijderen door op het :guilabel: ‘Virtuele bibliotheek’ te klikken en de geschikte actie te kiezen.

U kunt calibre instellen om altijd een bepaalde virtuele bibliotheek te gebruiken als de huidige bibliotheek geopend is, ga naar Voorkeuren → Interface → Gedrag.

U kan de huidige zoekopdracht snel als een tijdelijke Virtuele bibliotheek gebruiken, klik op de Virtuele Bibliotheek knop en kies de *huidige zoekopdracht.

U kan alle beschikbare virtuele bibliotheken als tabs boven het boekenoverzicht tonen. Dit is vooral handig als u veel tussen virtuele bibliotheken wilt wisselen. Klik op de knop Virtuele bibliotheek en selecteer Toon virtuele bibliotheken als tabs. U kan de tabs verplaatsen door slepen en neerzetten en die sluiten die u niet wilt zien. Gesloten tabs herstelt u door rechts te klikken op de tabbalk.

Virtuele bibliotheken gebruiken in zoekopdrachten

U kan boeken zoeken in een virtuele bibliotheek met het voorvoegsel vl:. Bijvoorbeeld: `` vl:Gelezen`` vindt alle boeken in de Gelezen virtuele bibliotheek. De zoekopdracht `` vl:Gelezen en vl:”Science Fiction” vindt alle boeken in zowel de virtuele bibliotheek Gelezen als Science Fiction.

De waarde na ``vl:’’ moet de naam van een virtuele bibliotheek zijn. Bevat de naam van de virtuele bibliotheek spaties, omring deze dan met aanhalingstekens.

Nut voor een Virtuele bibliotheek is in de Content server. In Voorkeuren → Delen → Delen via (inter)net → Vraag gebruikersnaam en wachtwoord kan u calibre bibliotheken zichtbaar voor een gebruiker beperken. Voor elke zichtbare bibliotheek kan u een zoekexpressie specificeren om verder te beperken welke boeken zichtbaar zijn. Gebruik vl:"Virtuele bibliotheek naam" om de boeken te beperken tot deze in een Virtuele bibliotheek.

Gebruik van extra restricties

U kunt boeken getoond in een Virtuele Bibliotheek verder beperken met Extra beperkingen. Een extra beperking is een opgeslagen zoekopdracht die u eerder heeft gemaakt die op de huidige Virtuele Bibliotheek wordt toegepast om verder boeken te beperken getoond in de Virtuele bibliotheek. Bv: U heeft een Virtuele Bibliotheek voor boeken getagd als Historische Fictie en een opgeslagen zoekopdracht voor ongelezen boeken. Klik op de knop Virtuele Bibliotheek en kies de optie Extra beperkingen om alleen ongelezen Historische Fictie boeken te tonen. Meer over opgeslagen zoekopdrachten op Zoekopdrachten opslaan.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Het calibre:// URL schema

Het calibre:// URL schema
calibre registreert zichzelf als het programma voor calibre:// URLs. U kan deze dus gebruiken voor acties als boeken openen, zoeken naar boeken, enz van andere programma’s/documenten of via de opdrachtregel. Bijvoorbeeld, het volgende uitvoeren via de opdrachtregel:

calibre calibre://switch-library/Some_Library

Opent calibre met de bibliotheek Some Library. Bibliotheek namen zijn de mapnaam van de bibliotheekmap met spaties vervangen door onderliggende streepjes. De speciale waarde _ betekent huidige bibliotheek. De verschillende URL types zijn hieronder gedocumenteerd.

U kan deze links zelfs in HTML bestanden of Word documenten zetten en het besturingssysteem zal automatisch calibre starten om de gespecificeerde actie uit te voeren.

- Wissen naar een specifieke bibliotheek

- Toon een specifiek boek in calibre

- Open een specifiek boek in de e-boek viewer op een specifieke plaats

- Naar boeken zoeken

- Open a book details window on a book in some library

- Open the notes associated with an author/series/etc.

- Hex codering van URL parameters

Wissen naar een specifieke bibliotheek

De URL syntax is

calibre://switch-library/Library_Name

Bibliotheeknamen zijn de mapnaam van de bibliotheek, spaties vervangen door onderliggende streepjes. Speciale waarde _ betekent huidige bibliotheek. U kan ook hex codering voor de bibliotheeknamen gebruiken, nuttig als de bibliotheek namen speciale karakters bevatten die anders URL codering nodig hebben. Hex gecodeerde bibliotheeknamen lijken op:

_hex_-AD23F4BC

Waar het deel na de _hex_- prefix is de bibliotheeknaam in UTF-8 en elke byte voorgesteld wordt door 2 hexadecimale karakters.

Toon een specifiek boek in calibre

De URL syntax is

calibre://show-book/Library_Name/book_id

Dit toont het boek met book_id (een nummer) in calibre. De boeken ids zijn zichtbaar in de calibre interface door over de Klik om te openen link in het Boek details paneel te hoveren, het is het nummer tussen haakjes aan het einde van het pad naar de boekmap.

U kan een link naar het nu getoonde boek in calibre kopiëren door rechts te klikken op het Boek details paneel en Kopieer link naar boek te kiezen.

If a search is active and the book is not matched by the search then the search is cleared.

If a Virtual library is selected, calibre will use it when showing the book. If the book isn’t found in that virtual library then the virtual library is cleared.

If you want to switch to a particular Virtual library when showing the book, use:

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

replacing spaces in the Virtual library name by %20. If the book isn’t found in that virtual library then the virtual library is ignored.

Open een specifiek boek in de e-boek viewer op een specifieke plaats

De URL syntax is

calibre://view-book/Library_Name/book_id/book_format?open_at=location

Hier is book_format het formaat van het boek, bv. EPUB or MOBI en de locatie is een optionele locatie in het boek. De gemakkelijkste manier om deze links te krijgen is door het boek te openen in de viewer, selecteer dan in de viewer controls Ga naar → Locatie en daar vindt u zo’n link die u naar ergens kan kopiëren/plakken.

Naar boeken zoeken

De URL syntax is

calibre://search/Library_Name?q=query
calibre://search/Library_Name?eq=hex_encoded_query

Zoeken is hier elke geldige search expression. Als de zoekuitdrukking ingewikkeld is, codeer als hex string en gebruik eq in de plaats. De zoekopdracht leeg laten zal de huidige zoekopdracht wissen.

Als een Virtuele bibliotheek is geselecteerd,zal calibre ze standaard leegmaken voor het zoeken zodat alle boeken zeker gevonden worden. Als u de Virtuele bibliotheek wil behouden, gebruik:

calibre://search/Library_Name?q=query&virtual_library=_

Als u naar een bepaalde Virtuele bibliotheek wil schakelen, gebruik:

calibre://search/Library_Name?virtual_library=Library%20Name
or
calibre://search/Library_Name?encoded_virtual_library=hex_encoded_virtual_library_name

vervang spaties in naam Virtuele bibliotheek door “%20”.

Als u in calibre zoekt en een link wil genereren daarvoor, kan u dat doen door rechts te klikken op de zoekbalk en Kopieer zoekopdracht als URL te kiezen.

Open a book details window on a book in some library

De URL syntax is

calibre://book-details/Library_Name/book_id

This opens a book details window on the specified book from the specified library without changing the current library or the selected book.

Open the notes associated with an author/series/etc.

De URL syntax is

calibre://book-details/Library_Name/Field_Name/id_Item_Id

This opens a window showing the notes of the specified item. The easiest way to create such URLs is to show the notes you want in calibre and click the Copy URL button to copy the URL to the clipboard and paste it wherever you need.

Here Field_Name is the name of the columns such as authors or tags. For user created columns, replace the leading # in the field name with an underscore, so #mytags becomes _mytags.

In addition to specifying items by id using Item_Id you can also specify them by name using either val_Item_Name or hex_Hex_Encoded_Item_Name. For example:

calibre://book-details/Library_Name/authors/val_John%20Doe

Hex codering van URL parameters

Hex codering van URL parameters gebeurt door eerst de parameter als UTF-8 bytes te coderen en dan elke byte te vervangen door twee hex karakters die de byte voorstellen. Bv. de string abc zijn de bytes 0x61 0x62 en 0x63 in UTF-8, dus de gecodeerde versie is de string: 616263.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- calibre aanpassen

calibre aanpassen
calibre heeft een zeer modulair ontwerp. Veel stukken kunnen aangepast worden. Hier leert u:

- hoe omgevingsvariabelen te gebruiken en tweaks om calibre’s gedrag aan te passen.

- hoe uw eigen statische bronnen specificeren zoals iconen en sjablonen om de standaard te negeren

- hoe plug-ins gebruiken om bijkomende functionaliteit toe te voegen aan calibre.

- hoe icoonthema’s en plug-ins delen met andere calibre gebruikers.

- kijk hoe u “recepten” kan creëren om nieuwe online inhoudsbronnen aan calibre toe te voegen in de Sectie nieuws.

Notitie

Merk op dat alhoewel icoonthema’s en plug-ins geïndexeerd zijn en downloadbaar via calibre’s ingebouwde updater, ze geen deel van calibre zijn en hun officiële plaats voor ondersteuning en broncode zijn de Mobileread forums [https://www.mobileread.com/forums/forumdisplay.php?f=166] in hun ondersteuningsdraadjes.

- Omgevingsvariabelen

- Truckjes

- Iconen, sjablonen, enz. overschrijven

- Maak uw eigen icon thema voor Calibre

- calibre personaliseren met plugins

Omgevingsvariabelen

- CALIBRE_CONFIG_DIRECTORY - stelt de map in waar configuratiebestanden worden bewaard/gelezen.

- CALIBRE_TEMP_DIR - stelt de tijdelijke map in gebruikt door calibre

- CALIBRE_CACHE_DIRECTORY - stelt de map in waar calibre persistent data cachet tussen sessies

- CALIBRE_OVERRIDE_DATABASE_PATH - laat u een volledig pad opgeven naar metadata.db. Met deze variabele kunt u metadata.db op een andere locatie hebben dan in de bibliotheekmap. Handig als uw bibliotheekmap op een netwerkschijf staat waar bestandsvergrendeling niet is ondersteund.

- CALIBRE_DEVELOP_FROM - draaide in een calibre development omgeving. Kijk op Opzetten van Calibre in een development omgeving.

- CALIBRE_OVERRIDE_LANG - dwong de taal af gebruikt door de interface (ISO 639 taal code)

- CALIBRE_TEST_TRANSLATION - testte een vertaal .po bestand (zou het pad naar het .po bestand moeten zijn)

- CALIBRE_NO_NATIVE_FILEDIALOGS - laat calibre geen native bestandsdialogen gebruiken voor selecteren bestanden/mappen.

- CALIBRE_NO_NATIVE_MENUBAR - laat calibre geen native (global) menu in Ubuntu Unity en gelijkaardige Linux desktop omgevingen creëren. Het menu staat in plaats daarvan in het venster, traditioneel.

- CALIBRE_USE_SYSTEM_THEME - standaard, bij Linux, gebruikt calibre z’n eigen, ingebouwde Qt stijl. Dit is om crashen en hangen, veroorzaakt door incompatibiliteiten tussen de versie van Qt calibre die van het systeem. Het nadeel is dat calibre mogelijk niet de systeem look en feel volgt. Als u deze omgevingsvariabele op Linux zet, zal hij calibre het systeem thema laten gebruiken – opgelet voor crashes en hangen.

- CALIBRE_SHOW_DEPRECATION_WARNINGS - laat calibre depreciatie verwittigingen sturen naar stdout. Nuttig voor calibre ontwikkelaars.

- CALIBRE_NO_DEFAULT_PROGRAMS - voorkomt dat calibre automatisch de bestandstypes die het aankan registreert in Windows.

- QT_QPA_PLATFORM - On Linux set this to wayland to force calibre to use Wayland and xcb to force use of X11.

- SYSFS_PATH - Gebruik dit als sysfs niet in /sys staat maar ergens anders

- http_proxy, https_proxy - bij Linux gebruikt om een HTTP(S) proxy te specificeren

Zie Hoe omgevings-variabelen in Windows in te stellen <https://www.computerhope.com/issues/ch000549.htm> _. Als u macOS gebruikt, kunt u omgevingsvariabelen instellen door de ~/Library/Preferences/calibre/macos-env.txt te maken en de omgevingsvariabelen één per regel erin te plaatsen, bijvoorbeeld:

CALIBRE_DEVELOP_FROM=$HOME/calibre-src/src
CALIBRE_NO_NATIVE_FILEDIALOGS=1
CALIBRE_CONFIG_DIRECTORY=~/.config/calibre

Truckjes

Truckjes zijn kleine wijzigingen die u kunt aanbrengen om diverse aspecten te beheren in calibres gedrag. U kunt deze wijzigen in Voorkeuren->Geavanceerd->Truckjes. De standaard waardes voor aanpassingen staan hieronder

#!/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'

Iconen, sjablonen, enz. overschrijven

Notitie
calibre has direct support for icon themes, there are several icon themes available for calibre, that you can use by going to Preferences → Interface → Look & Feel → Change icon theme. It is preferable to use icon themes over overriding individual icons.

calibre staat u toe de statische bronnen, zoals pictogrammen, JavaScript en sjablonen voor het metadata inlegvel, catalogi, enz. te vervangen door aangepaste versies die u leuk vindt. Alle statische bronnen worden opgeslagen in de bronnen submap van de calibre installatielocatie. In Windows is dit meestal C:\Program Files\Calibre2app\resources. In macOS, /Applications/calibre.app/Contents/Resources/resources/. In Linux , als u het binaire installatieprogramma van de calibre-website gebruikt, is dit /opt/calibre/resources. Deze paden kunnen veranderen, afhankelijk van waar u kiest om calibre te installeren.

De bestanden in deze resourcesmap niet veranderen want uw veranderingen worden overschreven bij de volgende calibre update. In plaats daarvan, ga naar Voorkeuren → Geavanceerd → Allerlei en klik Open calibre configuratie map. Creëer in deze configuratiemap een submap genaamd bronnen en plaats er de bestanden die u wilt negeren. Plaats de bestanden in geschikte submappen, plaats afbeeldingen bijvoorbeeld in resources/images, etc. calibre zal automatisch uw aangepast bestand verkiezen boven het ingebouwde bij de volgende start.

Bijvoorbeeld, als u het icoon voor de Boeken verwijderen actie wilt veranderen, zou u eerst in de ingebouwde bronnenmap kijken en zien dat het relevant bestand resources/images/remove_books.png is. Aangenomen dat u een alternatief icoon heeft in PNG formaat, genaamd my_remove_books.png, zou u het opslaan in de configuratiemap als resources/images/remove_books.png. Alle iconen gebruikt door de calibre gebruikersinterface staan in resources/images en zijn submappen.

Maak uw eigen icon thema voor Calibre

If you have created a beautiful set of icons and wish to share them with other calibre users via calibre’s builtin icon theme support, you can easily package up your icons into a theme. To do so, go to Preferences → Miscellaneous → Create icon theme, select the folder where you have put your icons. Then fill up the theme metadata and click OK. This will result in a ZIP file containing the theme icons. You can upload that to the calibre forum at Mobileread [https://www.mobileread.com/forums/forumdisplay.php?f=166] and then I will make your theme available via calibre’s builtin icon theme system. By default, the icon theme you just created will also be installed as the current theme in calibre. If you are testing your theme, remember to remove the images from the resources/images folder so that the icons from the theme are used.

As of calibre 6, you can have custom icons for light and dark mode. Simply create two versions of the icon and name the files with the suffix -for-dark-theme and -for-light-theme. For example, modified-for-dark-theme.png and modified-for-light-theme.png. Then calibre will automatically use the appropriate icon based on the current theme.

calibre personaliseren met plugins
calibre is zeer modulair ontworpen. Bijna alle functionaliteit in calibre komt in de vorm van plug-ins. Er zijn plug-ins voor conversie, downloaden van nieuws (recepten genoemd), voor verschillende componenten van de gebruikersinterface, om te verbinden met verschillende toestellen, om bestanden te verwerken bij toevoegen aan calibre enz. U vindt een volledige lijst van alle ingebouwde plug-ins in calibre op :guilabel: `Voorkeuren-> Geavanceerd-> Plug-ins ‘.

U kunt uw eigen plugins schrijven om het gedrag van calibre aan te passen en uit te breiden. De opbouw van plugins is heel simpel, lees hierover de handleiding Uw eigen plugins schrijven om de functionaliteit van calibre uit te breiden.

Als u een plug-in geschreven heeft kan u die uploaden naar het calibre plug-in forum op Mobileread [https://www.mobileread.com/forums/forumdisplay.php?f=237] en hij zal beschikbaar zijn via calibre’s ingebouwde plug-in updater.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- calibre aanpassen »
- API documentatie voor plugins

API documentatie voor plugins

Definieert verschillende abstracte basis verdelingen die kunnen worden onderverdeeld om sterke plugins te creëren. De handige verdelingen zijn:

- Plugin

- BestandsTypePlugin

- Metadata plugins

- Catalogus plugins

- Metadata download plugins

- Omzet plugins

- Device drivers

- User interface actions

- Preferences plugins

Plugin
class calibre.customize.Plugin(plugin_path)[broncode]

Een Calibre plugin. Nuttige onderdelen bevatten:

- self.installation_type: Stores how the plugin was installed.

- self.plugin_path: slaat pad op naar het ZIP-bestand dat bevat

deze plugin of None als het een ingebouwde plugin betreft

- self.site_customization: slaat een aanpassingsreeks op die is ingevoerd

door de gebruiker.

Methoden die moeten worden opgeheven in subklassen:

- iinitialiseren()

- customization_help()

Nuttige methodes:

- temporary_file()

- __enter__()

- load_resources()

supported_platforms = []

Lijst van platformen waar deze plugin op werkt. Bijvoorbeeld: ['windows', 'osx', 'linux']

name = 'Trivial Plugin'

De naam van de plug-in. Deze mag geen Trivial Plugin heten.

version = (1, 0, 0)

De versie van deze plugin in 3’en (hoofd, sub, herziening)

description = 'Doet helemaal niets'

Een korte tekst die beschrijft wat de plugin doet

author = 'Onbekend'

De maker van deze plugin

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 = 'Basis'

The type of this plugin. Used for categorizing plugins in the GUI

initialize()[broncode]

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()[broncode]

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)[broncode]

Save the settings specified by the user with config_widget.

Parameters:

config_widget – The widget returned by config_widget().

do_user_config(parent=None)[broncode]

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)[broncode]

If this plugin comes in a ZIP file (user added plugin), this method will allow you to load resources from the ZIP file.

Bijvoorbeeld om een afbeelding te laden:

pixmap = QPixmap()
pixmap.loadFromData(self.load_resources(['images/icon.png'])['images/icon.png'])
icon = QIcon(pixmap)

Parameters:

names – List of paths to resources in the ZIP file using / as separator

Returns:

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)[broncode]

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.

Parameters:

gui – Indien waar, geef HTML hulp terug, anders platte tekst hulp.

temporary_file(suffix)[broncode]

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.

Parameters:

suffix – Het achtervoegsel dat het tijdelijke bestand zal krijgen.

cli_main(args)[broncode]

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.

BestandsTypePlugin
class calibre.customize.FileTypePlugin(plugin_path)[broncode]

Basisklassen: 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 = 'Bestandstype'

The type of this plugin. Used for categorizing plugins in the GUI

run(path_to_ebook)[broncode]

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.

Parameters:

path_to_ebook – Absolute path to the e-book.

Returns:

Absolute path to the modified e-book.

postimport(book_id, book_format, db)[broncode]

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.

Parameters:

- book_id – Gegevensbank id van het toegevoegde boek.

- book_format – Het bestandsformaat van het toegevoegde boek.

- db – Bibliotheek gegevensbank

postconvert(book_id, book_format, db)[broncode]

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.

Parameters:

- book_id – Gegevensbank id van het toegevoegde boek.

- book_format – Het bestandsformaat van het toegevoegde boek.

- db – Bibliotheek gegevensbank

postdelete(book_id, book_format, db)[broncode]

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.

Parameters:

- book_id – Gegevensbank id van het toegevoegde boek.

- book_format – Het bestandsformaat van het toegevoegde boek.

- db – Bibliotheek gegevensbank

postadd(book_id, fmt_map, db)[broncode]

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.

Parameters:

- book_id – Gegevensbank id van het toegevoegde boek.

- 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 – Bibliotheek gegevensbank

Metadata plugins

class calibre.customize.MetadataReaderPlugin(*args, **kwargs)[broncode]

Basisklassen: 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']

Lijst van platformen waar deze plugin op werkt. Bijvoorbeeld: ['windows', 'osx', 'linux']

version = (7, 9, 0)

De versie van deze plugin in 3’en (hoofd, sub, herziening)

author = 'Kovid Goyal'

De maker van deze plugin

type = 'Metagegevens-lezer'

The type of this plugin. Used for categorizing plugins in the GUI

get_metadata(stream, type)[broncode]

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.

Parameters:

type – The type of file. Guaranteed to be one of the entries in file_types.

Returns:

A calibre.ebooks.metadata.book.Metadata object

class calibre.customize.MetadataWriterPlugin(*args, **kwargs)[broncode]

Basisklassen: 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']

Lijst van platformen waar deze plugin op werkt. Bijvoorbeeld: ['windows', 'osx', 'linux']

version = (7, 9, 0)

De versie van deze plugin in 3’en (hoofd, sub, herziening)

author = 'Kovid Goyal'

De maker van deze plugin

type = 'Metagegevens-schrijver'

The type of this plugin. Used for categorizing plugins in the GUI

set_metadata(stream, mi, type)[broncode]

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.

Parameters:

- type – The type of file. Guaranteed to be one of the entries in file_types.

- mi – A calibre.ebooks.metadata.book.Metadata object

Catalogus plugins

class calibre.customize.CatalogPlugin(plugin_path)[broncode]

Basisklassen: Plugin

Een plugin welke een catalogus generator implementeert.

file_types = {}

Output file type for which this plugin should be run. For example: ‘epub’ or ‘xml’

type = 'Catalogusgenerator'

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()[broncode]

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)[broncode]

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.

Parameters:

- path_to_output – Absolute path to the generated catalog file.

- opts – A dictionary of keyword arguments

- db – Een LibraryDatabase2 object

Metadata download plugins
class calibre.ebooks.metadata.sources.base.Source(*args, **kwargs)[broncode]

Basisklassen: Plugin

type = 'Externe metagegevensbron'

The type of this plugin. Used for categorizing plugins in the GUI

author = 'Kovid Goyal'

De maker van deze plugin

supported_platforms = ['windows', 'osx', 'linux']

Lijst van platformen waar deze plugin op werkt. Bijvoorbeeld: ['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()[broncode]

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()[broncode]

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.

Parameters:

gui – Indien waar, geef HTML hulp terug, anders platte tekst hulp.

config_widget()[broncode]

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)[broncode]

Save the settings specified by the user with config_widget.

Parameters:

config_widget – The widget returned by config_widget().

get_author_tokens(authors, only_first_author=True)[broncode]

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)[broncode]

Take a title and return a list of tokens useful for an AND search query. Excludes connectives(optionally) and punctuation.

split_jobs(jobs, num)[broncode]

Split a list of jobs into at most num groups, as evenly as possible

test_fields(mi)[broncode]

Return the first field from self.touched_fields that is null on the mi object

clean_downloaded_metadata(mi)[broncode]

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)[broncode]

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)[broncode]

Return a human readable name from the return value of get_book_url().

get_book_urls(identifiers)[broncode]

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)[broncode]

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)[broncode]

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={})[broncode]

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)[broncode]

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.

Parameters:

- 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 – De titel van het boek, mag Geen zijn.

- 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.

Returns:

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)[broncode]

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)[broncode]

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.

Het algoritme 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

Omzet plugins
class calibre.customize.conversion.InputFormatPlugin(*args)[broncode]

Basisklassen: 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 = 'Invoer converteren'

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']

Lijst van platformen waar deze plugin op werkt. Bijvoorbeeld: ['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()[broncode]

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)[broncode]

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.

Parameters:

- 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)[broncode]

Called to allow the input plugin to perform postprocessing after the book has been parsed.

specialize(oeb, opts, log, output_fmt)[broncode]

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)[broncode]

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)[broncode]

Basisklassen: 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 = 'Converteer uitvoer'

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']

Lijst van platformen waar deze plugin op werkt. Bijvoorbeeld: ['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)[broncode]

Render the contents of oeb_book (which is an instance of calibre.ebooks.oeb.OEBBook) to the file specified by output.

Parameters:

- 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)[broncode]

Can be used to change the values of conversion options, as used by the conversion pipeline.

specialize_css_for_output(log, opts, item, stylizer)[broncode]

Can be used to make changes to the CSS during the CSS flattening process.

Parameters:

- 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)[broncode]

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.

Device drivers

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)[broncode]

Basisklassen: Plugin

Defines the interface that should be implemented by backends that communicate with an e-book reader.

type = 'Toestel interface'

The type of this plugin. Used for categorizing plugins in the GUI

FORMATS = ['lrf', 'rtf', 'pdf', 'txt']

Geordende lijst met ondersteunde formaten

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

Een integer of een lijst met 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

Hoogte van voorbeeldweergave op het bestand

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'

Icoon voor dit apparaat

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()[broncode]

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)[broncode]

Return True, device_info if a device handled by this plugin is currently connected.

Parameters:

devices_on_system – Lijst met momenteel aangesloten apparaten

detect_managed_devices(devices_on_system, force_refresh=False)[broncode]

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.

Parameters:

- devices_on_system – Set van USB apparaten gevonden op het systeem

- 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)[broncode]

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)[broncode]

Parameters:

- key – De sleutel om het apparaat te ontgrendelen

- 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)[broncode]

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.

Parameters:

usbdevice – A usbdevice as returned by calibre.devices.winusb.scan_usb_devices()

can_handle(device_info, debug=False)[broncode]

Unix version of can_handle_windows().

Parameters:

device_info – Is a tuple of (vid, pid, bcd, manufacturer, product, serial number)

open(connected_device, library_uuid)[broncode]

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.

Parameters:

- 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()[broncode]

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()[broncode]

Called if the user yanks the device without ejecting it first.

set_progress_reporter(report_progress)[broncode]

Set a function to report progress information.

Parameters:

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)[broncode]

Ask device for device information. See L{DeviceInfoQuery}.

Returns:

(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()[broncode]

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)[broncode]

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)[broncode]

Get total space available on the mountpoints:

1. Hoofdgeheugen

2. Geheugenkaart A

3. Geheugenkaart B

Returns:

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)[broncode]

Get free space available on the mountpoints:

1. Hoofdgeheugen

2. Geheugenkaart A

3. Geheugenkaart B

Returns:

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)[broncode]

Return a list of e-books on the device.

Parameters:

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.

Returns:

Een boekenlijst.

upload_books(files, names, on_card=None, end_session=True, metadata=None)[broncode]

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”.

Parameters:

- files – Een lijst met paden

- 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).

Returns:

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)[broncode]

Add locations to the booklists. This function must not communicate with the device.

Parameters:

- 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)[broncode]

Delete books at paths on device.

classmethod remove_books_from_metadata(paths, booklists)[broncode]

Remove books from the metadata list. This function must not communicate with the device.

Parameters:

- paths – paden naar boeken op het apparaat.

- 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)[broncode]

Metadata bijwerken op het apparaat.

Parameters:

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)[broncode]

Read the file at path on the device and write it to outfile.

Parameters:

outfile – file object like sys.stdout or the result of an open() call.

classmethod config_widget()[broncode]

Should return a QWidget. The QWidget contains the settings for the device interface

classmethod save_settings(settings_widget)[broncode]

Should save settings to disk. Takes the widget created in config_widget() and saves all settings to disk.

classmethod settings()[broncode]

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)[broncode]

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).

Returns:

None or a single plugboard instance.

set_driveinfo_name(location_code, name)[broncode]

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)[broncode]

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()[broncode]

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()[broncode]

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()[broncode]

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)[broncode]

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()[broncode]

Return map of device uid to friendly name for all devices that the user has asked to be ignored.

set_user_blacklisted_devices(devices)[broncode]

Set the list of device uids that should be ignored by this driver.

specialize_global_preferences(device_prefs)[broncode]

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)[broncode]

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()[broncode]

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()[broncode]

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()[broncode]

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)[broncode]

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)[broncode]

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()[broncode]

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)[broncode]

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)[broncode]

Basisklassen: list

A list of books. Each Book object must have the fields

1. titel

2. auteurs

3. grootte (bestandsgrootte van het boek)

4. datetime (a UTC time tuple)

5. pad (pad op het apparaat naar het boek)

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()[broncode]

Return True if the device supports collections for this book list.

add_book(book, replace_metadata)[broncode]

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)[broncode]

Remove a book from the booklist. Correct any device metadata at the same time

get_collections(collection_attributes)[broncode]

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.

Parameters:

collection_attributes – A list of attributes of the Book object

USB Mass Storage based devices

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)[broncode]

Basisklassen: 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

Een integer of een lijst met 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

Plaats nieuws in een eigen map

reset(key='-1', log_packets=False, report_progress=None, detected_device=None)[broncode]

Parameters:

- key – De sleutel om het apparaat te ontgrendelen

- 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)[broncode]

Set a function to report progress information.

Parameters:

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)[broncode]

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)[broncode]

Get total space available on the mountpoints:

1. Hoofdgeheugen

2. Geheugenkaart A

3. Geheugenkaart B

Returns:

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)[broncode]

Get free space available on the mountpoints:

1. Hoofdgeheugen

2. Geheugenkaart A

3. Geheugenkaart B

Returns:

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)[broncode]

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)[broncode]

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.

Parameters:

usbdevice – A usbdevice as returned by calibre.devices.winusb.scan_usb_devices()

open(connected_device, library_uuid)[broncode]

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.

Parameters:

- 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()[broncode]

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()[broncode]

Called if the user yanks the device without ejecting it first.

sanitize_callback(path)[broncode]

Callback to allow individual device drivers to override the path sanitization used by create_upload_path().

filename_callback(default, mi)[broncode]

Callback to allow drivers to change the default file name set by create_upload_path().

sanitize_path_components(components)[broncode]

Perform any device specific sanitization on the path components for files to be uploaded to the device

get_annotations(path_map)[broncode]

Resolve path_map to annotation_map of files found on the device

add_annotation_to_library(db, db_id, annotation)[broncode]

Add an annotation to the calibre library

class calibre.devices.usbms.cli.CLI[broncode]

class calibre.devices.usbms.driver.USBMS(plugin_path)[broncode]

Basisklassen: CLI, Device

The base class for all USBMS devices. Implements the logic for sending/getting/updating metadata/caching metadata/etc.

description = 'Communiceer met een e-boek reader.'

Een korte tekst die beschrijft wat de plugin doet

author = 'John Schember'

De maker van deze plugin

supported_platforms = ['windows', 'osx', 'linux']

Lijst van platformen waar deze plugin op werkt. Bijvoorbeeld: ['windows', 'osx', 'linux']

booklist_class

alias of BookList

book_class

alias of Book

FORMATS = []

Geordende lijst met ondersteunde formaten

CAN_SET_METADATA = []

Whether the metadata on books can be set via the GUI.

get_device_information(end_session=True)[broncode]

Ask device for device information. See L{DeviceInfoQuery}.

Returns:

(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)[broncode]

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)[broncode]

Return a list of e-books on the device.

Parameters:

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.

Returns:

Een boekenlijst.

upload_books(files, names, on_card=None, end_session=True, metadata=None)[broncode]

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”.

Parameters:

- files – Een lijst met paden

- 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).

Returns:

A list of 3-element tuples. The list is meant to be passed to add_books_to_metadata().

upload_cover(path, filename, metadata, filepath)[broncode]

Upload book cover to the device. Default implementation does nothing.

Parameters:

- path – The full path to the folder where the associated book is located.

- filename – De naam van het boek bestand zonder de extensie.

- 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)[broncode]

Add locations to the booklists. This function must not communicate with the device.

Parameters:

- 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)[broncode]

Delete books at paths on device.

remove_books_from_metadata(paths, booklists)[broncode]

Remove books from the metadata list. This function must not communicate with the device.

Parameters:

- paths – paden naar boeken op het apparaat.

- 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)[broncode]

Metadata bijwerken op het apparaat.

Parameters:

booklists – A tuple containing the result of calls to (books(oncard=None)(), books(oncard='carda')(), :meth`books(oncard=’cardb’)`).

classmethod normalize_path(path)[broncode]

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)[broncode]

Basisklassen: 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.

Als twee InterfaceAction objecten dezelfde naam hebben, krijgt de gene met de hoogste prioriteit voorrang.

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

Of deze actie moet worden herhaald als de snelkoppeling-toets wordt ingedrukt.

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

Indien waar, wordt automatisch een menu gemaakt en toegevoegd aan 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)[broncode]

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)[broncode]

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)[broncode]

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)[broncode]

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.

Parameters:

- menu – The QMenu de nieuw gemaakte actie zal worden toegevoegd aan

- 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 – De tekst van de actie.

- 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 – Een beschrijving voor deze actie. Gebruikt om tooltips in te stellen.

- 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)[broncode]

If this plugin comes in a ZIP file (user added plugin), this method will allow you to load resources from the ZIP file.

Bijvoorbeeld om een afbeelding te laden:

pixmap = QPixmap()
pixmap.loadFromData(tuple(self.load_resources(['images/icon.png']).values())[0])
icon = QIcon(pixmap)

Parameters:

names – List of paths to resources in the ZIP file using / as separator

Returns:

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()[broncode]

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)[broncode]

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)[broncode]

Aangeroepen elke keer wanneer de huidige bibliotheek wijzigt.

Parameters:

- olddb – The LibraryDatabase corresponding to the previous library.

- db – The LibraryDatabase corresponding to the new library.

library_changed(db)[broncode]

Aangeroepen elke keer wanneer de huidige bibliotheek wijzigt.

Parameters:

db – The LibraryDatabase corresponding to the current library.

gui_layout_complete()[broncode]

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()[broncode]

Called once per action when the initialization of the main GUI is completed.

tag_browser_context_action(index)[broncode]

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()[broncode]

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)[broncode]

Basisklassen: Plugin

supported_platforms = ['windows', 'osx', 'linux']

Lijst van platformen waar deze plugin op werkt. Bijvoorbeeld: ['windows', 'osx', 'linux']

author = 'Kovid Goyal'

De maker van deze plugin

type = 'Gebruikersinterface actie'

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)[broncode]

This method must return the actual interface action plugin object.

Preferences plugins
class calibre.customize.PreferencesPlugin(plugin_path)[broncode]

Basisklassen: 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']

Lijst van platformen waar deze plugin op werkt. Bijvoorbeeld: ['windows', 'osx', 'linux']

author = 'Kovid Goyal'

De maker van deze plugin

type = 'Voorkeuren'

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)[broncode]

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[broncode]

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 = 'Standaardinstellingen terugzetten. U moet op ‘Toepassen’ klikken om de standaardinstellingen daadwerkelijk terug te zetten.'

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)[broncode]

Called once before the widget is displayed, should perform any necessary setup.

Parameters:

gui – The main calibre graphical user interface

initialize()[broncode]

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()[broncode]

Should set all config values to their defaults.

commit()[broncode]

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)[broncode]

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()[broncode]

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)[broncode]

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'>)[broncode]

Een instelling registreren.

Parameters:

- name – De instelling naam

- 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()[broncode]

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)[broncode]

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)[broncode]

Should set all config values to their defaults.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt

Opdrachtpromt

Notitie

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.

Gedocumenteerde commando’s

- 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

Ongedocumenteerde commando’s

- ebook-device

- markdown-calibre

U kan het gebruik van ongedocumenteerde commandos zien door ze uit te voeren zonder extra opties in een terminal.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- calibre

calibre

calibre [options] [path_to_ebook or calibre url ...]

Start calibre’s Grafische Gebruiker Interface en voeg optioneel het e-boek toe aan path_to_ebook naar de database. U kan ook calibre URLs specificeren om verscheidene taken uit te voeren, buiten boeken toevoegen. Bijvoorbeeld:

calibre://view-book/test_library/1842/epub

Opent het boek met id 1842 in het EPUB formaat uit de bibliotheek “test_library” in de calibre e-boek viewer. Bibliotheeknamen zijn de mapnamen van de bibliotheken met spaties vervangen door onderliggende streepjes. Een volledige beschrijving van de verschillende URL gebaseerde acties staat in de Gebruikershandleiding.

Als u aan calibre parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--detach

Loskoppelen van de controleterminal, indien aanwezig (alleen Linux)

--help, -h

toon dit helpbericht en stop

--ignore-plugins

Aangepaste plug-ins negeren. Nuttig als u een plug-in geïnstalleerd hebt waardoor calibre niet kan starten

--no-update-check

Niet op nieuwe versies controleren

--shutdown-running-calibre, -s

Sluit, indien aanwezig, een actieve calibre-instantie af. Bedenk dat eventuele draaiende taken zonder melding worden afgebroken. Gebruik deze optie voorzichtig.

--start-in-tray

Geminimaliseerd naar het systeemvak starten.

--verbose, -v

Genegeerd: gebuik dit niet. Alleen aanwezig voor compatibiliteitsredenen

--version

toon programmaversie en stop

--with-library

Gebruik de bibliotheek op de opgegeven locatie.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- calibre-customize

calibre-customize

calibre-customize opties

Pas calibre aan door externe plug-ins te laden.

Als u aan calibre-customize parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--add-plugin, -a

Voeg een plug-in toe door het pad op te geven naar z'n ZIPbestand.

--build-plugin, -b

Voor plug-in ontwikkelaars: Pad naar de map waar u de plug-in ontwikkelt. Dit commando zal de plug-in automatisch zippen en updaten in 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

Deactiveer de genoemde plug-in

--enable-plugin

Activeer de genoemde plug-in

--help, -h

toon dit helpbericht en stop

--list-plugins, -l

Toon alle geïnstalleerde plug-ins

--remove-plugin, -r

Verwijder een aangepaste plug-in op naam. Heeft geen effect op standaard plug-ins

--version

toon programmaversie en stop

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- calibre-debug

calibre-debug

calibre-debug [opties]

Verscheidene commandoregel interfaces nuttig voor foutzoeken in calibre. Zonder opties start dit commando een geïntegreerde Python vertaler. U kan ook de hoofd calibre GUI gebruiken, het calibre leesvenster en de calibre bewerker in de foutzoekmodus.

Het bevat ook interfaces voor verschillende stukjes van calibre die geen specifieke commandoregel gereedschappen hebben, zoals font subsets, de e-boek diff tool en zo voort.

U kan ook calibre-debug gebruiken om zelfstandige scripts te draaien. Gebruik het daarvoor zo:

calibre-debug -e myscript.py -- --option1 --option2 file1 file2 …

Alles na de -- wordt doorgegeven aan het script. U kan ook calibre foutzoeken gebruiken als een shebang in scripts, zo:

#!/usr/bin/env -S calibre-debug -e -- –

Als u aan calibre-debug parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--add-simple-plugin

Voeg een eenvoudige plug-in toe (b.v. een plug-in die één .py bestand bevat) door het pad aan te geven naar het py-bestand die de code voor de plug-in bevat.

--command, -c

Python code uitvoeren.

--debug-device-driver, -d

Fouten in toesteldetectie zoeken

--default-programs

calibre (af)melden als Windows standaard programma. --default-programs = (register|unregister)

--diff

Draait de calibre diff tool. Bijvoorbeeld: calibre-debug --diff bestand1 bestand2

--edit-book

Start het calibre "Bewerk Boek" tool in debug modus.

--exec-file, -e

Python code in het bestand uitvoeren.

--explode-book, -x

Explodeer het boek in de gespecificeerde map. Gebruik: -x file.epub output_dir Exporteert het boek als een verzameling HTML bestanden en metadata die u kan bewerken met standaard HTML gereedschappen. Werkt met EPUB, AZW3, HTMLZ en DOCX bestanden.

--export-all-calibre-data

Exporteer alle Calibre-gegevens (boeken/instellingen/plug-ins). Normaal gesproken wordt u gevraagd naar de exportmap en de bibliotheken die u wilt exporteren. U kunt ze ook opgeven als opdrachtregel argumenten om de vragen over te slaan. Gebruik absolute paden voor de exportmap en bibliotheken. Het speciale trefwoord "all" kan worden gebruikt om alle bibliotheken te exporteren. Voorbeelden: calibre-debug --export-all-calibre-data # voor interactief gebruik calibre-debug --export-all-calibre-data /path/to/empty/export/folder /path/to/library/folder1 /path/to/library2 calibre-debug --export-all-calibre-data /export/folder all # exporteer alle bekende bibliotheken

--fix-multiprocessing

Voor intern gebruik

--gui, -g

Start de GUI met de probleemoplosser ingeschakeld. De output wordt afgedrukt naar stdou en stderr.

--gui-debug

Voer de GUI uit met de debug console, log naar het gespecificeerd pad. Enkel intern gebruik, gebruik de -g optie om de GUI in debug modus uit te voeren

--help, -h

toon dit helpbericht en stop

--implode-book, -i

Implodeer een eerder geëxplodeerd boek. Gebruik: -i output_dir file.epub Importeert het boek vanuit bestanden in output_dir die gecreëerd werden bij een vorige aanroep van --explode-book. Zorg ervoor hetzelfde bestandstype op te geven als bij het exploderen.

--import-calibre-data

Importeer eerder geëxporteerde calibre gegevens

--inspect-mobi, -m

Inspecteer MOBI-bestand(en) in opgegeven pad(en)

--paths

Output van de paden de nodig zijn voor de installatie van de calibre omgeving

--run-plugin, -r

plug-inplug-inVoer een plugin uit welke via opdracht-prompt kan starten. Bijvoorbeeld: calibre-debug -r "Plug-in naam" -- bestand1 --option1 Alles na de -- zal worden meegegeven aan de plugin als argumenten.

--run-test, -t

Voer de genoemde test(s) uit. Gebruik de speciale waarde "alle" om alle tests uit te voeren. Als de testnaam begint met een punt, wordt aangenomen dat het een modulenaam is. Als de testnaam begint met @, wordt aangenomen dat het een categorienaam is.

--run-without-debug

Niet uitvoeren met de DEBUG flag set

--shutdown-running-calibre, -s

Sluit, indien aanwezig, een actieve calibre-instantie af. Bedenk dat eventuele draaiende taken zonder melding worden afgebroken. Gebruik deze optie voorzichtig.

--subset-font, -f

Wissel het gespecifieerde lettertype. Gebruik -- na deze optie om optie door te geven naar lettertype wisselprogramma.

--test-build

Test binaire modules in build

--version

toon programmaversie en stop

--viewer, -w

E-boek leesvenster in debug modus uitvoeren

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- calibre-server

calibre-server

calibre-server [options] [path to library folder...]

Start de calibre Content server. De calibre Content server toont uw calibre bibliotheken via het internet. U kan het pad naar de bibliotheek mappen specificeren als argumenten bij calibre-server. Als u geen pad specificeert, worden alle bibliotheken die het calibre hoofdprogramma kent, gebruikt.

Als u aan calibre-server parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--access-log

Pad naar toegangslogbestand. Dit houdt informatie bij over clients die verbinding maken met de server en aanvragen indienen. Standaard worden geen toegangslogbestanden bijgehouden.

--ajax-timeout

Tijd (in seconden) om te wachten op een antwoord van de server bij zoekopdrachten.

--auth-mode

Kies het type authenticatie dat gebruikt is. Stel de HTTP authenticatie modus in gebruikt door de server. Op "basis" als u de server achter een proxy zet, laat het anders op "auto" wat "basis" gebruikt als SSL is ingesteld, anders gebruikt het "digest".

--auto-reload

Herstart automatisch wanneer de bron code veranderd. Handig voor ontwikkeling. U zou ook een kleine waarde moeten geven aan de afsluit timeout.

--ban-after

Aantal mislukte inlogpogingen voor bannen. Het aantal mislukte inlogpogingen waarna een IP adres wordt geband

--ban-for

Ban IP adressen met meerdere mislukte inlogpogingen. Bant tijdelijk IP adressen die meerdere mislukte inlogpogingen hebben voor het gespecificeerde aantal minuten. Nuttig om pogingen tot wachtwoord raden te voorkomen. Indien ingesteld op 0 wordt er niet geband.

--book-list-mode

De standaard boekenlijst modus kiezen. Stel de standaard boekenlijst modus in voor nieuwe gebruikers. Individuele gebruikers kunnen de standaard negeren in hun instellingen. De standaard is een omslagraster.

--compress-min-size

Minimale grootte waarbij antwoorden data compressie gebruiken (in bytes).

--custom-list-template

Pad naar een JSON bestand met een sjabloon voor de aangepaste boekenlijst modus. De gemakkelijkste manier om zo'n sjabloonbestand te creëren is via Voorkeuren->Delen->Delen via (inter)net->Boek lijst sjabloon in calibre, creëer de sjabloon en exporteer ze.

--daemonize

Draai het proces in de achtergrond als een daemon (enkel Linux)

--displayed-fields

Getoonde gebruiker gedefinieerde velden beperken. Komma gescheiden lijst met door de gebruiker gedefinieerde metadatavelden die door de Content server in de /opds en /mobile weergaven worden getoond. Als u deze optie selecteert, worden velden die niet in deze lijst zijn opgenomen niet weergegeven. Bv.: my_rating,my_tags

--enable-allow-socket-preallocation, --disable-allow-socket-preallocation

Socket voor-toewijzing, bv. bij systemd socket activatie. Deze optie is standaard ingeschakeld.

--enable-auth, --disable-auth

Wachtwoord gebaseerde authenticatie voor toegang tot de server. Normaal heeft de server geen beperkingen en geeft iedereen toegang. Met deze optie kan u toegang beperken tot vooraf gedefinieerde gebruikers. Deze optie is standaard uitgeschakeld.

--enable-fallback-to-detected-interface, --disable-fallback-to-detected-interface

Terug vallen op automatisch gedetecteerde interface. Als de server voor een bepaalde reden niet in staat is om te "bind"en met de interface gespecificeerd in listen_on, probeert hij een interface te detecteren die met de buitenwereld verbindt en bind daarmee. Deze optie is standaard ingeschakeld.

--enable-local-write, --disable-local-write

Laat niet-geauthentiseerde lokale verbindingen veranderingen maken. Normaal, als u authenticatie niet aanzet, werkt de server in alleen-lezen modus zodat anonieme gebruikers geen veranderingen kunnen aanbrengen in uw calibre bibliotheken. Deze optie laat iedereen die verbindt vanop dezelfde computer als waarop de server draait veranderingen maken. Nuttig als u de server wilt draaien zonder authenticatie maar toch calibredb wilt gebruiken om uw bibliotheken te wijzigen. Merk op dat deze optie aanzetten betekent dat elk programma op de computer uw calibre bibliotheken kan wijzigen. Deze optie is standaard uitgeschakeld.

--enable-log-not-found, --disable-log-not-found

HTTP 404 (Niet Gevonden) verzoeken loggen. Normaal logt de server alle HTTP verzoeken voor bronnen die hij niet vond. Dit kan een hoop log spam genereren als uw server het doel wordt van bots. Gebruik deze optie om het af te zetten. Deze optie is standaard ingeschakeld.

--enable-use-bonjour, --disable-use-bonjour

Adverteer OPDS feeds via BonJour. Adverteer de OPDS feeds via de BonJour dienst, zodat OPDS gebaseerde reading apps de server kunnen detecteren en automatisch verbinden. Deze optie is standaard ingeschakeld.

--enable-use-sendfile, --disable-use-sendfile

Zero copy file transfers for increased performance. This will use zero-copy in-kernel transfers when sending files over the network, increasing performance. However, it can cause corrupted file transfers on some broken filesystems. If you experience corrupted file transfers, turn it off. Deze optie is standaard ingeschakeld.

--help, -h

toon dit helpbericht en stop

--ignored-fields

Genegeerde door de gebruiker gedefinieerde metadata velden. Door komma's gescheiden lijst van door de gebruiker gedefinieerde metadata gegevensvelden die niet door de inhoudsserver in de weergaven /OPDS en /mobiel worden weergegeven. Bijvoorbeeld: my_rating,my_tags

--listen-on

De interface waarop te luisteren voor verbindingen. 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

Pad naar logbestand voor serverlog. Dit logbestand bevat servergegevens en fouten, maar geen toegangslogs. Het wordt standaard naar stdout geschreven.

--manage-users

Beheer de database met gebruikers die met deze server mogen verbinden. U kan het in automatische modus gebruiken door toevoeging van een –. Bekijk calibre-server --manage-users -- help voor details. Bekijk ook de --userdb optie.

--max-header-line-size

Max. grootte van enkele HTTP header (in KB).

--max-job-time

Maximale tijd voor werkprocessen. Maximale tijd dat werker processen mogen draaien (in minuten). Stel in op nul voor geen limiet.

--max-jobs

Maximale aantal werkprocessen. Werker processen worden gelanceerd indien nodig en gebruikt voor grote taken zoals voorbereiden van een boek om te tonen, boeken toevoegen, converteren, enz. Normaal is het maximale aantal gebaseerd op het aantal CPU kernen. U kan het controleren met deze instelling.

--max-log-size

Max. logfile grootte (in MB). Maximum grootte van log bestanden gegenereerd door de server. Als de log groter wordt, wordt het automatisch geroteerd. Zet op nul om log rotatie uit te schakelen.

--max-opds-items

Maximale aantal boeken in OPDS feeds. Maximum aantal boeken dat de server terug geeft in een enkele OPDS verkrijgingsfeed.

--max-opds-ungrouped-items

Maximaal aantal niet-gegroepeerde items in OPDS-feeds. Groepeer items in categorieën zoals auteur/labels per beginletter wanneer er meer items dan dit aantal zijn. Stel op nul in om uit te schakelen.

--max-request-body-size

Toegestane max. bestandsgrootte om naar server te uploaden (in MB).

--num-per-page

Aantal boeken te tonen op een enkele pagina. Aantal boeken te tonen op een enkele pagina in de browser.

--pidfile

Schrijf het PID van het proces naar het opgegeven bestand

--port

De poort waarop geluisterd word naar verbindingen.

--search-the-net-urls

Pad naar JSON bestand met URLs voor de "Doorzoek het internet" functie. De gemakkelijkste manier om zo'n bestand te creëren is via Voorkeuren->Delen->Delen via (inter)net->Doorzoek het internet in calibre, creëer de URLs en exporteer ze.

--shutdown-timeout

Totaaltijd in seconden om te wachten om netjes af te sluiten.

--ssl-certfile

Pad naar bestand SSL certificaat.

--ssl-keyfile

Pad naar bestand SSL persoonlijke sleutel.

--timeout

Tijd (in seconden) totdat een inactieve verbinding wordt gesloten.

--trusted-ips

Laat niet-geauthentiseerde verbindingen van specifieke IP adressen veranderingen maken. Normaal, als u authenticatie niet aanzet, werkt de server in alleen-lezen modus zodat anonieme gebruikers geen veranderingen kunnen aanbrengen in uw calibre bibliotheken. Deze optie laat iedereen die verbindt vanop specifieke IP adressen veranderingen maken. Moet een komma gescheiden lijst zijn van adressen of netwerk specificaties. Nuttig als u de server wilt draaien zonder authenticatie maar toch calibredb wilt gebruiken om uw bibliotheken te wijzigen. Merk op dat deze optie aanzetten betekent dat iedereen die verbindt vanop de gespecificeerde adressen uw calibre bibliotheken kan wijzigen.

--url-prefix

Een prefix om voor alle URL's te zetten. Nuttig als u deze server achter een omgekeerde proxy wil draaien. Bv. gebruik /calibre als het URL voorvoegsel.

--userdb

Pad naar de gebruikersdatabase voor authenticatie. De database is een SQLite bestand. Gebruik --manage-users om het te creëren. U kan meer lezen over gebruikersbeheer op: https://manual.calibre-ebook.com/nl/server.html#managing-user-accounts-from-the-command-line-only

--version

toon programmaversie en stop

--worker-count

Aantal werker threads gebruikt om verzoeken te verwerken.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- calibre-smtp

calibre-smtp

calibre-smtp [opties] [van naar tekst]

Verzend een email met het SMTP protocol. calibre-smtp heeft 2 manieren van werken, in de schrijf modus kan u een van / to en text specifieren en deze zullen dan gebruikt worden voor het opstellen en verzenden van een email boodschap. In de filter modus, zal calibre-smtp een complete email inlezen uit STDIN en het dan verzenden
tekst is de tekst in het email bericht Als tekst niet is gespecifieerd, dan zal een complete email uitgelezen worden vanuit STDIN van is het email adres van de verzender en to die van de ontvanger Wanneer een complete email is uitgeleden vanuit STDIN, worden van en naar enkel gebruikt bij het SMTP onderhandeling, de bericht headers worden niet aangepast.

Als u aan calibre-smtp parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--fork, -f

Fork en verzend berichten in de achtergrond. Indien u deze optie gebruikt zou u ook de --outbox moeten gebruiken om verzend problemen op te vangen.

--help, -h

toon dit helpbericht en stop

--localhost, -l

Computernaam van deze pc. Gebruikt bij verbinden met SMTP server.

--outbox, -o

Pad naar mail-map om mislukte e-mails in op te slaan.

--timeout, -t

Time-out voor verbinding

--verbose, -v

Wees woordrijker

--version

toon programmaversie en stop

COMPOSE MAIL

Opties om een e-mail op te stellen. Genegeerd als tekst niet is opgegeven

--attachment, -a

Bestand om toe te voegen aan email

--subject, -s

Onderwerp van de email

SMTP RELAY

Opties om een SMTP relay server te gebruiken om mail te verzenden. calibre zal proberen om rechtstreeks een email te versturen, tenzij –relay wordt meegegeven.

--cafile

Path to a file of concatenated CA certificates in PEM format, used to verify the server certificate when using TLS. By default, the system CA certificates are used.

--dont-verify-server-certificate

Do not verify the server certificate when connecting using TLS. This used to be the default behavior in calibre versions before 3.27. If you are using a relay with a self-signed or otherwise invalid certificate, you can use this option to restore the pre 3.27 behavior

--encryption-method, -e

Encryptie methode gebruiken bij verbinden van relay. Keuzes zijn TLS, SSL en NONE. Standaard is TLS. WAARSCHUWING: NONE kiezen is zeer onveilig

--password, -p

Wachtwoord voor relay

--port

Poort om te connecteren naar een relay server. Standaard wordt 465 gebruikt indien encryptie actief is, en 25 anders.

--relay, -r

Een SMTP relay server gebruiken om email te verzenden.

--username, -u

Gebruikersnaam voor relay

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- calibredb

calibredb

%prof commando [opties] [parameters]

calibrebd is de command line interface naar de calibre database. Het heeft verschillende sub-commando’s, hieronder gedocumenteerd.

calibrebd kan gebruikt worden om ofwel een calibre database te manipuleren gespecificeerd bij pad of een calibre Content server draaiend ofwel op de lokale machine of over het internet. U kan een calibre Content server starten ofwel met het calibre-server programma ofwel klik in het calibre hoofdprogramma op Connect/share  → Start Content Server. Aangezien calibredb veranderingen kan aanbrengen in uw calibre bibliotheken, moet u eerst authenticatie instellen op de server. Er zijn twee manieren om dat te doen:

- Als u enkel wil verbinden met een server die draait op dezelfde computer,

kan u gewoon de --enable-local-write optie gebruiken van de Content server, om elk programma, inbegrepen calibre, dat draait op de lokale computer om dingen te veranderen in uw calibre data. Als u de server vanuit het calibre hoofdprogramma draait, is deze optie in Preferences → Sharing over het internet → Geavanceerd.

- Als u toegang over het internet wil inschakelen, dan moet u gebruikersaccounts instellen op de server en de opties --username en --password gebruiken op calibredb om het toegang te verlenen. U kan gebruikerauthenticatie instellen voor calibre-server met de --enable-auth optie en gebruik makend van --manage-users om de gebruikersaccounts te creëren. Als u de server draait vanuit het calibre hoofdprogramma, gebruik Voorkeuren → Delen over het net → Vraag gebruikersnaam/wachtwoord.

Om te verbinden met een draaiende Content server, geef de URL van de server door aan de --with-library optie, bekijk de documentatie van die optie voor details en voorbeelden.

- ALGEMENE OPTIES

- list

- add

- TOEVOEGEN VANUIT MAPPEN

- remove

- add_format

- remove_format

- show_metadata

- set_metadata

- export

- catalog

- EPUB OPTIES

- 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

ALGEMENE OPTIES

--help, -h

toon dit helpbericht en stop

--library-path, --with-library

Pad naar calibre bibliotheek. Standaard wordt het pad gebruikt opgeslagen in de instellingen. U kan ook met een calibre Content server verbinden om te wijzigen in bibliotheken op afstand. Om dit te doen, gebruik een URL van de vorm: http://hostname:port/#library_id bv, http://localhost:8080/#mylibrary. library_id is de id van de bibliotheek waarmee u wilt verbinden op de Content server. U kan de speciale library_id gebruiken van - om een lijst te krijgen van beschikbare ids op de server. Voor details over het toegang krijgen via een Content server, zie https://manual.calibre-ebook.com/nl/generated/nl/calibredb.html.

--password

Wachtwoord om te verbinden met een calibre Content server. Gebruik speciale waarde: <stdin> om het wachtwoord van de standaardinvoer te lezen. Om het wachtwoord van een bestand te lezen, gebruik: <f:/path/to/file> (d.w.z. <f: gevolgd door volledig pad naar bestand en een trailing>). De punthaken in het bovenstaande zijn vereist, onthoud om ze te escapen of gebruik aanhalingstekens voor uw shell.

--timeout

De time-out, in seconden, bij verbinden met een calibre bibliotheek over het netwerk. De standaard is twee minuten.

--username

Gebruikersnaam voor verbinden met een calibre Content server

--version

toon programmaversie en stop

list

calibredb list [opties]

Toon de beschikbare boeken in de calibre-database.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--ascending

Sorteer resultaten in oplopende volgorde

--fields, -f

Velden te tonen bij het maken van boekenlijsten in de database. Moet een lijst met komma-gescheiden waarden zijn. Beschikbare velden: author_sort, authors, comments, cover, formats, identifiers, isbn, languages, last_modified, pubdate, publisher, rating, series, series_index, size, tags, template, timestamp, title, uuid Standaard: title,authors. Het speciale veld "alle" kan dienen om alle velden te selecteren. Naast de ingebouwde velden boven zijn ook aangepaste velden beschikbaar als *field_name, bijvoorbeeld, voor een aangepast veld #rating, gebruik de naam: *rating

--for-machine

Genereer uitvoer in JSON formaat, dit is veel beter voor machine verificatie. Zorgt ervoor dat de lengte van lijnen en de afscheidings opties genegeerd zullen worden.

--limit

Het maximum aantal resultaten om weer te geven. Standaard: allen

--line-width, -w

De maximale breedte van een enkele regel in de uitvoer. Standaard is dit hetzelfde als de schermgrootte.

--prefix

De prefix voor mappaden. Standaard is het absolute pad naar de bibliotheekmap.

--search, -s

Filter de resultaten op de zoekoperatie. Voor het indeling van de zoekopdracht kunt u de zoek-gerelateerde documentatie in de gebruikershandleiding raadplegen. Standaard word er niet gefilterd.

--separator

De string gebruikt om velden van elkaar te scheiden. Standaard is dit een spatie.

--sort-by

Het veld waarop de resultaten moeten worden gesorteerd. U kunt meerdere velden specificeren door ze te scheiden met komma's. Beschikbare velden: author_sort, authors, comments, cover, formats, identifiers, isbn, languages, last_modified, pubdate, publisher, rating, series, series_index, size, tags, template, timestamp, title, uuid Standaard: id

--template

Het sjabloon dat moet worden uitgevoerd als "template" in de veld-lijst staat. Standaard: Geen

--template_file, -t

Pad naar een bestand met het sjabloon dat moet worden uitgevoerd als "template" in de veld-lijst staat. Standaard: Geen

--template_heading

Kop voor de sjabloonkolom. Standaard: stan%daard. Deze optie wordt genegeerd als de optie --for-machine is ingesteld

add

calibredb toevoegen [opties] bestand1 bestand2 bestand3 ...

Voeg de gespecificeerde bestanden als boeken toe aan de database. U kan ook mappen specificeren, kijk bij map gerelateerde opties beneden.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--authors, -a

Stel de auteur in voor toegevoegde boek(en)

--automerge, -m

Als boeken met vergelijkbare titels en auteurs worden gevonden, voegt u de binnenkomende formaten (bestanden) automatisch samen met bestaande boekrecords. De waarde "negeren" betekent dat dubbele formaten worden verwijderd. De waarde "overschrijven" betekent dat dubbele formaten in de bibliotheek worden overschreven door de nieuw toegevoegde bestanden. De waarde "new_record" betekent dat dubbele formaten in een nieuw boekrecord worden geplaatst.

--cover, -c

Pad naar de omslag voor het toegevoegde boek

--duplicates, -d

Voeg boeken toe aan de database, zelfs als ze al bestaan. Er wordt vergeleken op basis van boektitels en auteurs. Merk op dat de optie --automerge voorrang heeft.

--empty, -e

Voeg een leeg boek toe (een boek dat van geen enkel formaat gebruikt maakt)

--identifier, -I

Stel de identificatren voor dit boek in, bijvoorbeeld: -I asin:XXX -I isbn:YYY

--isbn, -i

Stel het ISBN-nummer in voor toegevoegde boek(en)

--languages, -l

Een door komma's gescheiden lijst van talen (het beste is om ISO639 taal codes te gebruiken, alhoewel sommige taal-namen ook herkend worden)

--series, -s

Stel de series in van toegevoegde boek(en)

--series-index, -S

Stel het nummer in de serie in voor toegevoegde boek(en)

--tags, -T

Stel labels in voor toegevoegde boek(en)

--title, -t

Stel de titel in voor toegevoegde boek(en)

TOEVOEGEN VANUIT MAPPEN

Opties voor beheer van boeken toevoegen vanuit mappen. Standaard enkel bestanden met extensies van gekende e-boek bestandtypes.

--add

Een bestandsnaam (glob) patroon, bestanden die overeenkomen met dit patroon zullen worden toegevoegd bij het scannen van mappen voor bestanden, zelfs als ze geen bekend e-boek bestandstype hebben. Kan meerdere keren worden opgegeven voor meerdere patronen.

--ignore

Een bestandsnaam (glob) patroon, bestanden die overeenkomen met dit patroon, worden genegeerd bij het scannen van mappen. Kan meerdere keren worden opgegeven voor meerdere patronen. Bijvoorbeeld: *.pdf zal alle pdf-bestanden negeren

--one-book-per-directory, -1

Neem aan dat elke map een enkel logisch boek heeft and dat alle bestanden erin verschillende e-boek formaten zijn van dat boek

--recurse, -r

Verwerk mappen recursief

remove

calibredb verwijder ID's

Verwijder de boek indentificatoren van de database. ID’s is een komma-gescheiden lijst van ID nummers (u kan ID nummers verkrijgen door het zoek commando te gebruiken). Bijvoorbeeld: 23,34,57-85 (bij opgave van een serie, wordt het laatste nummer niet opgenomen).

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--permanent

Gebruik de Prullenbak niet

add_format

calibredb add_format [opties] id ebook_file

Voeg het e-boek in ebook_file toe aan de beschikbare formaten voor het logische boek geïdentificeerd door id. U kunt id krijgen door de zoekopdracht te gebruiken. Als het formaat al bestaat, wordt het vervangen, tenzij de optie Niet vervangen is opgegeven.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--as-extra-data-file

Voeg het bestand toe als een extra gegevensbestand aan het boek, niet als e-boekindeling

--dont-replace

Vervang het formaat niet als het al bestaat

remove_format

calibredb remove_format [opties] id fmt

Verwijder het formaat fmt van het logische boek geïdentificeerd door id. U kunt het id met het zoek-commando vinden. fmt is een bestandsextensie zoals LRF, TXT of EPUB. Als het logische boek niet in dit formaat bestaat zal er niets gebeuren.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

show_metadata

calibredb show_metadata [opties] id

Toon de metadata die in de calibre database zijn opgeslagen voor het boek dat bij id hoort. id is een id nummer verkregen met het zoek-commando.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--as-opf

Metadata in OPF-formaat (XML) afdrukken

set_metadata

calibredb set_metadata [options] book_id [/path/to/metadata.opf]

Stel de metadata in die zijn opgeslagen in de Calibre-database voor het boek geïdentificeerd door book_id uit het OPF-bestand metadata.opf. book_id is een boek-id-nummer van de zoek opdracht. U kunt snel een idee krijgen van het OPF-formaat door de –as-opf schakelt over naar de opdracht show_metadata. U kunt ook de metagegevens van individuele velden met de optie –field. Als u de optie –field gebruikt, is er het is niet nodig om een OPF-bestand op te geven.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--field, -f

Het veld om te zetten. Formaat is veld_naam:waarde, bijvoorbeeld: --field labels:label1,label2. Gebruik --list-fields om een lijst van alle veld namen te krijgen. U kunt deze optie meermalen gebruiken om meerdere velden te zetten. Opgelet: Voor talen moet u de ISO639 taal codes gebruiken (b.v. en voor Engels, fr voor Frans enzovoort). Voor identificatoren, de syntaxis is --field identifiers:isbn:XXXX,doi:YYYYY. Voor booleaans (ja/nee) velden gebruik waar en niet waar of ja en nee.

--list-fields, -l

Toon de metadata veldnamen die kunnen worden gebruikt met de --field optie

export

calibredb export [options] ids

Export the books specified by ids (a comma separated list) to the filesystem. The export operation saves all formats of the book, its cover and metadata (in an OPF file). Any extra data files associated with the book are also saved. You can get id numbers from the search command.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--all

Exporteer alle boeken in de database, negeer de lijst met id's.

--dont-asciiize

Laat calibre alle niet-Latijnse karakters converteren naar Latijnse voor de bestandsnamen. Nuttig bij opslaan naar een verouderd bestandssysteem zonder volledige ondersteuning voor Unicode bestandsnamen. Als u dit opgeeft zal het gedrag uitgeschakeld worden.

--dont-save-cover

Normaal slaat calibre de omslag op in een apart bestand samen met de overige boekbestanden van het bijbehorende boek. Als u dit opgeeft zal het gedrag uitgeschakeld worden.

--dont-save-extra-files

Save any data files associated with the book when saving the book Als u dit opgeeft zal het gedrag uitgeschakeld worden.

--dont-update-metadata

Normaal zal calibre de metadata in opgeslagen bestanden bijwerken met informatie in de calibre bibliotheek. Maakt opslaan op schijf langzamer. Als u dit opgeeft zal het gedrag uitgeschakeld worden.

--dont-write-opf

Normaal gesproken, zal calibre de metadata in een apart OPF-bestand opslaan, samen met de daadwerkelijke e-boekbestanden. Als u dit opgeeft zal het gedrag uitgeschakeld worden.

--formats

Kommagescheiden lijst van opslagformaten voor elk boek. Standaard worden alle formaten bewaard.

--progress

Rapporteer voortgang

--replace-whitespace

Vervang witruimte door lage streepjes (_).

--single-dir

Exporteer alle boeken naar één enkele map

--template

De sjabloon waarmee de bestandsnaam en mapstructuur van de opgeslagen bestanden wordt gecontroleerd. Standaard is dit "{author_sort}/{title}/{title} - {authors}’" dat boeken per auteur in een submap plaatst met bestandsnamen met de naam van de auteur en de titel. Beschikbare opties zijn: {author_sort, authors, id, isbn, languages, last_modified, pubdate, publisher, rating, series, series_index, tags, timestamp, title}

--timefmt

Het formaat om gegevens weer te geven. %d - dag, %b - maand, %m - maandnummer, %Y - jaar. Standaard is: %b, %Y

--to-dir

Exporteer boeken naar de gespecificeerde map. Standaard is .

--to-lowercase

Paden naar kleine letters omzetten.

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.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--ids, -i

Kommagescheiden lijst met database-ID's om te catalogiseren. Indien gebruikt, zal --search genegeerd worden. Standaard: all

--search, -s

Filter de resultaten met de zoekopdracht. Voor het formaat van de zoekopdracht, zie de zoekfunctionaliteit in de gebruikershandleiding. Standaard: geen filter

--verbose, -v

Laat gedetailleerde uitvoerinformatie zien. Nuttig bij het opsporen van problemen

EPUB OPTIES

--catalog-title

Titel van gegenereerde catalogus gebruikt als titel in metadata. Standaard: 'My Books' Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten

--cross-reference-authors

Creëert kruis-referenties in Auteursonderdeel voor boeken met meerdere auteurs. Standaard: 'False' Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten.

--debug-pipeline

Sla de uitvoer van verschillende stadia in de omzettingspijplijn op in de gespecificeerde map. Nuttig wanneer u onzeker bent in welk stadium van het omzettingsproces een fout optreedt. Standaard: 'None' Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten

--exclude-genre

Regex die beschrijft welke labels uit te sluiten als genre's. Standaard: '[.+]|^+$' sluit labels in haken uit, b.v. '[Project Gutenberg]', en '+', de standaard voor gelezen boeken. Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten

--exclusion-rules

Specificeert de regels gebruikt om boeken uit te sluiten van de aangemaakt catalogus. Het model voor een uitsluitregel is ofwel ('<rule name>','Tags','<comma-separated list of tags>') of ('<rule name>','<custom column>','<pattern>'). Bijvoorbeeld: (('Gearchiveerde boeken','#status','Gearchiveerd'),) zal een boek uitsluiten met de waarde 'Gearchiveerd' in de aangepaste kolom 'status'. Wanneer meerder kolommen zijn gedefinieerd, worden alle regels toegepast. Standaard: "(('Catalogs','Tags','Catalog'),)" Geldt voor AZW3, EPUB, MOBI uitvoerformaten

--generate-authors

Voeg 'Auteurs' onderdeel toe aan catalogus. Standaard: 'False' Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten

--generate-descriptions

Voeg 'Beschrijvingen' onderdeel toe aan catalogus. Standaard: 'False' Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten

--generate-genres

Voeg 'Genres' onderdeel toe aan catalogus. Standaard: 'False' Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten

--generate-recently-added

Voeg 'Onlangs toegevoegd' onderdeel toe aan catalogus. Standaard: 'False' Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten

--generate-series

Voeg 'Series' onderdeel toe aan catalogus. Standaard: 'False' Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten

--generate-titles

Voeg 'Titels' onderdeel toe aan catalogus. Standaard: 'False' Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten

--genre-source-field

Bronveld voor Genres onderdeel. Standaard: 'Labels' Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten

--header-note-source-field

Aangepast veld met in de Beschrijving koptekst in te voegen notitie tekst. Standaard: '' Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten

--merge-comments-rule

#<custom field>:[voor|na]:[Waar|Onwaar] specificerend: <custom field> Aangepast veld met nota's samen te voegen met commentaar [voor|na] Plaatsing van nota's ten opzichte van commentaar [Waar|Onwaar] - Een horizontale regel wordt ingevoegd tussen nota's en commentaar Standaard: '::' Geldt voor: AZW3, EPUB, MOBI outvoerformaten

--output-profile

Specificeert het uitvoerprofiel. In sommige gevallen is een uitvoerprofiel vereist om de catalogus te optimaliseren voor het toestel. Bijvoorbeeld 'kindle' of 'kindle_dx' creëert een gestructureerde Inhoudsopgave met Onderdelen en Artikelen Standaard: 'None' Geldt voor AZW3, EPUB, MOBI uitvoerformaten

--prefix-rules

Specificeert de regels gebruikt voor het insluiten van voorvoegsels die gelezen boeken aanduiden, wenslijst items en andere gebruiker gespecificeerde voorvoegsels. Het model voor een voorvoegsel regel is ('<rule name>','<source field>','<pattern>','<prefix>'). Wanneer meerdere regels gedefinieerd zijn, wordt de eerste regel gebruikt. Standaard: "(('Read books','tags','+','✓'),('Wishlist item','tags','Wishlist','×'))" Geldt voor AZW3, EPUB, MOBI uitvoerformaten

--preset

Gebruik een benoemde voorinstelling gemaakt met de GUI catalogus bouwer. Een voorinstelling specificeert alle instellingen om een catalogus te maken. Standaard: 'None' Van toepassing op AZW3, EPUB, MOBI uitvoerformaten

--thumb-width

Formaat tip (in inches) voor boekomslagen in catalogus. Bereik: 1.0 - 2.0 Standaard: '1.0' Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten

--use-existing-cover

Vervang bestaande omslag bij genereren van de catalogus. Standaard: 'False' Van toepassing op: AZW3, EPUB, MOBI uitvoerformaten

saved_searches

calibredb saved_searches [options] (list|add|remove)

Beheer de opgeslagen zoekopdrachten in deze database. Als u een zoekopdracht met dezelfde naam wil opslaan, dan zal de oude vervangen worden.

Syntax om toe te voegen:

calibredb saved_searches add search_name search_expression

Syntax om te verwijderen:

calibredb saved_searches remove search_name

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

add_custom_column

calibredb add_custom_column [opties] label naam gegevenstype

Creëer een zelf gedefinieerde kolom. label is de machine vriendelijke naam van de kolom. Dit label

mag geen spaties of dubbele punten bevatten, naam is de mens vriendelijke naam van de kolom.

gegevenstype is één van: bool, comments, composite, datetime, enumeration, float, int, rating, series, text

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--display

Een woordenboek van opties voor het aanpassen van hoe de gegevens in deze kolom worden geïnterpreteerd. Dit is een JSON string. Voor kolommen met opsommingen gebruik --display"{\ "enum_values\ ":[\ "val1\ ", \ "val2\ "]}" Er bestaan vele opties voor de weergave variabele. De opties per kolomtype zijn: samenstelling: composite_template, composite_sort, make_category,contains_html, use_decorations datum/tijd: date_format opsomming: enum_values, enum_colors, use_decorations int, float: number_format tekst: is_names, use_decorations De beste manier om geldige combinaties te vinden is door het maken van een aangepaste kolom van het juiste type in de GUI en daarna in de back-up OPF zoeken naar een boek (verzeker u ervan dat er een nieuw OPF is aangemaakt nadat de kolom is toegevoegd). U zult de JSON zien voor de "weergave" van de nieuwe kolom in de OPF.

--is-multiple

Deze kolom slaat label like data op (i.e. waarden gescheiden door komma's). Enkel van toepassing als data tekst is.

custom_columns

calibredb custom_columns [options]

Lijst van beschikbare aangepaste kolommen. Geeft kolommen en ID’s weer.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--details, -d

Toon details voor iedere kolom.

remove_custom_column

calibredb remove_custom_column [options] label

Verwijder de door het label geïdentificeerde aangepaste kolom. U kan de beschikbare kolommen bekijken door het custom_columns commando te gebruiken.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--force, -f

Niet om bevestiging vragen

set_custom

calibredb set_custom [options] column id value

Stel de waarde van de aangepaste kolom in voor het boek, die geïdentificeerd is op ID. U kunt een lijst van ids krijgen met behulp van het zoek commando. U kunt een lijst met aangepaste kolomnamen krijgen met behulp van het custom_columns commando.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--append, -a

Als de kolom meerde waardes bevat, voeg dan de opgegeven waarden aan de bestaande toe, in plaats van ze te vervangen.

restore_database

calibredb restore_database [options]

Herstel deze database van de metadata bewaard in OPF bestanden in elke map van de calibre bibliotheek. Nuttig als uw metadata.db bestand gecorrumpeerd is.

WAARSCHUWING/ Dit commando regenereert uw database volledig. U zal alle zoekacties, gebruikerscategorieën, adapters, opgeslagen per-boek conversies en aangepaste recepten verliezen. Herstelde metadata zullen maar zo accuraat zijn als wat is gevonden in de OPF bestanden.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--really-do-it, -r

Het herstel echt uitvoeren. Het commando zal niet uitgevoerd worden tenzij deze optie ingesteld is.

check_library

calibredb check_library [opties]

Voer controles uit op het bestandssysteem van de bibliotheek. Rapporten zijn invalid_titles, extra_titles, invalid_authors, extra_authors, missing_formats, extra_formats, extra_files, missing_covers, extra_covers, failed_folders

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--csv, -c

Uitvoer naar CSV

--ignore_extensions, -e

Kommagescheiden lijst van te negeren extensies. Standaard: all

--ignore_names, -n

Kommagescheiden lijst van te negeren namen. Standaard: all

--report, -r

Kommagescheiden lijst van rapporten. Standaard: all

--vacuum-fts-db

Leeg de volledige tekstzoek database. Dit kan erg traag en geheugenintensief zijn, afhankelijk van de grootte van de database

list_categories

calibredb list_categories [opties]

Maak een rapport van de categorie-informatie in de database. De informatie is het equivalent van wat wordt weergegeven in de Labelbrowser.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--categories, -r

Kommagescheiden lijst van categorie-opzoeknamen. Standaard: alle

--csv, -c

Uitvoer naar CSV

--dialect

Het type CSV bestand te maken. Keuzes excel, excel-tab, unix

--item_count, -i

Toon alleen het aantal items in een categorie in plaats van de aantallen per item in de categorie

--width, -w

De maximale breedte van een enkele regel in de uitvoer. Standaard is dit hetzelfde als de schermgrootte.

backup_metadata

calibredb backup_metadata [opties]

Maak een back-up van de metadata uit de database in individuele OPF bestanden

in elke boek map. Dit gebeurt normaal automatisch maar u kan dit commando

uitvoeren om een vernieuwing van de OPF af te dwingen met de –all optie.

NB: Dit is normaal niet nodig omdat er van de OPF bestanden automatisch een back-up wordt gemaakt elke keer metadata is veranderd.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--all

Normaal werkt dit commando enkel op boeken met verouderde OPF bestanden. Deze optie zorgt dat het op alle boeken werkt.

clone

calibredb kloon pad/naar/nieuw/bibliotheek

Maak een kloon van de huidige bibliotheek. Hierdoor wordt een nieuwe, lege bibliotheek gemaakt met dezelfde aangepaste kolommen, Virtuele bibliotheken en andere instellingen als de huidige bibliotheek.

De gekloonde bibliotheek bevat geen boeken. Als u een volledig duplicaat wilt maken, inclusief alle boeken, gebruik dan gewoon uw bestandssysteemtools om de bibliotheekmap te kopiëren.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

embed_metadata

calibredb ingevoegde_metadata [opties] boek_id

Update de metadata in het eigenlijke boek file opgeslagen in calibre bibliotheek vanuit de metadata in de calibre database. Normaal worden metadata enkel geüpdatet wanneer files uit calibre worden geëxporteerd, dit commando is nuttig indien u wilt dat de bestanden worden geüpdatet ter plaatse. Opgelet dat verschillende formaten verschillende hoeveelheden metadata ondersteunen. U kan de speciale waarde “all” gebruiken in plaats van boek_id om de metadata van alle boeken aan te passen. U kan ook verschillende boek id’s meegeven gescheiden door spaties en id reeksen gescheiden door koppeltekens. Bijvoorbeeld: calibredb ingevoegde_metadata 1 2 10-15 23

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--only-formats, -f

Update alleen de metadata in specifieke bestands types. Gebruik dit meerdere keren om verschillende formaten aan te passen. Standaard worden alle formaten aangepast.

search

calibredb search [opties] zoekexpressie

Doorzoek de bibliotheek voor de specifieke zoekterm, geeft een komma gescheiden lijst van boek IDs terug overeenkomend met de zoekuitdrukking. Het uitvoerformaat is nuttig om andere commando’s uit te voeren die een lijst van IDs aanvaarden als invoer

De zoekexpressie kan eerder wat zijn uit calibre’s krachtige zoek querytaal bijvoorbeeld: %p rog search author:asimov ‘title:”i robot”’

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--limit, -l

Het maximum aantal weer te geven resultaten. Standaard is alle resultaten.

fts_index

calibredb fts_index [options] enable/disable/status/reindex

Controleer het Volledige tekst doorzoeken indexeren proces.

enable

Schakelt FTS indexeren in voor deze bibliotheek

disable

Schakelt FTS indexeren uit voor deze bibliotheek

status

Toont de huidige indexeringsstatus

reindex

Kan gebruikt worden om bepaalde boeken te re-indexeren of de hele bibliotheek. Om bepaalde boeken te re-indexeren specificeer de boek IDs als extra argumenten na de reindex opdracht. Als geen boek IDs gespecificeerd werden, wordt de hele bibliotheek geherindexeerd.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--indexing-speed

Indexeersnelheid. Gebruik snel voor snel indexeren met alle kracht van uw pc en traag voor minder intensief indexeren. Merk op dat de snelheid terug op traag wordt gezet na elk gebruik.

--wait-for-completion

Wacht tot alle boeken geïndexeerd zijn, indexeringsvoortgang wordt regelmatig getoond

fts_search

calibredb fts_search [options] zoekexpressie

Doe een volledige tekstopzoeking op de gehele bibliotheek of een onderdeel ervan.

Als u aan calibredb parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

--do-not-match-on-related-words

Match alleen op exacte woorden, niet op verwante woorden. Correctie komt dus niet overeen met corrigeren.

--include-snippets

Voeg fragmenten toe van de tekst rond elke overeenkomst. Merk op dat dit het zoeken veel langzamer maakt.

--indexing-threshold

Hoeveel procent van de bibliotheek moet worden geïndexeerd voordat zoeken is toegestaan. Standaard ingesteld op 90

--match-end-marker

De markering die wordt gebruikt om het einde van een overeenkomend woord in een fragment aan te geven

--match-start-marker

De markering die wordt gebruikt om het begin van een overeenkomend woord in een fragment aan te geven

--output-format

De indeling waarin de zoekresultaten moeten worden uitgevoerd. Ofwel 'tekst' voor platte tekst of 'json' voor JSON-uitvoer.

--restrict-to

Beperk de doorzochte boeken, ofwel met een zoekexpressie ofwel met IDs. Bijvoorbeeld: IDs: 1,2,3 om te beperken op ID of search:tag:foo om te beperken tot boeken met label foo.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- ebook-convert

ebook-convert

ebook-convert input_file output_file [opties]

Converteer een e-boek van het ene naar het andere formaat.

input_file is de invoer en output_file is de uitvoer. Beiden moeten gespecificeerd worden als de eerste twee argumenten bij het commando.

Het e-boek uitvoerformaat wordt geraden van de extensie van output_file. output_file kan ook van het speciale .EXT formaat zijn, waar EXT de uitvoerbestandextensie is. In dit geval wordt de naam van het invoerbestand die van het uitvoerbestand. Merk op dat de bestandsnaam niet mag beginnen met een koppelteken. Als, tot slot, output_file geen extensie heeft, wordt het gezien als een map en wordt een “open e-boek” (OEB) bestaande uit HTML bestanden geschreven in die map. Deze bestanden zijn dezelfde die normaal gezien naar de uitvoer plug-in zouden zijn gepasseerd.

Na specificatie van het invoer- en uitvoerbestand kan u de conversie aanpassen door een aantal opties te kiezen. De beschikbare opties hangen af van het type invoer- en uitvoerbestanden. Voor hulp specificeer het invoer- en uitvoerbestand en gebruik dan de -h optie.

Voor de volledige documentatie van het conversiesysteem zie E-boek conversie

Als u aan ebook-convert parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

De opties en standaard waarden voor de opties veranderen met de de input en output waarden. U moet dus altijd controleren bij:

ebook-convert myfile.input_format myfile.output_format -h

Hier onder staan de opties die gleden voor alle conversies, gevolgd door de opties die specifiek zijn voor elk input en output formaat.

- INVOEROPTIES

- UITVOEROPTIES

- LOOK AND FEEL

- HEURISTISCHE VERWERKING

- ZOEK EN VERVANG

- STRUCTUUR DETECTIE

- INHOUDSOPGAVE

- METAGEGEVENS

- FOUTOPSPORING

--help, -h

toon dit helpbericht en stop

--input-profile

Specificeer het invoerprofiel. Het invoerprofiel geeft het conversiesysteem informatie over hoe de informatie in het invoerdocument geïnterpreteerd moet worden. Bijvoorbeeld resolutie-afhankelijke lengtes (lengte in pixels). Mogelijkheden zijn: cybookg3, cybook_opus, default, hanlinv3, hanlinv5, illiad, irexdr1000, irexdr800, kindle, msreader, mobipocket, nook, sony, sony300, sony900

--list-recipes

Namen van ingebouwde recepten weergeven. U kunt de volgende opdracht gebruiken om met behulp van een recept een e-boek te maken: eboek-converteer "Receptnaam.recipe" uitvoer.epub

--output-profile

Specificeer het uitvoerprofiel. Het uitvoerprofiel bepaalt hoe het conversiesysteem het aangemaakte document optimaliseert voor het aangegeven apparaat. In sommige gevallen kan een uitvoerprofiel worden gebruikt voor een bepaald apparaat, maar dat is zelden nodig. Keuzes zijn:cybookg3, cybook_opus, default, generic_eink, generic_eink_large, generic_eink_hd, 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

--version

toon programmaversie en stop

INVOEROPTIES

Opties om de bewerking van invoerbestand mobi aan te passen

--input-encoding

Specificeer de tekencodering van het invoerbestand. Als deze optie ingesteld is, zal de codering die het document zelf bevat genegeerd worden. Vooral nuttig voor documenten die geen codering ingesteld hebben, of die een foutieve tekencodering aangeven.

UITVOEROPTIES

Opties om de bewerking van uitvoerbestand epub aan te passen

--dont-split-on-page-breaks

Schakel splitsen bij pagina-eindes uit. Normaal worden de invoerbestanden automatisch bij elk pagina-einde opgesplitst in twee bestanden. Hierdoor krijg je een uitvoer e-boek dat sneller en met minder middelen kan verwerkt worden. Splitsen is echter traag, en als uw bronbestand een groot aantal pagina-eindes bevat, moet u splitsen op pagina-eindes uitschakelen.

--epub-flatten

Deze optie is alleen nodig als u de EPUB op een FBReaderJ wilt gebruiken. Het verwijdert alle deelmappen binnen de EPUB, en zet alle bestanden op het hoogste niveau.

--epub-inline-toc

Voeg een in-line Inhoudsopgave in, die in het boek komt als deel van de inhoud.

--epub-max-image-size

De maximale afbeeldingsgrootte (breedte x hoogte). Een waarde van none betekent dat u de schermgrootte uit het uitvoerprofiel gebruikt. Een waarde van profile betekent dat er geen maximale grootte is opgegeven. Een waarde van 100x200 zorgt er bijvoorbeeld voor dat het formaat van alle afbeeldingen wordt aangepast, zodat hun breedte niet meer is dan 100 pixels en hun hoogte niet meer dan 200 pixels. Houd er rekening mee dat dit alleen de grootte van de daadwerkelijke afbeeldingsbestanden zelf beïnvloedt. Elke afbeelding kan in een ander formaat worden weergegeven, afhankelijk van de stijl die erop wordt toegepast in het document.

--epub-toc-at-end

Zet de ingevoegde in-line Inhoudsopgave aan het eind van het boek in plaats van aan het begin.

--epub-version

De versie van het EPUB-bestand dat moet worden gegenereerd. EPUB 2 is het meest compatibel, gebruik alleen EPUB 3 als u weet dat u het echt nodig hebt.

--extract-to

Extraheer de inhoud van het gegenereerde EPUB bestand naar de aangegeven map. De inhoud van de map wordt eerst gewist, dus wees voorzichtig.

--flow-size

Splits alle HTML-bestanden die groter zijn dan deze grootte (in kB). Dit is nodig omdat de meeste EPUB-lezers geen grote bestanden kunnen hanteren. De standaard van 260KB is de grootte vereist voor Adobe Digital Editions. Ingesteld op 0 schakelt de opsplitsing uit.

--no-default-epub-cover

Als het invoerbestand geen omslag bevat en er niet expliciet een is opgegeven zal een standaardomslag worden aangemaakt met daarop de titel, auteurs, enz. Deze optie schakelt dit uit.

--no-svg-cover

Gebruik geen SVG voor de omslag van het boek. Gebruik deze optie als uw EPUB gebruikt gaat worden op een toestel dat SVG niet ondersteunt, zoals de iPhone of de JetBook Lite. Zonder deze optie zullen deze toestellen de omslag van het boek als een lege pagina laten zien.

--preserve-cover-aspect-ratio

Bij gebruik van een SVG-omslag zal deze optie ervoor zorgen dat de omslag het hele scherm vult, maar toch de breedte/hoogteverhouding zal behouden. Dit betekent dat er sprake kan zijn van omslagen met witte randen aan de zijkanten of de boven- en onderkant van het beeld, maar de afbeelding zal nooit vervormd worden. Zonder deze optie kan de afbeelding enigszins vervormd worden, maar zullen er geen witte randen zijn.

--pretty-print

Indien opgegeven, zal de uitvoerplug-in proberen om uitvoer te genereren die zo leesbaar mogelijk is. Het kan zijn dat dit op sommige uitvoerplug-ins geen enkel effect heeft.

--toc-title

Titel voor iedere gegenereerde in-line Inhoudsopgave.

LOOK AND FEEL

Opties om uiterlijk van de uitvoer aan te passen

--asciiize

Zet unicode karakters om naar ASCII tekens. Voorzichtig gebruiken want dit zal unicode karakters vervangen door ASCII. Bijvoorbeeld: "Pelé" wordt vervangen door "Pele". Merk ook op dat als er meerdere weergaven van een teken zijn (zoals bij gedeelde tekens uit Chinees en Japans), de weergave gekozen wordt op basis van de huidige taalinstellingen voor calibre.

--base-font-size

De basistekstgrootte in pt's. Alle lettergroottes in het geproduceerde boek worden opnieuw geschaald op basis van deze grootte. Door een groter formaat te kiezen, kunt u de lettertypen in de uitvoer groter maken en omgekeerd. Als de waarde nul is, wordt de basislettergrootte standaard gekozen op basis van het uitvoerprofiel dat u hebt gekozen.

--change-justification

Wijzig tekst uitvulling. De waarde ‘links uitlijnen’ verandert alle uitgelijnde tekst in de bron naar links uitgelijnde tekst (m.a.w. niet uitgevuld). De waarde ‘tekst uitvullen’ verandert alle niet uitgevulde tekst naar uitgevuld. De waarde ‘origineel’ (de standaardwaarde) verandert de uitvulling in het bronbestand niet. Merk op dat maar een beperkt aantal uitvoerformaten uitvullen ondersteunen.

--disable-font-rescaling

Geen herschaling van lettergrootte.

--embed-all-fonts

Alle lettertypes invoegen waaraan in het input-document wordt gerefereerd maar nog niet ingevoegd. Dit zal uw systeem doorzoeken naar de lettertypes, en indien gevonden, zullen ze ingevoegd worden. Invoegen zal alleen werken als het formaat waarnaar u converteert ingebouwde fonts ondersteunt, zoals EPUB, AZW3, DOCX of PDF. Let aub op dat u de nodige licentie bezit om de in dit document gebruikte lettertypes in te voegen.

--embed-font-family

Het gespecificeerde lettertype inbedden in het boek. Dit specificeert het "basis" lettertype voor het boek. Als het invoer document eigen lettertypes specificeert, kunnen deze het basis lettertype negeren. U kunt de filter stijlinformatie optie gebruiken om lettertypes uit het invoer document te verwijderen. Merk op: inbedden van lettertypes werkt alleen met bepaalde uitvoer indelingen, voornamelijk EPUB, AZW3 en DOCX.

--expand-css

Standaard gebruikt calibre het verkorte formaat voor verschillende CSS-eigenschappen zoals marges, padding, rand, enz. Deze optie zorgt ervoor dat het in plaats daarvan het volledige formaat gebruikt. Merk op dat CSS altijd wordt uitgebreid bij genereren van EPUB bestanden met als uitvoerprofiel een van de Nook profielen daar de Nook geen verkorte CSS kan verwerken.

--extra-css

Het pad naar een CSS stijlblad of rauwe CSS. Deze CSS wordt toegevoegd aan de stijlregels van het bronbestand, zodat het deze regels kan negeren.

--filter-css

Een door komma's gescheiden lijst van CSS-eigenschappen die zullen worden verwijderd uit alle CSS-stijlregels. Dit is handig als de aanwezigheid van enkele stijl informatie voorkomt dat het wordt overschreven op uw toestel. Bijvoorbeeld: font-family, kleur, margin-left, margin-right

--font-size-mapping

Omzetten van CSS-lettertypenamen naar lettergroottes in pts. Een voorbeeld van instelling is 12,12,14,16,18,20,22,24. Dit zijn de omzettingen voor de groottes xx-small tot xx-large, met de laatste grootte voor enorme letters. Het algoritme voor lettertypeherschaling gebruikt deze lettergroottes om de letters intelligent aan te passen. Standaard worden de instellingen van het gekozen uitvoerprofiel gebruikt.

--insert-blank-line

Voeg een lege regel toe tussen alinea's. Dit werkt niet als het bronbestand geen alinea's gebruikt (<p>- of <div>-labels).

--insert-blank-line-size

Stel de hoogte van de ingevoegde blanco regels in (in em). De hoogte van de regels tussen paragrafen is het dubbele van wat u hier insteld.

--keep-ligatures

Behoud aanwezige bindingen in het invoer document. Een binding is een speciale weergave van een tekenpaar zoals ff, fi, fl enz. De meeste e-readers bieden geen ondersteuning voor bindingen in hun standaard lettertypes, dus worden ze waarschijnlijk niet juist weergegeven. Standaard zal calibre een ligatuur omzetten in het overeenkomstige paar normale tekens. Deze optie wordt daarna behouden.

--line-height

De lijnhoogte in pts. Regelt de vrije ruimte tussen opeenvolgende tekstregels. Geldt alleen voor elementen die hun eigen lijnhoogte niet bepalen. Meestal is het opgeven van de minimale lijnhoogte nuttiger. Standaard wordt de lijnhoogte niet aangepast.

--linearize-tables

Sommige slecht ontworpen documenten gebruiken tabellen om de opmaak van tekst op de pagina te beïnvloeden. Wanneer deze documenten geconverteerd worden hebben ze vaak vreemde fouten, zoals tekst die langer is dan de pagina. Deze optie zal de inhoud uit de tabellen halen en deze achter elkaar weergeven.

--margin-bottom

Zet de ondermarge in punten. Standaard is 5.0. Bij een negatieve waarde wordt er geen marge ingesteld (de marge-instelling in het originele document blijft behouden). Opmerking: pagina-georiënteerde indelingen zoals PDF en DOCX hebben hun eigen marge-instellingen die voorrang hebben.

--margin-left

Zet de linkermarge in punten. Standaard is 5.0. Bij een negatieve waarde wordt er geen marge ingesteld (de marge-instelling in het originele document blijft behouden). Opmerking: pagina-georiënteerde indelingen zoals PDF en DOCX hebben hun eigen marge-instellingen die voorrang hebben.

--margin-right

Zet de rechtermarge in punten. Standaard is 5.0. Bij een negatieve waarde wordt er geen marge ingesteld (de marge-instelling in het originele document blijft behouden). Opmerking: pagina-georiënteerde indelingen zoals PDF en DOCX hebben hun eigen marge-instellingen die voorrang hebben.

--margin-top

Zet de bovenmarge in punten. Standaard is 5.0. Bij een negatieve waarde wordt er geen marge ingesteld (de marge-instelling in het originele document blijft behouden). Opmerking: pagina-georiënteerde indelingen zoals PDF en DOCX hebben hun eigen marge-instellingen die voorrang hebben.

--minimum-line-height

De minimale hoogte van een regel, als percentage van de berekende lettergrootte van het element. calibre zorgt ervoor dat ieder element een regelhoogte heeft van op zijn minst deze instelling, onafhankelijk van wat het ingevoerde document specificeert. Zet op nul om te negeren. De standaardwaarde is 120%. Gebruik deze instelling liever dan de rechtstreekse regelhoogte-instelling, tenzij u weet wat u doet. Om bijvoorbeeld dubbele regelafstand te verkrijgen zet u deze instelling op 240.

--remove-paragraph-spacing

Verwijder witregels tussen alinea's. Stelt ook inspringen met 1.5em in voor alinea's. Witregels verwijderen werkt niet als het invoerbestand geen alinea's gebruikt (<p>- of <div>-labels).

--remove-paragraph-spacing-indent-size

Als calibre lege regels tussen paragrafen verwijderd, zal automatisch een paragraaf insprong worden toegevoegd om de paragraaf makkelijk te kunnen onderscheiden. Deze optie stelt de breedte van die insprong (in em) in. Als y deze waarde negatief instelt, zal de insprong-waarde gebruikt worden die is opgegeven in het bestand. Oftewel, calibre zal de insprong niet aanpassen.

--smarten-punctuation

Zet gewone aanhalingstekens, streepjes en beletseltekens om in hun typografisch juiste equivalenten. Voor details: https://daringfireball.net/projects/smartypants.

--subset-embedded-fonts

Uitdunnen van alle ingebedde lettertypes. Elk ingebed lettertype is beperkt tot de tekens gebruikt in dit document. Dit beperkt de omvang van de lettertype bestanden. Bruikbaar wanneer u een zeer uitgebreid lettertype wilt insluiten met veel ongebruikte tekens.

--transform-css-rules

Pad naar een bestand met regels om de CSS-stijlen in dit boek te transformeren. De eenvoudigste manier om een dergelijk bestand te maken is om de wizard te gebruiken voor het maken van regels in de GUI van calibre. Open het in de "Uitstraling & gevoel-> Transformeer stijlen" onderdeel van het conversie dialoogvenster. Nadat u de regels hebt gemaakt, kunt u de knop "Exporteren" gebruiken om ze in een bestand op te slaan.

--transform-html-rules

Pad naar een bestand met regels om de HTML in dit boek te transformeren. De eenvoudigste manier om een zo'n bestand te maken is de calibre GUI wizard te gebruiken voor het maken van regels. Open het in de "Uitstraling & gevoel-> Transformeer HTML" onderdeel van de conversiedialoog. Na maken van de regels, kunt u de knop "Exporteren" gebruiken om ze in een bestand op te slaan.

--unsmarten-punctuation

Vervang mooie aanhalingstekens, punten en afbrekingstekens met de 'platte tekst' equivalenten.

HEURISTISCHE VERWERKING

Bewerk de tekst van het document en de struktuur, gebruikmakend van algemene patronen. Standaard uitgeschakeld. Gebruik –enable-heuristics om in te schakelen. Individuele taken kunnen worden uitgeschakeld met de –disable-* opties.

--disable-dehyphenate

Analyseer de afgebroken woorden. Het document zelf dient als woordenboek om te bepalen of het afbreekteken als liggend streepje moet worden behouden.

--disable-delete-blank-paragraphs

Verwijder lege alinea's als ze elke tweede alinea voorkomen

--disable-fix-indents

Maak van indentatie aangeduid door niet-afbrekende spaties CSS-indentatie.

--disable-format-scene-breaks

Links uitgelijnde breekpuntmarkeringen worden gecentreerd. Vervang opeenvolgende lege regels door horizontale lijnen.

--disable-italicize-common-cases

Zoek naar veelgebruikte woorden en patronen die duiden op cursief en maak deze cursief.

--disable-markup-chapter-headings

Detecteer onopgemaakte hoofdstuk (sub)koppen en verander deze in h2 en h3 tags. Deze instelling creëert geen Inhoudsopgave maar kan samen met structuurdetectie gebruikt worden om er een te maken.

--disable-renumber-headings

Zoek naar opeenvolgende <h1>- of <h2>-labels. Deze worden hernummerd om te voorkomen dat hoofdstuktitels opgedeeld worden.

--disable-unwrap-lines

Verwijder harde regeleinden op basis van interpunctie en andere opmaak hints.

--enable-heuristics

Heuristische verwerking inschakelen. Zonder deze optie wordt geen enkele heuristische verwerking uitgevoerd.

--html-unwrap-factor

Schaal om te bepalen vanaf welke lengte harde regeleinden moeten worden verwijderd. Geldige waarde is een decimaal getal tussen 0 en 1. De standaardwaarde is 0.4, iets minder dan de mediaan van de regellengte. Als maar bij weinig regels de harde regeleinden hoeven worden verwijderd, kunt u beter een lagere waarde kiezen

--replace-scene-breaks

Vervang breekpunten door de aangegeven tekst. Standaard wordt de tekst uit het invoerbestand gebruikt.

ZOEK EN VERVANG

Wijzig de tekst en structuur van het document m.b.v. gebruiker-gedefinieerde patronen.

--search-replace

Pad naar een bestand dat zoek en vervang reguliere expressies bevat. Het bestand moet afwisselende regels bevatten van reguliere expressies gevolgd door vervangingspatronen (die een lege regel kan zijn). De reguliere expressie moet in de Python regex schrijfwijze zijn en het bestand moet in de UTF-8 codering zijn.

--sr1-replace

Vervanging voor vervangen van tekst gevonden met sr1-zoeken.

--sr1-search

Zoekpatroon (regexp) dat vervangen moet worden door sr1.

--sr2-replace

Vervanging voor vervangen van tekst gevonden met sr2-zoeken.

--sr2-search

Zoekpatroon (regexp) dat vervangen moet worden door sr2.

--sr3-replace

Vervanging voor vervangen van tekst gevonden met sr3-zoeken.

--sr3-search

Zoekpatroon (regexp) dat vervangen moet worden door sr3.

STRUCTUUR DETECTIE

Stel de auto-detectie van de documentstructuur in.

--chapter

Een XPath-expressie om hoofdstuktitels te detecteren. De standaard is om <h1> of <h2> labels die de woorden "chapter", "book", "section", "prologue", "epilogue" or "part" bevatten als hoofdstuktitels te overwegen, evenals labels met class = "chapter". De gebruikte uitdrukking moet resulteren in een lijst met elementen. Gebruik de uitdrukking "/" om hoofdstukdetectie uit te schakelen. Raadpleeg de XPath zelfstudie in de calibre Gebruikershandleiding voor meer hulp bij het gebruik van deze functie.

--chapter-mark

Specificeer hoe gedetecteerde hoofdstukken moeten worden gemarkeerd. Een waarde "pagebreak" zal een nieuwe pagina beginnen voor nieuwe hoofdstukken. Een waarde "rule" zal een streep toevoegen voor hoofdstukken. Een waarde "none" zal hoofdstukmarkering uitschakelen, en een waarde "both" zal zowel nieuwe pagina's als strepen gebruiken om hoofdstukken te markeren.

--disable-remove-fake-margins

In sommige documenten worden voor elke alinea apart linker- en rechtermarges opgegeven. calibre tracht deze te verwijderen, maar soms worden daardoor marges verwijderd die niet weg mogen. In dat geval kan margeverwijdering uitgeschakeld worden.

--insert-metadata

Voeg de boek-metadata in aan het begin van het boek. Dit is nuttig als uw e-boeklezer de directie manier van tonen/zoeken van metadata niet ondersteunt.

--page-breaks-before

Een XPath uitdrukking. Pagina splitsing wordt toegevoegd voor de aangegeven elementen. Om uit te schakelen gebruikt u de uitdrukking: /

--prefer-metadata-cover

Gebruik de in het bronbestand gedetecteerde omslag en niet de opgegeven omslag.

--remove-first-image

Verwijder de eerste afbeelding van het oorspronkelijke e-boek. Nuttig als het brondocument een omslagafbeelding heeft die niet wordt herkend als omslag. In dat geval als u een omslag heeft ingesteld in calibre zal het e-boek twee omslagfoto's laten zien als u deze optie niet instelt.

--start-reading-at

Een XPath-expressie om de locatie in het document te detecteren waar te beginnen met lezen. Sommige e-boek leesprogramma's (vooral de Kindle) gebruiken deze locatie als de positie om het boek te openen. Raadpleeg de XPath-zelfstudie in de calibre gebruikershandleiding voor meer hulp bij het gebruik van deze functie.

INHOUDSOPGAVE

Configureer het automatisch aanmaken van de Inhoudsopgave. Standaard zal, indien aanwezig, de Inhoudsopgave van het invoerbestand gebruikt worden, in plaats van de automatisch aangemaakte Inhoudsopgave.

--duplicate-links-in-toc

Laat dubbele invoer toe bij creëren van een Inhoudsopgave vanuit links in het invoerdocument, d.w.z. laat meer dan één invoer toe met dezelfde tekst indien ze verwijzen naar een verschillende locatie.

--level1-toc

XPath expressie die alle aan de Inhoudsopgave op niveau een toe te voegen tags specificeert. Deze optie gaat boven andere vormen van auto-detectie. Bekijk de XPath handleiding in de calibre handleiding voor voorbeelden .

--level2-toc

XPath expressie die alle aan de Inhoudsopgave op niveau twee toe te voegen tags specificeert.. Elke vermelding komt onder de vorige niveau een vermelding. Bekijk de XPath handleiding in de calibre handleiding voor voorbeelden .

--level3-toc

XPath expressie die alle aan de Inhoudsopgave op niveau drie toe te voegen tags specificeert.. Elke vermelding komt onder de vorige niveau twee vermelding. Bekijk de XPath handleiding in de calibre handleiding voor voorbeelden .

--max-toc-links

Maximaal aantal links dat aan de Inhoudsopgave toegevoegd mag worden. Gebruik 0 om uit te schakelen. Standaard is: 50. Links worden alleen aan de inhoudsopgave toegevoegd als minder dan dit aantal hoofdstukken werd gedetecteerd.

--no-chapters-in-toc

Voeg geen automatisch gedetecteerde hoofdstukken aan de Inhoudsopgave toe.

--toc-filter

Verwijder vermeldingen uit de Inhoudsopgave wiens titels overeenkomen met de opgegeven reguliere expressie. Overeenkomende vermeldingen en kinderen worden verwijderd.

--toc-threshold

Als het aantal gedetecteerde hoofdstukken kleiner is dan dit getal worden links aan de Inhoudsopgave toegevoegd. Standaard: 6

--use-auto-toc

Indien aanwezig, wordt normaal de Inhoudsopgave van het bronbestand gebruikt i.p.v. een automatisch gegenereerde. Met deze optie zal altijd de automatisch gegenereerde versie gebruikt worden.

METAGEGEVENS

Opties om metagegevens voor de uitvoer in te stellen

--author-sort

De string die gebruikt wordt bij het sorteren op auteur.

--authors

Geef de auteurs op. Meerdere auteurs moeten met een ampersand (&) van elkaar gescheiden worden.

--book-producer

Geef de producent op.

--comments

Stel de e-boek omschrijving in.

--cover

Stel de omslag in met het opgegeven bestand of URL

--isbn

Geef het ISBN van het boek op.

--language

De taal instellen.

--pubdate

Stel de publicatiedatum in (veronderstellend de locale tijdzone, behalve als de tijdzone expliciet is gespecificeerd)

--publisher

Stel de e-boek uitgever in.

--rating

Waardering geven. Moet een getal tussen de 1 en 5 zijn.

--read-metadata-from-opf, --from-opf, -m

Lees metadata uit het opgegeven OPF bestand. Metadata die hieruit worden gelezen negeren metadata in het bronbestand.

--series

Stel de serie in waartoe dit e-boek behoort.

--series-index

Geef de index van dit boek in de serie op.

--tags

Geef de labels voor het boek op. Dit moet een door komma's gescheiden lijst zijn.

--timestamp

Maak tijdstempel voor boek (wordt nergens meer gebruikt)

--title

Geef de titel op.

--title-sort

De versie van de titel die wordt gebruikt bij het sorteren.

FOUTOPSPORING

Opties om te helpen bij het vinden van fouten bij de conversie

--debug-pipeline, -d

Sla de uitvoer van verschillende stadia van de conversielijn op in de opgegeven map. Nuttig als u niet zeker weet tijdens welke stap in het conversieproces een fout optreedt.

--verbose, -v

Niveau van detail informatie uitvoer. Specifieer meerdere malen voor meer details. Indien het twee maal wordt meegegeven zal er een volledig detail gegeven worden, eenmaal medium details, en geen enkele keer de minimale details.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- ebook-edit

ebook-edit

ebook-edit [opts] [path_to_ebook] [name_of_file_inside_book ...]

Lanceert het calibre boek Bewerken gereedschap. Optioneel kan u ook de namen van bestanden in het boek specificeren die automatisch geopend worden voor bewerken.

Als u aan ebook-edit parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--detach

Loskoppelen van de controleterminal, indien aanwezig (alleen Linux)

--help, -h

toon dit helpbericht en stop

--select-text

De tekst om te selecteren in het boek wanneer het geopend is voor bewerking

--version

toon programmaversie en stop

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- ebook-meta

ebook-meta

ebook-meta ebook_file [opties]

Lezen/schrijven metadata van/naar e-boekbestanden.

Ondersteunende formaten om metadata te lezen: 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

Ondersteunende formaten om metadata te schrijven: azw, azw1, azw3, azw4, docx, epub, fb2, fbz, htmlz, lrf, mobi, odt, pdb, pdf, prc, rtf, tpz, txtz

Verscheidene bestandtypes ondersteunen verscheidene soorten metadata. Indien u probeert sommige metadata in een niet-ondersteunde bestandstype op te slaan worden die metadata gewoon genegeerd.

Als u aan ebook-meta parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--author-sort

Tekst die wordt gebruikt bij het sorteren op auteur. Als deze leeg is, en de auteur(s) zijn opgegeven, dan zal deze automatisch aan de hand van de auteur(s) aangemaakt worden.

--authors, -a

Geef de auteurs op. Meerdere auteurs moeten door het &-teken gescheiden worden. Auteursnamen moeten in de volgorde Voornaam Achternaam zijn.

--book-producer, -k

Geef de producent op.

--category

Geef de boekcategorie op.

--comments, -c

Stel de e-boek omschrijving in.

--cover

Gebruik het opgegeven bestand voor de omslag.

--date, -d

Geef de publicatiedatum op.

--from-opf

Lees metadata uit het opgegeven OPF bestand en gebruik deze om metadata in het e-boek in te stellen. Metadata die op de opdrachtregel opgegeven worden negeren metadata uit het OPF bestand

--get-cover

Haal de omslag van het e-boek en sla dit op als het gespecificeerde bestand.

--help, -h

toon dit helpbericht en stop

--identifier

Stel de ID's voor het boek in, kan meerdere keren worden opgegeven. Bijvoorbeeld: --identificeer uri https://acme.com --identificeer isbn: 12345 Als u een ID wilt verwijderen, geeft u geen waarde op. --Identificeer isbn: Houd er rekening mee dat voor EPUB-bestanden een ID die is gemarkeerd als de pakket-id niet kan worden verwijderd.

--index, -i

Geef de index van dit boek in de serie op.

--isbn

Geef het ISBN van het boek op.

--language, -l

De taal instellen.

--lrf-bookid

Stel het BoekID in, in LRF-bestanden

--publisher, -p

Stel de e-boek uitgever in.

--rating, -r

Waardering geven. Moet een getal tussen de 1 en 5 zijn.

--series, -s

Stel de serie in waartoe dit e-boek behoort.

--tags

Geef de labels voor het boek op. Dit moet een door komma's gescheiden lijst zijn.

--title, -t

Geef de titel op.

--title-sort

De versie van de titel die wordt gebruikt bij het sorteren. Als deze leeg is en de titel is opgegeven, dan zal deze automatisch aan de hand van de titel aangemaakt worden.

--to-opf

Geef de naam van een OPF-bestand op. De metadata zullen in het OPF-bestand worden opgeslagen.

--version

toon programmaversie en stop

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- ebook-polish

ebook-polish

ebook-polish [opties] invoer_bestand [uitvoer_bestand]

Boeken oppoetsen gaat over alles wat de indruk van perfectie aan je zorgvuldig samengestelde E-boeken geeft.

Oppoetsen tracht zo min mogelijk de interne code van u E-boek te veranderen. Anders dan bij converteren vereenvoudigt het geen CSS, hernoemt het geen bestanden, verandert niet de lettertype grootte, aanpassen van marges, enz. Elke actie veroorzaakt slechts de minimaal benodigde veranderingen om het beoogde effect te bereiken.

Je dient dit als laatste stap in u E-boek creatieproces te gebruiken.

Merk op dat oppoetsen alleen werkt bij bestanden in het AZW3 of EPUB formaat.

Als u aan ebook-polish parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--add-soft-hyphens, -H

Voeg zachte koppeltekens toe aan alle woorden in het boek. Hiermee kan het boek worden weergegeven beter wanneer de tekst gerechtvaardigd is, in lezers die geen woordafbreking ondersteunen

--compress-images, -i

Comprimeer de afbeeldingen in het boek, zonder de kwaliteit aan te tasten..

--cover, -c

Pad naar omslag afbeelding. Wijzigt de omslag gespecificeerd in het E-boek. Als geen omslag beschikbaar is, of de omslag is niet goed herkend, dan wordt een nieuwe omslag toegevoegd.

--embed-fonts, -e

Alle lettertypes insluiten waaraan gerefereerd wordt in het document en die nog niet zijn ingesloten in. Hierbij wordt uw computer doorzocht op lettertypes en indien gevonden zullen zij worden ingesloten in het document. Verzeker u ervan dat u over de juist licentie beschikt voor het insluiten van de in dit document gebruikte lettertypes.

--help, -h

toon dit helpbericht en stop

--jacket, -j

Voegt een "Boekomslag" pagina in aan het begin van het boek, die alle metadata van het boek bevat, zoals titel, labels, auteurs, series, commentaren enz. Elke vorig gebruikte boekomslag zal worden vervangen.

--opf, -o

Pad naar een OPF bestand. De metadata in het boek is bijgewerkt vanuit het OPF bestand.

--remove-jacket

Verwijder een eerder ingevoegde overzichtspagina.

--remove-soft-hyphens

Verwijder zachte koppeltekens uit alle tekst in het boek

--remove-unused-css, -u

Verwijder alle ongebruikte CSS regels van stijlbladen en <stijl> tags. Boeken gemaakt op basis van productiesjablonen kunnen een groot aantal extra CSS-regels hebben die niet slaan op enige feitelijke inhoud. Deze extra regels kunnen e-readers vertragen die ze allemaal moeten verwerken.

--smarten-punctuation, -p

Zet platte tekst streepjes, weglatingstekens, aanhalingstekens, meerdere afbreekstreepjes, enz. om naar de typografisch juiste equivalenten. NB, het algoritme kan soms foute resultaten geven, met name als enkele aanhalingstekens worden gebruikt bij de start van samenstelling.

--subset-fonts, -f

Uitdunnen van lettertypen betekend het reduceren van ingesloten lettertypes zodat ze enkel de tekens bevatten welke worden gebruikt in het boek. Dit zorgt voor een grote reductie van de lettertype bestanden (een halvering van de lettertypebestanden is normaal). Bijvoorbeeld, als het boek een specifiek lettertype gebruikt voor koppen, dan zal uitdunnen het lettertype reduceren tot enkel de tekens weergegeven in de koppen van het boek. Of als het boek vette en scheve versies van het lettertype heeft ingevoegd, maar vet en scheef niet of weinig wordt gebruikt, dan kunnen deze worden gereduceerd tot een paar tekens of zelfs helemaal worden verwijderd Het enige nadeel van uitdunnen van een lettertype is dat u, wanneer u op een later moment besluit tekst toe te voegen aan uw boek, de nieuwe tekst mogelijk tekens bevat die u heeft verwijderd uit het lettertype.

--upgrade-book, -U

Upgrade de interne structuren van het boek, indien mogelijk. Bijvoorbeeld: upgrade EPUB 2-boeken naar EPUB 3-boeken.

--verbose

Geef uitgebreidere uitvoer, nuttig bij foutopsporing.

--version

toon programmaversie en stop

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- ebook-viewer

ebook-viewer

ebook-viewer [opties] bestand

Bekijk een e-boek.

Als u aan ebook-viewer parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--continue

Laatst geopend boek verder lezen

--detach

Loskoppelen van de controleterminal, indien aanwezig (alleen Linux)

--force-reload

Dwing herladen van alle geopende boeken af

--full-screen, --fullscreen, -f

Indien gespecificeerd, zal het e-boekleesvenster proberen in volledig scherm te openen bij opstarten.

--help, -h

toon dit helpbericht en stop

--new-instance

Open a new viewer window even when the option to use only a single viewer window is set

--open-at

The position at which to open the specified book. The position is a location or position you can get by using the Go to->Location action in the viewer controls. Alternately, you can use the form toc:something and it will open at the location of the first Table of Contents entry that contains the string "something". The form toc-href:something will match the href (internal link destination) of toc nodes. The matching is exact. If you want to match a substring, use the form toc-href-contains:something. The form ref:something will use Reference mode references. The form search:something will search for something after opening the book. The form regex:something will search for the regular expression something after opening the book.

--raise-window

Indien gespecificeerd, zal het e-boekleesvenster proberen naar voren te komen bij opstarten.

--version

toon programmaversie en stop

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- fetch-ebook-metadata

fetch-ebook-metadata

fetch-ebook-metadata [options]

Haal metadata over het boek van online bronnen. Er moet minstens een titel, auteur of ISBN meegegeven worden.

Als u aan fetch-ebook-metadata parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--allowed-plugin, -p

Geef de naam van een metadata download plug-in om te gebruiken. Standaard worden alle metadata -plug-ins gebruikt. Kan meerdere keren worden opgegeven voor meerdere plug-ins. Alle plug-in-namen: Google, Google Images, Amazon.com, Edelweiss, Open Library, Big Book Search

--authors, -a

Boek Auteur(s)

--cover, -c

Geef een bestandsnaam. De omslag "indien beschikbaar" wordt opgeslagen. Zonder deze optie wordt geen omslag gedownload.

--help, -h

toon dit helpbericht en stop

--identifier, -I

Identificatoren zoals ASIN/Goodreads id enz. Kunnen meermaals gespecificeerd worden voor meerdere Identificatoren. Bv.: --identifier asin:B0082BAJA0

--isbn, -i

Boek ISBN

--opf, -o

De metadata uitvoeren in OPF indeling in plaats van door mensen leesbare tekst.

--timeout, -d

Timeout in seconden. Standaard is dit 30

--title, -t

Boek titel

--verbose, -v

Print de log naar de console (stderr)

--version

toon programmaversie en stop

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- lrf2lrs

lrf2lrs

lrf2lrs boek.lrf

Een LRF-bestand naar LRS (XML UTF-8-codering) converteren

Als u aan lrf2lrs parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--dont-output-resources

Ingebedde afbeeldingen en lettertypes niet bewaren op schijf

--help, -h

toon dit helpbericht en stop

--output, -o

Uitvoer-LRS-bestand

--verbose

Wees woordrijker

--version

toon programmaversie en stop

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- lrfviewer

lrfviewer

lrfviewer [opties] boek.lrf

Lees het LRF e-boek boek.lrf

Als u aan lrfviewer parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--disable-hyphenation

Zet woordafbreken uit. Zou weergeven opmerkelijk moeten versnellen.

--help, -h

toon dit helpbericht en stop

--profile

Profileer de LRF weergave

--verbose

Laat meer informatie zien over de weergaveproces

--version

toon programmaversie en stop

--visual-debug

Zet de visuele hulp aan voor foutzoeken in de weergave engine

--white-background

Als standaard is de achtergrond een gebroken wit sinds dit gemakkelijker is om te lezen voor de ogen. Gebruik deze optie om de achtergrond puur wit te maken.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- lrs2lrf

lrs2lrf

lrs2lrf [opties] bestand.lrs

Compileer een LRS-bestand naar LRF-formaat.

Als u aan lrs2lrf parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--help, -h

toon dit helpbericht en stop

--lrs

LRS naar LRS converteren, handig om fouten op te sporen.

--output, -o

Pad naar uitvoerbestand

--verbose

Verwerking met veel uitleg

--version

toon programmaversie en stop

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Opdrachtpromt »
- web2disk

web2disk

web2disk URL

Waar URL is bijvoorbeeld http://google.com

Als u aan web2disk parameters met spaties mee wilt geven, moet u deze met aanhalingstekens omsluiten. Bv.: “/some path/with spaces”

[Opties]

--base-dir, -d

Basismap waar de URL wordt opgeslagen. Standaard is dit .

--delay

Minimuminterval in seconden tussen aaneensluitende downloads. Standaard is dit 0 s

--dont-download-stylesheets

Geen CSS-stylesheets downloaden.

--encoding

De tekencodering voor de websites die u probeert te downloaden. Standaard zal er worden geprobeerd om de codering te raden.

--filter-regexp

Alle links die overeenkomen met deze reguliere expressie worden genegeerd. U kunt deze optie vaker toepassen. Als een regexp een match oplevert, wordt de link genegeerd. Standaard wordt links niet genegeerd. Als filter regexp en match regexp allebei zijn gespecifieerd, wordt filter regexp als eerste toegepast.

--help, -h

toon dit helpbericht en stop

--match-regexp

Alleen links die overeenkomen met deze regexp zullen gevolgd worden. Deze optie kan meerdere keren worden opgegeven, in welk geval de link zal worden gevolgd als deze overeenkomt met ten minste één regexp. Standaard zullen alle links gevolgd worden.

--max-files, -n

Het maximaal aantal te downloaden bestanden. Dit is alleen van toepassing op bestanden in <a href>-labels. Standaard is dit 9223372036854775807

--max-recursions, -r

Maximaal aantal niveaus voor recursie, d.w.z. diepte van te volgen koppelingen. Standaard 1

--timeout, -t

Time-out in seconden om te wachten op een antwoord van de server. Standaard: 10.0 s

--verbose

Laat gedetailleerde uitvoerinformatie zien. Nuttig bij het opsporen van problemen

--version

toon programmaversie en stop

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »

- Opzetten van Calibre in een development omgeving

Opzetten van Calibre in een development omgeving
calibre is volledig open source, onder de GNU GPL v3 licentie <https://www.gnu.org/licenses/gpl.html> _. Dit betekent dat u vrij bent om het programma naar hartenlust te downloaden en aan te passen. In dit gedeelte leert u hoe u een calibre ontwikkelingsomgeving kunt opzetten voor het besturingssysteem van uw keuze. calibre wordt voornamelijk geschreven in Python <https://www.python.org> _ met wat C/C++ code voor snelheid en systeeminterface. Merk op dat calibre minstens Python 3.8 nodig heeft.

Inhoud

- Ontwerp filosofie

- Code opmaak

- De code verkrijgen

- Uw aanpassingen insturen voor productie

- Windows ontwikkel omgeving

- macOS ontwikkelomgeving

- Linux ontwikkel omgeving

- Afzonderlijke “normale” en “ontwikkel” calibre installaties op dezelfde computer hebben

- Debug tips

- Gebruik van print statements

- Een interactieve Python vertaler gebruiken

- De Python foutzoeker als een externe foutzoeker gebruiken

- De foutzoeker gebruiken in uw favoriete Python IDE

- Willekeurige scripts uitvoeren in de calibre Python omgeving

- Calibre gebruiken in uw projecten

- Binaire installatie van Calibre

- Broncode installatie op Linux

- API installatie voor diverse onderdelen van Calibre

Ontwerp filosofie

Calibre heeft zijn wortels in de Unix-wereld, wat betekent dat het design bijzonder modulair is. De modules werken samen met elkaar via precies gedefinieerde interfaces. Hierdoor wordt het toevoegen van nieuwe features en het oplossen van bugs in Calibre erg eenvoudig, wat resulteert in een snel ontwikkelingstempo. Vanwege deze wortels heeft Calibre een uitgebreide command line interface voor al zijn functies, welke gedocumenteerd zijn in Opdrachtpromt.

Het modulaire ontwerp van Calibre wordt uitgedrukt via `` Plugins``. Er is een: ref :handleiding <customize> over het schrijven van Calibre plug-ins. Als u bijvoorbeeld ondersteuning toevoegt voor een nieuw Calibre apparaat, heeft dit meestal betrekking op het schrijven van minder dan 100 regels code in de vorm van een plug-in voor apparaatstuurprogramma’s. U kunt bladeren door de ingebouwde stuurprogramma’s [https://github.com/kovidgoyal/Calibre/tree/master/src/Calibre/devices]. Op dezelfde manier houdt het toevoegen van ondersteuning voor nieuwe conversie-indelingen het schrijven van invoeg- / uitvoerformatenplug-ins in. Een ander voorbeeld van het modulaire ontwerp is de: ref: receptuursysteem <news> voor het ophalen van nieuws. Zie de Index van plug-ins <https://www.mobileread.com/forums/showthread.php?p=1362767#post1362767> _ voor meer voorbeelden van plug-ins die zijn ontworpen om functies aan Calibre toe te voegen.

Code opmaak

Alle de Calibre python code staat in het Calibre pakket. Dit pakket bevat de volgende hoofddeel pakketten

- apparaten - Alle apparaatstuurprogramma’s. Bekijk enkele van de ingebouwde stuurprogramma’s om een idee te krijgen hoe ze werken.

- Voor details, bekijk: devices.interface wat de interface definieert ondersteund door toestel stuurprogramma’s en devices.usbms wat een generiek stuurprogramma definieert die met een USBMS toestel verbindt. Alle USBMS gebaseerde stuurprogramma’s erven ervan.

- e-boeken - Alle e-boek conversie/metadata code. Een goed beginpunt is calibre.ebooks.conversion.cli, de module die het ebook-convert commando drijft. Het conversieproces wordt gecontroleerd via conversion.plumber. De formaat onafhankelijke code zit allemaal in ebooks.oeb en de formaat afhankelijke code in ebooks.format_name.

- Metadata lezen, schrijven en downloaden staat allemaal in `` ebooks.metagegevans``

- Conversie gebeurt in een pijplijn, voor de structuur van de pijplijn, bekijk Inleiding. De pijplijn bestaat uit een invoerplug-in, verschillende transforms en een uitvoerplug-in. De code die de pijplijn opbouwt en aandrijft zit in plumber.py. De pijplijn werkt op een voorstelling van een e-boek als een ontzipte EPUB, met manifest, spine, ToC, gids, HTML inhoud, enz. De class die deze voorstelling beheert is OEBBook in ebooks.oeb.base. De verschillende transformaties toegepast op het boek tijdens conversie wonen in oeb/transforms/*.py. En de in- en uitvoerplug-in wonen in oeb/transforms/*.py.

- E-boek bewerken gebeurt met een ander container object, gedocumenteerd in API documentatie voor e-book bewerking functies.

- db - De database achterkant. Bekijk API documentatie voor de gegevensbank interface voor de interface naar de calibre bibliotheek.

- Content server: srv is de calibre Content server.

- gui2 - De Grafische Gebruiker Interface. GUI initialisatie gebeurt in gui2.main en gui2.ui. De e-boek-viewer zit in gui2.viewer. De e-book editor in gui2.tweak_book.

Als u de ingangspunten voor alle calibre executables wilt lokaliseren, bekijk de entry_points structuur in linux.py [https://github.com/kovidgoyal/calibre/blob/master/src/calibre/linux.py].

Als u hulp nodig hebt om de code te begrijpen, post iets in het ontwikkelforum [https://www.mobileread.com/forums/forumdisplay.php?f=240] en u zal heel waarschijnlijk hulp krijgen van een van de vele calibre ontwikkelaars.

De code verkrijgen

U kan de calibre broncode op twee manieren krijgen, via een versiecontrolesysteem of download direct een tarball [https://calibre-ebook.com/dist/src].

calibre gebruikt Git [https://www.git-scm.com/], een gedistribueerd versiecontrolesysteem. Git is beschikbaar op alle platformen die calibre ondersteunt. Na installeren van Git kan u de calibre source code krijgen met het commando:

git clone https://github.com/kovidgoyal/calibre.git

Op Windows heb je de volledige padnaam nodig, dat zal ongeveer zo zijn: bestand: C:\Program Files\Git\git.exe.

Calibre is een zeer groot project met een zeer lange versiebeheergeschiedenis, dus het bovenstaande kan een tijd duren (10 minuten tot een uur, afhankelijk van je internetsnelheid).

Als u de code sneller wilt krijgen, de broncode van de laatste uitgave is altijd beschikbaar als een archive [https://calibre-ebook.com/dist/src].

Om een vertakking bij te werken naar de nieuwste code, gebruik de volgende opdracht:

git pull --no-edit

U kan ook de code doorbladeren op GitHub [https://github.com/kovidgoyal/calibre].

Uw aanpassingen insturen voor productie

Als u enkel een paar kleine veranderingen wilt maken, maak uw veranderingen en creëer een “merge directive” die u dan aan een ticket in de calibre bug tracker [https://bugs.launchpad.net/calibre] kan koppelen. Om dit te doen, maak uw veranderingen , draai dan:

git commit -am "Comment describing your changes"
git format-patch origin/master --stdout > my-changes

Dit creëert een my-changes bestand in de huidige map, koppel dit gewoon aan een ticket op de calibre bug tracker [https://bugs.launchpad.net/calibre]. Merk op dat dit alle commits die gemaakt hebt meeneemt. Als u slechts enkele commits wil sturen, moet u de origin/master boven veranderen. Om enkel de laatste commit te sturen, gebruik:

git format-patch HEAD~1 --stdout > my-changes

Als u de laatste n commentaren wilt verzenden, vervangt u 1 door n, bijvoorbeeld voor de laatste 3 commentaren:

git format-patch HEAD~3 --stdout > my-changes

Zorg dat u geen samenvoegingen opneemt bij het gebruik van HEAD~n.

Als u veel ontwikkelwerk aan calibre wil doen, kan u best een GitHub [https://github.com] account creëren. Onder vindt u een basisgids om uw eigen fork van calibre op te zetten die u toelaat pull requests in te dienen voor opname in calibre’s hoofdrepository:

- Stel git in op uw machine zoals beschreven in dit artikel: Setup Git [https://help.github.com/articles/set-up-git]

- Stel ssh-sleutels in voor authenticatie naar GitHub, zoals hier beschreven: SSH-sleutels genereren <https://help.github.com/articles/generating-ssh-keys> _

- Ga naar https://github.com/kovidgoyal/Calibre en klik op de Fork button.

- In een terminalvenster doe:

git clone git@github.com:<username>/calibre.git
git remote add upstream https://github.com/kovidgoyal/calibre.git

Vervang <username> boven door uw GitHub gebruikersnaam. Dat zorgt ervoor dat uw fork lokaal wordt uitgecheckt.

- U kunt wijzigingen aanbrengen en deze toewijzen wanneer u maar wilt. Wanneer je klaar bent om je werk samen te voegen, doe dan een:

git push

en ga naar https://github.com/<username>/Calibre en klik op Pull Request button om een pull request te generen die samengevoegd kan worden.

- U kunt uw lokale exemplaar op elk gewenst moment bijwerken met code van de hoofdrepo door te doen:

git pull upstream

U houdt best een oogje op het calibre ontwikkelforum [https://www.mobileread.com/forums/forumdisplay.php?f=240]. Vooraleer grote veranderingen te maken, bespreek ze best in het forum of contacteer Kovid direct (de broncode krioelt van z’n e-mailadres).

Windows ontwikkel omgeving

Notitie

U moet ook de calibre broncode apart ophalen zoals beschreven boven.

Installeer calibre normaal met de Windows installer <https://calibre-ebook.com/download_windows>`__. Open dan een Commandoprompt en ga naar de voordien bekeken calibre codemap. Bijvoorbeeld:

cd C:\Users\kovid\work\calibre

calibre is de map die de src bevat en bronnensubmappen.

De volgende stap is de omgevingsvariabele CALIBRE_DEVELOP_FROM instellen op het absolute pad van de src map. Dus, volgens het voorbeeld boven zou het C:\Users\kovid\work\calibre\src zijn. Hier is een korte gids <https://docs.python.org/using/windows.html#excursus-setting-environment-variables>`_ over instellen van omgevingsvariabelen in Windows.

Nadat u de omgevingsvariabele hebt ingesteld, opent u een nieuwe opdrachtprompt en controleert u of deze correct is ingesteld met de opdracht:

echo %CALIBRE_DEVELOP_FROM%

Deze omgevingsvariabele instellen betekent dat calibre nu alle Python code vanuit de gespecificeerde locatie laadt.

Das alles! U bent nu klaar om de calibre code te beginnen hacken. Bv. open het bestand src\calibre\__init__.py in uw favoriete editor en voeg volgende regel toe:

print("Hello, world!")

in de buurt van de bovenkant van het bestand. Voer nu de opdracht: opdracht uit: calibredb. De allereerste regel van de uitvoer zou moeten zijn `` Hallo, wereld! ``.

U kan ook een calibre ontwikkelomgeving opzetten in het gratis Microsoft Visual Studio, als u wilt, met volgende instructies hier [https://www.mobileread.com/forums/showthread.php?t=251201].

macOS ontwikkelomgeving

Notitie

U moet ook de calibre broncode apart ophalen zoals beschreven boven.

Installeer calibre normaal met de provided .dmg [https://calibre-ebook.com/download_osx]. Open dan een Terminal en ga naar de voordien bekeken calibre codemap, bijvoorbeeld:

cd /Users/kovid/work/calibre

calibre is de map die de src bevat en bronnensubmappen. de calibre opdrachtregelgereedschappen vindt u in de calibre appbundel, in /Applications/calibre.app/Contents/MacOS, u voegt deze map best toe aan uw PATH omgevingsvariabele als u de opdrachtregelgereedschappen vlot wilt gebruiken.

De volgende stap is het maken van een bash-script dat de omgevingsvariabele CALIBRE_DEVELOP_FROM instelt op het absolute pad van de src map bij het uitvoeren van calibre in foutzoekmodus.

Maak een platte-tekst bestand:

#!/bin/sh
export CALIBRE_DEVELOP_FROM="/Users/kovid/work/calibre/src"
calibre-debug -g

Sla dit bestand op als /usr/local/bin/calibre-develop, stel dan de rechten in zodat het kan worden uitgevoerd:

chmod +x /usr/local/bin/calibre-develop

Als u dit heeft gedaan, voer uit:

calibre-develop

U zou wat diagnostische informatie in het Terminal-venster moeten zien als Calibre opstart, en u zou een asterisk na het versienummer in het GUI-venster moeten zien, om aan te geven dat u vanuit de bron loopt.

Linux ontwikkel omgeving

Notitie

U moet ook de calibre broncode apart ophalen zoals beschreven boven.

calibre is voornamelijk in Linux ontwikkeld. U hebt twee keuzes bij instellen van de ontwikkelomgeving. U kan de calibre binary als gewoonlijk installeren en dat als runtime omgeving gebruiken voor uw ontwikkeling. Deze werkwijze is gelijkaardig aan die in Windows en macOS. Als alternatief kan u calibre van de bron installeren. Instructies om een ontwikkelomgeving op te zetten vanuit de bron staan in het INSTALL bestand in de bestandsboom. Hier gaat het over gebruik van de binary als een runtime, wat de aanbevolen methode is.

Installeer calibre met de binary installer [https://calibre-ebook.com/download_linux]. Open dan een Terminal en ga naar de voordien bekeken calibre codemap, bijvoorbeeld:

cd /home/kovid/work/calibre

calibre is de map die de src bevat en bronnensubmappen.

De volgende stap is om de omgevingsvariabele `` CALIBRE_DEVELOP_FROM`` in te stellen op het absolute pad van de src-map. Dus, in navolging van het bovenstaande voorbeeld, zou het /home/kovid/work/Calibre/src zijn. Het instellen van omgevingsvariabelen is afhankelijk van je Linux-distributie en welke shell je gebruikt.

Notitie

Gebruik van de binaire installer, vanaf stroomopwaarts geleverd, wordt aanbevolen. Staat u erop een pakket te gebruiken uit uw distro, gebruik dan de CALIBRE_PYTHON_PATH and CALIBRE_RESOURCES_PATH variabelen.

Nadat u de omgevingsvariabele hebt ingesteld, opent u een nieuwe terminal en controleert u of deze correct is ingesteld met de opdracht:

echo $CALIBRE_DEVELOP_FROM

Deze omgevingsvariabele instellen betekent dat calibre nu alle Python code vanuit de gespecificeerde locatie laadt.

Das alles! U bent nu klaar om de calibre code te beginnen hacken. Bv. open het bestand src\calibre\__init__.py in uw favoriete editor en voeg volgende regel toe:

print("Hello, world!")

in de buurt van de bovenkant van het bestand. Voer nu de opdracht: opdracht uit: calibredb. De allereerste regel van de uitvoer zou moeten zijn `` Hallo, wereld! ``.

Afzonderlijke “normale” en “ontwikkel” calibre installaties op dezelfde computer hebben

De calibre bronboom is heel stabiel en breekt zelden maar als u de behoefte voelt om vanuit de bron te draaien op een aparte testbibliotheek en de uitgegeven calibre versie met uw dagdagelijkse bibliotheek, kan u dat gemakkelijk met .bat bestanden of shellscripts om calibre te starten. Het voorbeeld onder toont hoe dit te doen in Windows met .bat bestanden (de instructies voor andere platformen zijn hetzelfde, gebruik gewoon een shellscript i.p.v. een .bat bestand).

De releaseversie van calibre lanceren met uw alledaagse bibliotheek

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"

Debug tips

Python is een dynamisch getypeerde taal met uitstekend onderdak voor introspectie. Kovid schreef de kern calibre code zonder ook maar één keer een foutzoeker te gebruiken. Er zijn veel strategieën voor foutzoeken in calibre code:

Gebruik van print statements

Dit is de favoriete manier van Kovid om te debuggen. Voeg eenvoudig afdrukinstructies in op interessante plaatsen en voer uw programma uit in de terminal. U kunt de GUI bijvoorbeeld vanuit de terminal starten als:

calibre-debug -g

U kan de e-boekviewer ook starten als:

calibre-debug -w /path/to/file/to/be/viewed

The e-book editor can be started as:

calibre-debug --edit-book /path/to/be/edited

Een interactieve Python vertaler gebruiken

U kan de volgende twee regels code invoegen om op dat punt een interactieve Python sessie te starten:

from calibre import ipython
ipython(locals())

Bij het uitvoeren van de opdrachtregel start hiermee een interactieve Python vertaler met toegang tot alle lokaal gedefinieerde variabelen (variabelen in de lokale scope). De interactieve prompt heeft zelfs Tab voltooiing voor objecteigenschappen en u kunt de verschillende Python faciliteiten gebruiken voor introspectie, zoals dir(), type(), repr(), enz.

De Python foutzoeker als een externe foutzoeker gebruiken

U kan de ingebouwde Python foutzoeker gebruiken (pdb) als een externe foutzoeker vanop de opdrachtregel. Om te beginnen, start de externe foutzoeker op de plaats in de code waar u in geïnteresseerd bent, zoals dit:

from calibre.rpdb import set_trace
set_trace()

Voer vervolgens Calibre uit, hetzij als normaal, of met behulp van een van de Calibre-debug-opdrachten die in de vorige sectie zijn beschreven. Zodra het bovenstaande punt in de code is bereikt, zal Calibre bevriezen, wachtend totdat de foutopsporing verbinding maakt.

Open nu een terminal of opdrachtprompt en gebruik de volgende opdracht om de foutopsporingssessie te starten:

calibre-debug -c "from calibre.rpdb import cli; cli()"

U kan lezen hoe de Python foutzoeker te gebruiken in de Python stdlib docs voor de pdb module [https://docs.python.org/library/pdb.html#debugger-commands].

Notitie

Standaard probeert de debugger op afstand verbinding te maken op poort 4444. Je kunt dit wijzigen door de parameter port naar zowel de set_trace() als de cli() -functies hierboven door te geven, zoals deze: set_trace(port=1234)``en``cli(port = 1234).

Notitie

De Python foutzoeker kan niet met meerdere draadjes overweg, u moet set_trace dus voor elk draadje roepen, elke keer met een ander poortnummer.

De foutzoeker gebruiken in uw favoriete Python IDE

Het is mogelijk om de ingebouwde foutzoeker van uw favoriete Python IDE te gebruiken, als hij extern foutzoeken ondersteunt. De eerste stap is toevoegen van de calibre scr checkout aan PYTHONPATH in uw IDE. M.a.w. de map die u instelde als CALIBRE_DEVELOP_FROM boven moet ook in PYTHONPATH van uw IDE staan.

Plaats dan de IDE’s externe foutzoekmodule in de src submap van de calibre broncode checkout. Voeg de nodige code toe om de externe foutzoeker te starten op het punt van interesse, bv. in de hoofdfunctie. Draai calibre dan als gewoonlijk. Uw IDE zou nu moeten kunnen verbinden met de externe foutzoeker die in calibre draait.

Willekeurige scripts uitvoeren in de calibre Python omgeving

Het calibre-debug commando voorziet enkele handige schakelaars om uw eigen code uit te voeren, met toegang tot de calibre modules:

calibre-debug -c "some Python code"

is geweldig om een klein code knipseltje te testen op de commandoregel. Werkt op dezelfde manier als de -c schakelaar naar de Python vertaler:

calibre-debug myscript.py

kan worden gebruikt om je eigen Python-script uit te voeren. Het werkt op dezelfde manier als het doorgeven van het script aan de Python-interpreter, behalve dat de Calibre omgeving volledig is geïnitialiseerd, zodat u alle Calibre code in uw script kunt gebruiken. om opdrachtregelargumenten te gebruiken met uw script, gebruik de vorm:

calibre-debug myscript.py -- --option1 arg1

De `` –`` zorgt ervoor dat alle volgende argumenten aan je script worden doorgegeven.

Calibre gebruiken in uw projecten

Het is mogelijk calibre functies/code direct te gebruiken in uw Python project. Er zijn twee manieren om dit te doen:

Binaire installatie van Calibre

Als u een binaire installatie van calibre hebt, kan u de Python vertaler, gebundeld met calibre, zo gebruiken:

calibre-debug /path/to/your/python/script.py -- arguments to your script

Broncode installatie op Linux

Bijkomend aan de techniek boven, als u een broninstallatie doet in Linux kan u calibre ook direct importeren, op deze manier:

import init_calibre
import calibre

print(calibre.__version__)

Het is essentieel dat u de init_calibre module importeert voor enige andere calibre modules/packages want het stelt de vertaler in om de calibre code te draaien.

API installatie voor diverse onderdelen van Calibre

- API documentatie voor recepten

- API documentatie voor plugins

- API documentatie voor de gegevensbank interface

- API documentatie voor e-book bewerking functies

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »

- Opzetten van Calibre in een development omgeving »

- API documentatie voor de gegevensbank interface

API documentatie voor de gegevensbank interface

Deze API is thread safe (meervoudige reader, single writer vergrendelingsschema). U kunt een API intreden door

from calibre.library import db
db = db('Path to calibre library folder').new_api

Als u zich bevindt in een plugin dat deel uitmaakt van de hoofd caliber gebruikersinterface kunt u ook toegang krijgen tot dit zoals onderstaand;;

db = self.gui.current_db.new_api

class calibre.db.cache.Cache(backend, library_database_instance=None)[broncode]

Een geheugencache van het bestand metadata .db van een calibre bibliotheek. Deze class dient ook als een thread-safe API om toegang te krijgen tot de database. Het geheugencache wordt onderhouden in normale vorm voor maximale prestatie.

SQLITE wordt gebruikt om te lezen en schrijven van metadata .db op een robuuste manier. De logica van alle tabellen lezen/sorteren/zoeken/cachen wordt herimplementeerd. Dit was noodzakelijk voor maximale prestatie en flexibiliteit.

class EventType(value, names=None, *, 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)[broncode]

Voeg de gespecificeerde boeken toe aan de bibliotheek. Boeken moeten een iterabel zijn van 2-tupels, elk 2-tupel van de vorm: code: (mi, format_map) waarbij mi een Metadata -object is en format_map een woordenboek is van de vorm: code: {fmt: path_or_stream} `, bijvoorbeeld: :code: {‘EPUB’: ‘/pad/naar/bestand.epub’} `.

Retourneert een paar lijsten: id's, duplicaten. ids bevat de boek-id’s voor alle nieuw gemaakte boeken in de gegevensbank. duplicaten bevat de: code: (mi, format_map) voor alle boeken die al bestaan in de gegevensbank volgens de eenvoudige duplicaat detectie heuristiek gebruikt door has_book().

add_custom_book_data(name, val_map, delete_first=False)[broncode]

Toevoegen van gegevens voor naam waar val_map is een map van book-ids naar waardes. Indien delete_first is True of WAAR, dan wordt alle eerder opgeslagen gegevens voor naam verwijderd.

add_extra_files(book_id, map_of_relpath_to_stream_or_path, replace=True, auto_rename=False)[broncode]

Add extra data files

add_format(book_id, fmt, stream_or_path, replace=True, run_hooks=True, dbapi=None)[broncode]

Voeg een formaat toe aan het gespecificeerde boek. Koppel True terug als het formaat succesvol is toegevoegd.

Parameters:

- replace – Als “True” een bestaand formaat vervangt, anders als het formaat al bestaat, “False” terugkoppelen.

- run_hooks – Indien TRUE lopen de bestandstype-plugins in het formaat vóór en ná het toevoegen.

- dbapi – Enkel intern gebruik

add_listener(event_callback_function, check_already_added=False)[broncode]

Registreer een callback functie die aangeroepen wordt nadat bepaalde acties op de database uitgevoerd werden. De functie moet drie argumenten hebben: (EventType, library_id, event_type_specific_data)

add_notes_resource(path_or_stream_or_data, name: str, mtime: float = None) → int[broncode]

Add the specified resource so it can be referenced by notes and return its content hash

all_book_ids(type=<class 'frozenset'>)[broncode]

Bevroren voor alle beken de boek id’s.

all_field_for(field, book_ids, default_value=None)[broncode]

Zelfde als field_for, behalve dat het werkt voor meerdere boeken tegelijkertijd

all_field_ids(name)[broncode]

Bevroren set met ID’s voor alle waarden in het veld naam.

all_field_names(field)[broncode]

Bevroren set van alle veld namen (alleen gebruikt worden voor VEEL-EEN en VEEL-VEEL velden)

author_data(author_ids=None)[broncode]

Geeft auteurgegevens terug als woordenboek met toetsen: naam, sortering, link

Indien geen auteurs met de gespecificeerds id’s worden gevonden, geeft dit een leeg woordenboek terug. Indien author_ids is None worden gegevens van alle auteurs getoond.

author_sort_from_authors(authors, key_func=<function make_change_case_func.<locals>.change_case>)[broncode]

Geef een lijst met auteurs, terugkeren naar auteur_sorteren string voor de auteurs, voorkeur dat de auteur sortering geassocieerd is met de auteur over het berekende string.

books_for_field(name, item_id)[broncode]

Geeft alle die boeken terug die in verband staan met het item geïdentificeerd door item_id, waar het item behoord tot het veld naam.

Geeft als waarde terug een set boeken id’s of een leeg set indien het item of het veld niet bestaat.

books_in_virtual_library(vl, search_restriction=None, virtual_fields=None)[broncode]

Keer terug het set van boeken in het gespecificeerd virtueel bibliotheek

compress_covers(book_ids, jpeg_quality=100, progress_callback=None)[broncode]

Comprimeer de omslagafbeeldingen voor de gespecificeerde boeken. Een compressiekwaliteit van 100 voert een verliesvrije compressie uit, anders met verlies.

De vooruitgang callback wordt aangeroepen met het book_id en de oude en nieuwe groottes voor elk boek dat verwerkt werd. Als er een fout is, zal de nieuwe grootte een string met de foutdetails zijn.

copy_cover_to(book_id, dest, use_hardlink=False, report_file_size=None)[broncode]

Kopieer de opslag naar het bestandsobject doel. Geeft Onwaar indien geen omslag bestaat, of doel gelijk is aan de huidige omslag. Doel kan ook een pad zijn, waarbij de omslag ernaartoe wordt gekopieerd indien het pad anders is dan het huidige pad (rekening houdend met hoofdlettergevoeligheid).

copy_format_to(book_id, fmt, dest, use_hardlink=False, report_file_size=None)[broncode]

Kopieer het formaat fmt naar het bestand als object dest. Als het gespecificeerd formaat niet bestaat, verhoogd :class:’NoSuchFormat’ fout. dest kan ook een pad zijn (naar een bestand), in dat geval is het formaat ernaar gekopieerd, als het pad verschillend is van de huidige pad (hoofdlettergevoeligheid in het account meegenomen).

cover(book_id, as_file=False, as_image=False, as_path=False, as_pixmap=False)[broncode]

Geeft omslagfoto terug of None. Standaard geeft de omslag terug als een reeks bytes.

WAARSCHUWING: Het gebruik van as_path maakt een kopie van de omslag in een temp-bestand en geeft het pad terug naar het temp-bestand. U moet het temp-bestand verwijderen wanneer u er klaar mee bent.

Parameters:

- as_file – Indien TRUE de afbeelding teruggeeft als een open file object (een SpooledTemporaryFile)

- as_image – Indien waar, geef de afbeelding terug als een QImage object

- as_pixmap – If True return the image as a QPixmap object

- as_path – Indien TRUE de afbeelding teruggeeft als een pad die verwijst naar een temporair bestand

data_for_find_identical_books()[broncode]

Geeft data terug die gebruikt kunnen worden om find_identical_books() toe te passen in een worker process zonder toegang tot de database. Bekijk db.utils voor een toepassing.

data_for_has_book()[broncode]

Geeft data terug die gebruikt kunnen worden om has_book(). Dit kan gebruikt worden voor een toepassing van has_book() in een worker process zonder toegang tot de database.

delete_custom_book_data(name, book_ids=())[broncode]

Verwijder data voor naam. Verwijdert standaard alle data, als u enkel data voor sommige boek ID’s wilt verwijderen, geef een lijst met boek ID’s in.

delete_trash_entry(book_id, category)[broncode]

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)[broncode]

Update metadata in alle formaten van het gespecificeerde book_ids tot huidige metadata in de database.

expire_old_trash()[broncode]

Expire entries from the trash that are too old

export_note(field, item_id) → str[broncode]

Export the note as a single HTML document with embedded images as data: URLs

fast_field_for(field_obj, book_id, default_value=None)[broncode]

Hetzelfde als field_for, behalve dat het de extra opzoek vermijdt voor het veld object

field_for(name, book_id, default_value=None)[broncode]

Geeft waarde van het veld name voor boek geïdentificeerd door book_id terug. Als zo geen boek bestaat of geen gedefinieerde waarde heeft voor het veld name of zulk veld bestaat niet, wordt default_value teruggegeven.

default_value wordt niet gebruikt voor title, title_sort, authors, author_sort and series_index omdat deze altijd waardes hebben in de db. default_value wordt gebruikt voor alle aangepaste kolommen.

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)[broncode]

Geef de id’s terug (als een tupel) voor de waardes dat het veld name heeft voor het boek geïdentificeerd door book_id. Als er geen waardes zijn, of zo geen boek, of zo geen veld, wordt een lege tupel teruggegeven.

field_supports_notes(field=None) → bool[broncode]

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)[broncode]

Vindt boeken die een auteur superset hebben in mi en dezelfde titel (titel komt fuzzy overeen). Bekijk ook data_for_find_identical_books().

format(book_id, fmt, as_file=False, as_path=False, preserve_filename=False)[broncode]

Geef het boekformaat terug als een byte string of Geen als het formaat niet bestaat of we geen toestemming hebben om naar het e-boek bestand te schrijven.

Parameters:

- as_file – Indien Waar wordt het e-boek formaat teruggegeven als bestandsobject. Merk op dat het bestandsobject een SpooledTemporaryFile is dus als u het formaat naar een ander bestand wil kopiëren, gebruik in plaats daarvan copy_format_to() voor prestatie.

- as_path – Kopieert het formaat bestand naar een tijdelijk bestand en geeft het pad naar het tijdelijk bestand terug

- 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)[broncode]

Geeft absolute pad terug aan het e-boek bestand in formaat format. Dit mag u maar zelden gebruiken want het breekt de threadveilige belofte van deze API. Gebruik beter 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)[broncode]

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)[broncode]

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.

Parameters:

- 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)[broncode]

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]][broncode]

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)[broncode]

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 {}.

Parameters:

book_id – the book id in question.

Returns:

{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)[broncode]

Intern gebruikt om de labelbrowser te implementeren

get_custom_book_data(name, book_ids=(), default=None)[broncode]

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)[broncode]

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)[broncode]

Return the set of book ids for which name has data.

get_item_id(field, item_name)[broncode]

Return the item id for item_name (case-insensitive) or None if not found

get_item_ids(field, item_names)[broncode]

Geef de item id terug voor item_name (hoofdlettergevoelig)

get_item_name(field, item_id)[broncode]

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)[broncode]

Return mapping of item values to ids

get_link_map(for_field)[broncode]

Return a dictionary of links for the supplied field.

Parameters:

for_field – the lookup name of the field for which the link map is desired

Returns:

{field_value:link_value, …} for non-empty links

get_metadata(book_id, get_cover=False, get_user_categories=True, cover_as_data=False)[broncode]

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)[broncode]

Return the next series index for the specified series, taking into account the various preferences that control next series number generation.

Parameters:

- 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[broncode]

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)[broncode]

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)[broncode]

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)[broncode]

Geeft Waar desda de gegevensbank een item bevat met dezelfde titel als meegegeven in het metadata object. De vergelijking is niet hoofdlettergevoelig. Zie ook data_for_has_book().

has_format(book_id, fmt)[broncode]

Geeft Waar desda het formaat bestaat op disk

has_id(book_id)[broncode]

Geeft Waar desda het boek_id bestaat in de db

import_note(field, item_id, path_to_html_file, path_is_data=False)[broncode]

Import a previously exported note or an arbitrary HTML file as the note for the specified item

init()[broncode]

Initialize this cache with data from the backend.

items_with_notes_in_book(book_id: int) → dict[str, dict[int, str]][broncode]

Return a dict of field to items that have associated notes for that field for the specified book

link_for(field, item_id)[broncode]

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, ...][broncode]

Get information about extra files in the book’s directory.

Parameters:

- 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.

Returns:

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)[broncode]

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)[broncode]

Undelete a book from the trash directory

move_format_from_trash(book_id, fmt)[broncode]

Undelete a format from the trash directory

multisort(fields, ids_to_sort=None, virtual_fields=None)[broncode]

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[broncode]

Return all notes data as a dict or None if note does not exist

notes_for(field, item_id) → str[broncode]

Return the notes document or an empty string if not found

notes_resources_used_by(field, item_id)[broncode]

Return the set of resource hashes of all resources used by the note for the specified item

pref(name, default=None, namespace=None)[broncode]

Return the value for the specified preference or the value specified as default if the preference is not set.

read_backup(book_id)[broncode]

Return the OPF metadata backup for the book as a bytestring or None if no such backup exists.

remove_books(book_ids, permanent=False)[broncode]

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)[broncode]

Verwijder de specifieke formaten van de specifieke boeken

Parameters:

- 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.

Returns:

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)[broncode]

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)[broncode]

Rename extra data files

rename_items(field, item_id_to_new_name_map, change_index=True, restrict_to_book_ids=None)[broncode]

Rename items from a many-one or many-many field such as tags or series.

Parameters:

- 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=())[broncode]

Restore the book entry in the database for a book that already exists on the filesystem

restore_original_format(book_id, original_fmt)[broncode]

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)[broncode]

Save a copy of the specified format as ORIGINAL_FORMAT, overwriting any existing ORIGINAL_FORMAT.

search(query, restriction='', virtual_fields=None, book_ids=None)[broncode]

Search the database for the specified query, returning a set of matched book ids.

Parameters:

- 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)[broncode]

Search the text of notes using an FTS index. If the query is empty return all notes.

set_conversion_options(options, fmt='PIPE')[broncode]

options must be a map of the form {book_id:conversion_options}

set_cover(book_id_data_map)[broncode]

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)[broncode]

Set the values of the field specified by name. Returns the set of all book ids that were affected by the change.

Parameters:

- 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 – Intern gebruikt, u dient dit nooit te wijzigen.

set_link_map(field, value_to_link_map, only_set_if_no_existing_link=False)[broncode]

Sets links for item values in field. Note: this method doesn’t change values not in the value_to_link_map

Parameters:

- field – the lookup name

- value_to_link_map – dict(field_value:link, …). Note that these are values, not field ids.

Returns:

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)[broncode]

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[broncode]

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)[broncode]

Stel de opgegeven voorkeur aan de opgegeven waarde. Zie ook: meth: pref.

split_if_is_multiple_composite(f, val)[broncode]

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)[broncode]

Return the ids of all books having the tag tag that are older than the specified time. tag comparison is case insensitive.

Parameters:

- 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[broncode]

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)[broncode]

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.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »

- Opzetten van Calibre in een development omgeving »

- API documentatie voor e-book bewerking functies

API documentatie voor e-book bewerking functies

De hulpmiddelen voor het bewerken van e-boeken bestaan uit een: class: Calibre.ebooks.oeb.polish.container.Container-object dat een boek vertegenwoordigt als een verzameling HTML + bronbestanden en verschillende hulpmiddelen die kunnen worden gebruikt om bewerkingen uit te voeren op de houder. Alle tools zijn in de vorm van moduleniveaufuncties in de verschillende `` Calibre.ebooks.oeb.polish. * `` Modules.

U verkrijgt een containerobject voor een boek op een pad als dit:

from calibre.ebooks.oeb.polish.container import get_container
container = get_container('Path to book file', tweak_mode=True)

Als u een plug-in schrijft voor de E-book-editor, krijgt u de huidige container voor het bewerken van het boek zoals dit:

from calibre.gui2.tweak_book import current_container
container = current_container()
if container is None:
report_error # No book has been opened yet

Het container object
class calibre.ebooks.oeb.polish.container.Container(rootpath, opfpath, log, clone_data=None)[broncode]

Een container stelt een open e-boek voor als een map vol bestanden en een OPF-bestand. Er zijn twee belangrijke concepten:

- De hoofdmap. Dit is de basis van het e-boek. Alle e-boek bestanden bevinden zich in deze map of in zijn submappen.

- Namen: Dit zijn paden naar boekbestanden relatief aan de hoofdmap. Ze bevatten altijd POSIX separatoren en hebben geen aanhalingstekens. Zie ze als canonieke identificatoren voor bestanden in het boek. Meeste methodes op het containerobject werken met namen. Namen zijn altijd in de NFC Unicode normale vorm.

- Klonen: Het containerobject ondersteunt efficiënt op-schijf klonen, gebruikt om controlepunten toe te passen in de e-boek editor. Om dit te laten werken, mag u nooit bestanden direct in het bestandssysteem benaderen. In plaats daarvan, gebruik raw_data() of open() om te lezen/schrijven naar onderdelen in het boek.

Bij converteren tussen hrefs en namen, gebruik de methodes voorzien in deze class, ze gaan ervan uit dat alle hrefs tussen aanhalingstekens staan.

abspath_to_name(fullpath, root=None)[broncode]

Converteer een absoluut pad naar een canonieke naam relatief ten opzichte van root

Parameters:

root – De hoofdmap, Standaard wordt de root voor dit containerobject gebruikt.

add_file(name, data, media_type=None, spine_index=None, modify_name_if_needed=False, process_manifest_item=None)[broncode]

Bestand aan deze container toevoegen. Vermeldingen worden automatisch aangemaakt in het OPF manifest en de spine (als het bestand een tekstdocument is)

add_name_to_manifest(name, process_manifest_item=None)[broncode]

Vermelding toevoegen aan het manifest voor een bestand met de gespecificeerde naam, Geeft het manifest ID terug.

add_properties(name, *properties)[broncode]

De gespecificeerde eigenschappen toevoegen aan het manifest item geïdentificeerd bij naam.

apply_unique_properties(name, *properties)[broncode]

Zorg ervoor dat de gespecificeerde eigenschappen enkel geplaatst zijn op het manifest item geïdentificeerd bij naam. U kan Geen doorgeven als naam om de eigenschap van alle items te verwijderen.

book_type = 'oeb'

Het type boek (epub voor EPUB bestanden en azw3 voor AZW3 bestanden)

commit(outpath=None, keep_parsed=False)[broncode]

Commit all dirtied parsed objects to the filesystem and write out the e-book file at outpath.

Parameters:

- output – Het pad om het opgeslagen e-boekbestand naar toe te schrijven. Indien Geen, wordt het pad van het originele boekbestand gebruikt.

- keep_parsed – If True the parsed representations of committed items are kept in the cache.

commit_item(name, keep_parsed=False)[broncode]

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)[broncode]

Mark the parsed object corresponding to name as dirty. See also: parsed().

exists(name)[broncode]

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)[broncode]

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)[broncode]

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)[broncode]

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)[broncode]

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)[broncode]

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)[broncode]

Insert item into parent (or append if index is None), fixing indentation. Only works with self closing items.

is_dir = False

If this container represents an unzipped book (a directory)

iterlinks(name, get_line_numbers=True)[broncode]

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)[broncode]

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)[broncode]

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)[broncode]

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)[broncode]

Alle manifest items die de gespecificeerde eigenschap hebben

property manifest_type_map

Mapping van manifest mediatype naar lijst van canonieke namen van dat mediatype

property mi

De metadata van dit boek als een Metadata object. Merk op dat dit object on the fly wordt samengesteld elke keer om deze eigenschap wordt gevraagd, zuinig gebruiken dus.

name_to_abspath(name)[broncode]

Converteer een canonieke naam naar een absoluut OS afhankelijk pad

name_to_href(name, base=None)[broncode]

Converteer een naam naar een href relatief aan de basis, wat een naam moet zijn of Geen in welk geval self.root wordt gebruikt als basis.

property names_that_must_not_be_changed

Set namen die nooit hernoemd mag worden. Afhankelijk van het e-boekbestandsformaat.

property names_that_must_not_be_removed

Set namen die nooit uit de container verwijderd mag worden. Afhankelijk van het e-boekbestandsformaat.

property names_that_need_not_be_manifested

Set namen die mag ontbreken in het manifest. Afhankelijk van het e-boekbestandsformaat.

open(name, mode='rb')[broncode]

Open het bij naam aangewezen bestand voor direct lezen/schrijven. Merk op dat dit het bestand commit als het vuil is en het uit de verwerkingscache verwijderd. U moet dit bestand afwerken vooraleer de verwerkte versie weer te betreden of er dreigt onheil.

property opf

Het verwerkte OPF bestand

opf_get_or_create(name)[broncode]

Gemakzuchtige manier om ofwel terug te keren naar het eerste XML element met de gespecificeerde naam of het te creëren onder het opf:package element en het dan terug te geven, als het al niet bestaat.

property opf_version

De versie ingesteld op de OPF’s <package> element

property opf_version_parsed

De versie ingesteld op de OPF’s <package> element als een tupel van gehele getallen

opf_xpath(expr)[broncode]

Gemakzuchtige manier om een XPath expressie te evalueren in het OPF bestand, heeft de opf: en dc: naamruimte voorvoegsels voor-gedefinieerd.

parsed(name)[broncode]

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)[broncode]

Geef de rauwe data terug overeenkomstig met het bestand gespecificeerd bij naam

Parameters:

- decode – Als Waar en het bestand heeft een tekst gebaseerd MIME type, decodeer het en geef een Unicode object terug i.p.v. rauwe bytes.

- normalize_to_nfc – Als Waar is het teruggegeven Unicode object genormaliseerd volgens de NFC vorm zoals vereist voor de EPUB en AZW3 bestandsformaten.

relpath(path, base=None)[broncode]

Converteer een absoluut pad (met OS separators) naar een pad relatief naar basis (standaard naar self.root). Het relatieve pad is geen naam. Gebruik daarvoor abspath_to_name().

remove_from_spine(spine_items, remove_if_no_longer_in_spine=True)[broncode]

Verwijder de gespecificeerde items (per canonieke naam) uit de opbouw. Als remove_if_no_longer_in_spine Waar is, worden de items ook uit het boek verwijderd, niet enkel uit de opbouw.

remove_from_xml(item)[broncode]

Verwijdert item van ouder, inspringing herstellend ( werkt enkel met zelksluitende items)

remove_item(name, remove_from_guide=True)[broncode]

Verwijder het bij naam genoemde item uit deze container. Dit verwijdert alle referenties naar het item in het OPF manifest, gids en opbouw, zowel als elke interne cache.

rename(current_name, new_name)[broncode]

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)[broncode]

Vervang het verwerkte object overeenkomend op naam met obj, wat een gelijkaardig object moet zijn, bv. een lxml boom voor HTML/XML of een css_parser stijlblad voor een CSS bestand.

replace_links(name, replace_func)[broncode]

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)[broncode]

Converteer een verwerkt object (geïdentificeerd met canonieke naam) in een bytestring. Bekijk parsed().

set_spine(spine_items)[broncode]

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.

Component bestanden in een container beheren

calibre.ebooks.oeb.polish.replace.replace_links(container, link_map, frag_map=<function <lambda>>, replace_in_opf=False)[broncode]

Vervang links naar bestanden in de container. Gaat over alle bestanden in de container en verandert de gespecificeerde links erin.

Parameters:

- 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 – Indien Onwaar worden links in het OPF bestand niet vervangen.

calibre.ebooks.oeb.polish.replace.rename_files(container, file_map)[broncode]

Hernoem bestanden in de container. update links naar hen automatisch.

Parameters:

file_map – Mapping van oude canonieke namen naar nieuwe canonieke namen, bv. {'text/chapter1.html': 'chapter1.html'}.

calibre.ebooks.oeb.polish.replace.get_recommended_folders(container, names)[broncode]

Geef de mappen terug aanbevolen voor de gegeven bestandsnamen. De aanbeveling is gebaseerd op waar de meerderheid van bestanden van hetzelfde type staan in de container. Als geen bestanden van een bepaald type aanwezig zijn, wordt de aangewezen map veronderstelt de map te zijn met het OPF bestand.

Netjes afdrukken en auto-herstellen verwerkingsfouten

calibre.ebooks.oeb.polish.pretty.fix_html(container, raw)[broncode]

Herstel alle verwerkingsfouten in de HTML voorgesteld als een rauwe string. Herstellen wordt gedaan met het HTML5 verwerkingsalgoritme.

calibre.ebooks.oeb.polish.pretty.fix_all_html(container)[broncode]

Herstel alle verwerkingsfouten in alle HTML bestanden in de container. Herstellen wordt gedaan met het HTML5 verwerkingsalgoritme.

calibre.ebooks.oeb.polish.pretty.pretty_html(container, name, raw)[broncode]

Druk de HTML voorgesteld als een string in rauw netjes af

calibre.ebooks.oeb.polish.pretty.pretty_css(container, name, raw)[broncode]

Druk de CSS voorgesteld als een string in rauw netjes af

calibre.ebooks.oeb.polish.pretty.pretty_xml(container, name, raw)[broncode]

Druk de XML voorgesteld als een string in rauw netjes af. Als name de naam is van de OPF, extra OPF specifiek netter maken is gedaan.

calibre.ebooks.oeb.polish.pretty.pretty_all(container)[broncode]

Druk alle HTML/CSS/XML bestanden in de container netjes af

Boekomslagen beheren

calibre.ebooks.oeb.polish.jacket.remove_jacket(container)[broncode]

Remove an existing jacket, if any. Returns False if no existing jacket was found.

calibre.ebooks.oeb.polish.jacket.add_or_replace_jacket(container)[broncode]

Either create a new jacket from the book’s metadata or replace an existing jacket. Returns True if an existing jacket was replaced.

Splitsen en samenvoegen van bestanden

calibre.ebooks.oeb.polish.split.split(container, name, loc_or_xpath, before=True, totals=None)[broncode]

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.

Parameters:

- 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 – Intern gebruikt

calibre.ebooks.oeb.polish.split.multisplit(container, name, xpath, before=True)[broncode]

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.

Parameters:

before – If True the splits occur before the identified element otherwise after it.

calibre.ebooks.oeb.polish.split.merge(container, category, names, master)[broncode]

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.

Parameters:

- category – Must be either 'text' for HTML files or 'styles' for CSS files

- names – De lijst van bestanden om samen te voegen

- master – Which of the merged files is the master file, that is, the file that will remain after merging.

Omslagen beheren

calibre.ebooks.oeb.polish.cover.set_cover(container, cover_path, report=None, options=None)[broncode]

Set the cover of the book to the image pointed to by cover_path.

Parameters:

- 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)[broncode]

Mark the specified image as the cover image.

calibre.ebooks.oeb.polish.cover.mark_as_titlepage(container, name, move_to_start=True)[broncode]

Mark the specified HTML file as the titlepage of the EPUB.

Parameters:

move_to_start – If True the HTML file is moved to the start of the spine

Werken met CSS

calibre.ebooks.oeb.polish.fonts.change_font(container, old_name, new_name=None)[broncode]

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)[broncode]

Remove all unused CSS rules from the book. An unused CSS rule is one that does not match any actual content.

Parameters:

- 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=())[broncode]

Remove the specified CSS properties from all CSS rules in the book.

Parameters:

- 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.

Werken met de inhoudsopgave

calibre.ebooks.oeb.polish.toc.from_xpaths(container, xpaths, prefer_title=False)[broncode]

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)[broncode]

Generate a Table of Contents from links in the book.

calibre.ebooks.oeb.polish.toc.from_files(container)[broncode]

Generate a Table of Contents from files in the book.

calibre.ebooks.oeb.polish.toc.create_inline_toc(container, title=None)[broncode]

Create an inline (HTML) Table of Contents from an existing NCX Table of Contents.

Parameters:

title – De titel voor deze inhoudsopgave.

Bewerken Boekhulpmiddel
class calibre.gui2.tweak_book.plugin.Tool[broncode]

Basisklassen: 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)[broncode]

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.

Parameters:

- 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)[broncode]

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

Zie ook

Method register_shortcut().

Controlling the editor’s user interface

The e-book editor’s user interface is controlled by a single global Boss object. This has many useful methods that can be used in plugin code to perform common tasks.

class calibre.gui2.tweak_book.boss.Boss(parent, notify=None)[broncode]

add_savepoint(msg)[broncode]

Create a restore checkpoint with the name specified as msg

apply_container_update_to_gui(mark_as_modified=True)[broncode]

Update all the components of the user interface to reflect the latest data in the current book container.

Parameters:

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)[broncode]

Close the editor that is editing the file specified by name

commit_all_editors_to_container()[broncode]

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)[broncode]

Open het bestand op basis van naam in een beweker

Parameters:

- 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)[broncode]

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.

Parameters:

edit_file – The name of a file inside the newly opened book to start editing. Can also be a list of names.

rewind_savepoint()[broncode]

Undo the previous creation of a restore checkpoint, useful if you create a checkpoint, then abort the operation with no changes

save_book()[broncode]

Save the book. Saving is performed in the background

set_modified()[broncode]

Boek markeren als bewerkt

show_current_diff(allow_revert=True, to_container=None)[broncode]

Show the changes to the book from its last checkpointed state

Parameters:

- 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)[broncode]

Show the editor that is editing the file specified by name

sync_preview_to_editor()[broncode]

Sync the position of the preview panel to the current cursor position in the current editor

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- volgende |
- vorige |
- Start »
- Digital Rights Management (DRM)

Digital Rights Management (DRM)

Digital Rights Management (DRM) is een generische term voor toegangscontroletechnologieën die hardwarefabrikanten, uitgevers, copyrighthouders en individuen kunnen gebruiken om te proberen beperkingen op te leggen op het gebruik van digitale inhoud en toestellen. Het wordt ook, soms, neerbuigend beschreven als Digital Restrictions Management. Het wordt gebruikt voor elke technologie die gebruik verhindert (gepast of anders) van digitale inhoud niet gewenst of voorzien door de inhoudsleverancier. De term verwijst normaal niet naar andere vormen van kopieerbeveiliging die kan omzeild worden zonder het bestand of toestel te wijzigen, zoals serienummers of sleutelbestanden. Het kan ook verwijzen naar beperkingen geassocieerd met bepaalde instanties van digitale werken of toestellen. DRM technologieën proberen het gebruik van digitale media te controleren door toegang, kopiëren of converteren naar andere formaten door eindgebruikers te voorkomen. Kijk op Wikipedia [https://en.wikipedia.org/wiki/Digital_rights_management].

Wat betekent DRM voor mij persoonlijk?

Wanneer u een e-boek met DRM koopt, bezit u het niet echt, maar heeft u de toestemming gekocht om het te gebruiken op een manier die de verkoper u heeft voorgeschreven. DRM beperkt wat u kunt doen met e-books die u heeft “gekocht”. Vaak zijn mensen die boeken met DRM kopen niet op de hoogte van de omvang van deze beperkingen. Deze beperkingen voorkomen dat u het e-boek naar uw wens opnieuw formatteert, inclusief het aanbrengen van stilistische wijzigingen zoals het aanpassen van de lettergroottes, hoewel er software is die u in staat stelt dergelijke dingen te doen voor niet-DRM-boeken. Mensen zijn vaak verbaasd dat een e-book dat ze in een bepaald formaat hebben gekocht niet kan worden geconverteerd naar een ander formaat als het e-book DRM heeft. Dus als je een Amazon Kindle hebt en een boek koopt dat door Barnes and Nobles wordt verkocht, moet je weten dat als dat e-boek DRM heeft, je het niet op je Kindle kunt lezen. Merk op dat ik het heb over een boek dat je koopt, niet steelt of piraat maar KOOPT.

Wat doet DRM voor auteurs?

Uitgevers van DRMed-e-books beweren dat de DRM er alleen is voor de auteurs en om hun artistieke integriteit te beschermen en piraterij te voorkomen. Maar DRM voorkomt piraterij NIET. Mensen die inhoud willen pirateren of illegale inhoud willen gebruiken, doen het nog steeds en slagen daarin. De drie belangrijkste DRM-schema’s voor e-books van vandaag worden beheerd door Amazon, Adobe en Barnes en Noble en alle drie de DRM-schema’s zijn gekraakt. Alles wat DRM doet is het ongemak van legitieme gebruikers. Er kan worden beweerd dat het auteurs in feite schaadt, aangezien mensen die het boek zouden hebben gekocht ervoor kiezen om een illegale versie te vinden, omdat ze niet bereid zijn om DRM te accepteren. Degenen die zouden piraten zonder DRM, doen dat ook in haar aanwezigheid. Nogmaals, het belangrijkste punt is dat DRM * piraterij * niet voorkomt. DRM is dus niet alleen zinloos en schadelijk voor kopers van e-books, maar ook geldverspilling.

DRM en vrijheid

Hoewel digitale inhoud zowel informatie als creatieve werken gemakkelijk voor iedereen beschikbaar kan maken en de mensheid kracht kan geven, is dit niet in het belang van sommige uitgevers die mensen van deze mogelijkheid van vrijheid willen afhouden, simpelweg om hun relevantie te behouden in een wereld die zo snel ontwikkelt dat ze het niet kunnen bijhouden.

Waarom ondersteunt Calibre DRM niet?

calibre is open source software terwijl DRM van nature gesloten is. Als calibre openen/bekijken van DRM-bestanden zou ondersteunen, kan het gemakkelijk aangepast worden tot hulpmiddel voor het verwijderen van DRM, illegaal volgens huidige wetten. Open source software en DRM zijn een botsing van principes. Bij DRM draait alles om het controleren van de gebruiker, bij open source-software om het versterken van de gebruiker. De twee kunnen niet samen bestaan.

Wat is de mening van calibre over contentproviders?

We geloven vast dat auteurs en andere inhoudsleveranciers gecompenseerd moeten worden voor hun werk, maar DRM is daar niet de manier voor. We ontwikkelen deze database met DRM-vrije e-boeken uit verschillende bronnen om u te helpen DRM-vrije alternatieven te vinden en om onafhankelijke auteurs en uitgevers van DRM-vrije e-boeken te helpen hun inhoud te publiceren. We hopen dat u dit nuttig zult vinden en we verzoeken u de inhoud die u hier ter beschikking wordt gesteld, niet te piraten.

Hoe kan ik DRM helpen bestrijden?

Als e-boek koper/lezer kan u helpen DRM te bestrijden. Koop geen e-boeken met DRM. Er zijn uitgevers die DRM vrije e-boeken uitgeven. Doe een inspanning om te zien of zij het e-boek dat u zoekt, aanbieden. Houdt u van boeken van bepaalde onafhankelijke auteurs die DRM-vrije e-boeken verkopen, en het kan eraf, doe een donatie. Dit is goed besteed geld omdat hun e-boeken meestal goedkoper zijn (er zijn uitzonderingen) dan wat u zou kopen van DRM boekenuitgevers en werken waarschijnlijk op alle toestellen die u in de toekomst zal hebben, u de kost besparend ze terug te moeten kopen. Ontmoedig geen uitgevers en auteurs van DRM-vrije boeken door hun inhoud te piraten. Inhoudsleveranciers verdienen een vergoeding voor hun werk. Straf hen niet voor hun poging uw leeservaring beter te maken door het aanbieden van DRM-vrije -boeken. Op de lande duur is dit nadelig voor u. Als u boeken heeft gekocht van verkopers die boeken met en zonder DRM aanbieden, niet wetend of ze DRM aanbieden of niet, let erop een commentaar na te laten op hun site om toekomstige klanten te informeren over hun DRM status. Veel verkopers vinden het niet belangrijk om duidelijk aan te geven of een boek geDRMed is of niet. `Hier <https://www.defectivebydesign.org/guide/ebooks>`_vind u een gids voor DRM-vrij leven.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- vorige |
- Start »
- Woordenlijst

Woordenlijst

RSS

RSS (Really Simple Syndication) is een web feed formaat gebruikt om vaak geüpdatete inhoud te publiceren, zoals nieuwsartikelen, blogposts, enz. Het is een formaat dat bijzonder geschikt is om door computers gelezen te worden en dus de beste manier om inhoud van het web in een e-boek te krijgen. Er zijn veel andere feed formaten in gebruik op het internet en calibre begrijpt de meeste. Het heeft met name een goede ondersteuning voor het  ATOM formaat, vaak gebruikt voor blogs.

recept

Een recept is een reeks instructies dat calibre aanleert hoe het een online nieuwsbron, zoals een tijdschrift of een blog, omzet in een e-boek. Een recept is in hoofdzaak Python <https://www.python.org> _ code. Als zodanig is het in staat willekeurig complexe nieuwsbronnen om te zetten in e-boeken. Op het eenvoudigste niveau is het slechts een set variabelen, zoals URL’s, die calibre genoeg informatie geven om op het internet te gaan en het nieuws te downloaden.

HTML

HTML (Hyper Text Mark-Up Language), een subset van Standard Generalized Mark-Up Language (SGML) voor elektronische publicatie, is de standaard gebruikt op het World Wide Web.

CSS

CSS (Cascading Style Sheets) is een taal gebruikt om te beschrijven hoe een HTML document moet worden weergegeven (visuele weergave).

API

API (Application Programming Interface) is een broncode interface aangeboden door een bibliotheek om vragen voor diensten van computerprogramma’s te ondersteunen.

LRF

LRF Het e-boek formaat gelezen door de SONY e-boek readers.

URL

URL (Uniform Resource Locator) bijvoorbeeld: http://example.com

regexp

Reguliere expressies lever een bondige en flexibele manier om tekststrings of interest, zoals bepaalde karakters, woorden of karakterpatronen te identificeren. Kijk op regexp syntax [https://docs.python.org/library/re.html] voor de syntax van reguliere expressies gebruikt in Python.

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Python-moduleïndex

Python-moduleïndex
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`

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Overzicht: module broncode

Alle modules waarvoor de broncode beschikbaar is

- 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

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.customize

Broncode voor 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

[documentatie] 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

[documentatie] 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

[documentatie] 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()

[documentatie] 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()

[documentatie] 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()

[documentatie] 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

[documentatie] 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()

[documentatie] 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)

[documentatie] 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)

# }}}

[documentatie] 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')

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

# }}}

[documentatie] 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

[documentatie] 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

# }}}

[documentatie] 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

[documentatie] 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

# }}}

[documentatie] 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

[documentatie] 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()

[documentatie] 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')

# }}}

[documentatie] 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

[documentatie] 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

# }}}

[documentatie] 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

[documentatie] 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') # }}}

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.customize »
- calibre.customize.conversion

Broncode voor 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

[documentatie] 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()

[documentatie] 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()

[documentatie] 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

[documentatie] def postprocess_book(self, oeb, opts, log): ''' Called to allow the input plugin to perform postprocessing after the book has been parsed. ''' pass

[documentatie] 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

[documentatie] 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)

[documentatie] 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()

[documentatie] 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:')

[documentatie] 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

[documentatie] 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

[documentatie] 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)

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.db.cache

Broncode voor 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 contextlib import closing 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, 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'})

[documentatie] 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 # }}}

[documentatie] @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 {{{

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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

[documentatie] @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

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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))

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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 {{{

[documentatie] @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)

[documentatie] @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

[documentatie] @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

[documentatie] @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)

[documentatie] @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 ()

[documentatie] @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()

[documentatie] @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)

[documentatie] @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]))

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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]

[documentatie] @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

[documentatie] @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}

[documentatie] @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()}

[documentatie] @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}

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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()

[documentatie] @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

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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

[documentatie] @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

[documentatie] @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()

[documentatie] @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)

[documentatie] @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)

[documentatie] @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))

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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

[documentatie] @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[documentatie] @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

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)}

[documentatie] @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

[documentatie] @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

[documentatie] @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

[documentatie] @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

[documentatie] @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

[documentatie] @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

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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

[documentatie] @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')

[documentatie] @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}

[documentatie] @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)

[documentatie] @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[documentatie] @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))

[documentatie] @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

[documentatie] @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}

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @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)

[documentatie] @write_api def expire_old_trash(self): ' Expire entries from the trash that are too old ' self.backend.expire_old_trash()

[documentatie] @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

[documentatie] @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

[documentatie] @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 with exporter.start_file(key, mtime=mdata.get('mtime')) as dest: self._copy_format_to(book_id, fmt, dest, report_file_size=dest.ensure_space) 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, report_file_size=dest.ensure_space): 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)}

[documentatie] @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

[documentatie] @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

[documentatie] @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

[documentatie] @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 with open(os.path.join(library_path, 'metadata.db'), 'wb') as f: src = importer.start_file(metadata['metadata.db'], 'metadata.db for ' + library_path) shutil.copyfileobj(src, f) src.close() 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 with open(os.path.join(library_path, 'full-text-search.db'), 'wb') as f: src = importer.start_file(metadata['full-text-search.db'], 'full-text-search.db for ' + library_path) shutil.copyfileobj(src, f) src.close() 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 closing(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 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 iteritems(fmt_key_map): if fmt == '.cover': with closing(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 closing(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 closing(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 progress is not None: progress(_('Completed'), total, total) return cache # }}}

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.devices.interface

Broncode voor 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

[documentatie] 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

[documentatie] @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

[documentatie] 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

[documentatie] 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()

[documentatie] 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()

# }}}

[documentatie] 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()

[documentatie] 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

[documentatie] 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

[documentatie] 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()

[documentatie] 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()

[documentatie] def post_yank_cleanup(self): ''' Called if the user yanks the device without ejecting it first. ''' raise NotImplementedError()

[documentatie] 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()

[documentatie] 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()

[documentatie] 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 {}

[documentatie] 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()

[documentatie] 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()

[documentatie] 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()

[documentatie] 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()

[documentatie] 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()

[documentatie] @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()

[documentatie] def delete_books(self, paths, end_session=True): ''' Delete books at paths on device. ''' raise NotImplementedError()

[documentatie] @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()

[documentatie] 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()

[documentatie] 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()

[documentatie] @classmethod def config_widget(cls): ''' Should return a QWidget. The QWidget contains the settings for the device interface ''' raise NotImplementedError()

[documentatie] @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()

[documentatie] @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()

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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()

[documentatie] 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()

[documentatie] 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 {}

[documentatie] def set_user_blacklisted_devices(self, devices): ''' Set the list of device uids that should be ignored by this driver. ''' pass

[documentatie] 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()

[documentatie] 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.

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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))

[documentatie] 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

[documentatie] def supports_collections(self): ''' Return True if the device supports collections for this book list. ''' raise NotImplementedError()

[documentatie] 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()

[documentatie] def remove_book(self, book): ''' Remove a book from the booklist. Correct any device metadata at the same time ''' raise NotImplementedError()

[documentatie] 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()

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.devices.usbms.cli

Broncode voor 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()

[documentatie] 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)

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.devices.usbms.device

Broncode voor 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

[documentatie] 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

[documentatie] 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)

[documentatie] 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

[documentatie] 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

[documentatie] 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)

[documentatie] 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

[documentatie] 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

[documentatie] 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 # ------------------------------------------------------

[documentatie] 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)

[documentatie] 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 = {}

[documentatie] 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

[documentatie] 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)

[documentatie] def filename_callback(self, default, mi): ''' Callback to allow drivers to change the default file name set by :meth:`create_upload_path`. ''' return default

[documentatie] 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

[documentatie] def get_annotations(self, path_map): ''' Resolve path_map to annotation_map of files found on the device ''' return {}

[documentatie] 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)

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.devices.usbms.driver

Broncode voor 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. [documentatie] 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

[documentatie] 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)

[documentatie] 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

[documentatie] 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[documentatie] 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])))

[documentatie] 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

[documentatie] 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

[documentatie] 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)))

[documentatie] 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 [documentatie] 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

[documentatie] @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

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.ebooks.metadata.book.base

Broncode voor 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(',', '|')

[documentatie] 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

[documentatie] 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)

[documentatie] 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)

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] def standard_field_keys(self): ''' return a list of all possible keys, even if this book doesn't have them ''' return STANDARD_METADATA_FIELDS

[documentatie] def custom_field_keys(self): ''' return a list of the custom fields in this book ''' return iter(object.__getattribute__(self, '_data')['user_metadata'])

[documentatie] 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'])))

[documentatie] 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)

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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)

[documentatie] 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('--------------')

[documentatie] 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)

[documentatie] 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)

[documentatie] 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

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.ebooks.metadata.sources.base

Broncode voor 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()

[documentatie] @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

[documentatie] 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 {{{

[documentatie] 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

[documentatie] def customization_help(self): return 'This plugin can only be customized using the GUI'

[documentatie] def config_widget(self): from calibre.gui2.metadata.config import ConfigWidget return ConfigWidget(self)

[documentatie] 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 {{{

[documentatie] 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

[documentatie] 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

[documentatie] 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]

[documentatie] 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

[documentatie] 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 {{{

[documentatie] 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

[documentatie] 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

[documentatie] 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,)

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

# }}}

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.ebooks.oeb.polish.container

Broncode voor 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)) # }}}

[documentatie] 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)

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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[documentatie] 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)

[documentatie] 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

[documentatie] 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')

[documentatie] 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))

[documentatie] def name_to_abspath(self, name): ' Convert a canonical name to an absolute OS dependent path ' return name_to_abspath(name, self.root)

[documentatie] 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))

[documentatie] 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

[documentatie] 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)

[documentatie] 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)

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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)}

[documentatie] 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)

[documentatie] 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

[documentatie] 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

[documentatie] 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]

[documentatie] 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)

[documentatie] 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)

[documentatie] 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)

[documentatie] def dirty(self, name): ''' Mark the parsed object corresponding to name as dirty. See also: :meth:`parsed`. ''' self.dirtied.add(name)

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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'))

[documentatie] 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

[documentatie] 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)

[documentatie] 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)

[documentatie] 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

[documentatie] 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)

[documentatie] 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()

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.ebooks.oeb.polish.cover

Broncode voor 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)

[documentatie] 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)

[documentatie] 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)

[documentatie] 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

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.ebooks.oeb.polish.css

Broncode voor 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

[documentatie] 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

[documentatie] 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

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.ebooks.oeb.polish.fonts

Broncode voor 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

[documentatie] 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

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.ebooks.oeb.polish.jacket

Broncode voor 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)

[documentatie] 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)

[documentatie] 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

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.ebooks.oeb.polish.pretty

Broncode voor 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)

[documentatie] 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')

[documentatie] 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')

[documentatie] 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')

[documentatie] 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')

[documentatie] 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)

[documentatie] 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)

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.ebooks.oeb.polish.replace

Broncode voor 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.ebooks.oeb.polish.split

Broncode voor 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

[documentatie] 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

[documentatie] 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)

[documentatie] 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)

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.ebooks.oeb.polish.toc

Broncode voor 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

[documentatie] 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[documentatie] 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

[documentatie] 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

[documentatie] 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

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.gui2.actions

Broncode voor 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)

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] def library_changed(self, db): ''' Called whenever the current library is changed.

:param db: The LibraryDatabase corresponding to the current library.

''' pass

[documentatie] 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

[documentatie] def initialization_complete(self): ''' Called once per action when the initialization of the main GUI is completed. ''' pass

[documentatie] 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()

[documentatie] 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()

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.gui2.preferences

Broncode voor 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

[documentatie] 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

[documentatie] 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()

[documentatie] 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()

[documentatie] def restore_defaults(self): ''' Should set all config values to their defaults. ''' pass

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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 = {}

[documentatie] 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

[documentatie] def initialize(self): for setting in self.settings.values(): setting.initialize()

[documentatie] def commit(self, *args): restart_required = False for setting in self.settings.values(): rr = setting.commit() if rr: restart_required = True return restart_required

[documentatie] 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() # }}}

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.gui2.tweak_book.boss

Broncode voor 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

[documentatie] 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())

[documentatie] 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')

[documentatie] 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 '...'))

[documentatie] 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()

[documentatie] 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

[documentatie] 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)

# }}}

[documentatie] 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)

[documentatie] 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

[documentatie] 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)

[documentatie] 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)

[documentatie] 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

[documentatie] 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)

[documentatie] 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)# }}}

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.gui2.tweak_book.plugin

Broncode voor 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

[documentatie] 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()

[documentatie] 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'))

[documentatie] 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]

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.utils.formatter_functions

Broncode voor 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)

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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]

[documentatie] 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

[documentatie] 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

[documentatie] 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)

[documentatie] 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)

[documentatie] 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)

[documentatie] 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)

[documentatie] 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)))

[documentatie] 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)))

[documentatie] 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)))

[documentatie] 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))

[documentatie] 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])

[documentatie] 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)

[documentatie] 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)

[documentatie] 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

[documentatie] 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

[documentatie] 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 ''

[documentatie] 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)

[documentatie] 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)

[documentatie] 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)

[documentatie] 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_)]

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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()

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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)

[documentatie] 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)

[documentatie] 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()

[documentatie] 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

[documentatie] 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

[documentatie] 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]))

[documentatie] 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)

[documentatie] 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 ''

[documentatie] 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 ''

[documentatie] 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()

[documentatie] 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 ''

[documentatie] 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 ''

[documentatie] 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 ''

[documentatie] 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 ''

[documentatie] 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 ''

[documentatie] 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 ''

[documentatie] 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))

[documentatie] 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

[documentatie] 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

[documentatie] 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()

[documentatie] 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()

[documentatie] 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)

[documentatie] 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)

[documentatie] 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()

[documentatie] 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()

[documentatie] 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)

[documentatie] 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 ''

[documentatie] 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 ''

[documentatie] 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 ''

[documentatie] 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 ''

[documentatie] 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'

[documentatie] 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 ''

[documentatie] 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'

[documentatie] 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())

[documentatie] 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())

[documentatie] 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])

[documentatie] 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())

[documentatie] 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)

[documentatie] 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)

[documentatie] 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"))

[documentatie] 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

[documentatie] 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)

[documentatie] 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)

[documentatie] 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')

[documentatie] 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)))

[documentatie] 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)))

[documentatie] 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)

[documentatie] 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)

[documentatie] 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()

[documentatie] 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()

[documentatie] 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

[documentatie] 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)

[documentatie] 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()

[documentatie] 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()

[documentatie] 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)

[documentatie] 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)

[documentatie] 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()

[documentatie] 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)

[documentatie] 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()

[documentatie] 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()

[documentatie] 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 ""

[documentatie] 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')

[documentatie] 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))

[documentatie] 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()

[documentatie] 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()

[documentatie] 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()

[documentatie] 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 ''

[documentatie] 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()

[documentatie] 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()

[documentatie] 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)

[documentatie] 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()

[documentatie] 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)[documentatie] 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)[documentatie] 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)

[documentatie] 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)

[documentatie] 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)

[documentatie] 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)

[documentatie] 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)

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Navigatie

- modules
- Start »
- Modulebroncode »
- calibre.web.feeds.news

Broncode voor 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

[documentatie] 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)

[documentatie] 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()

[documentatie] 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

[documentatie] 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)

[documentatie] 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)

[documentatie] 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

[documentatie] 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

[documentatie] @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()

[documentatie] @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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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'))

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] def cleanup(self): ''' Called after all articles have been download. Use it to do any cleanup like logging out of subscription sites, etc. ''' pass

[documentatie] 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('/'))})

[documentatie] 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)[documentatie] 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

[documentatie] 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

[documentatie] 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()

[documentatie] 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)

[documentatie] 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()

[documentatie] 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)

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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

[documentatie] 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)

[documentatie] 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))

[documentatie] 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[documentatie] @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)

[documentatie] @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')

© Auteursrecht Kovid Goyal. Laatst geüpdated op apr 19, 2024.

Table of Contents
calibre Gebruikershandleiding

De Grafische Gebruikersinterface

1. Acties

1. Boeken toevoegen
2. Metadata bewerken
3. Boeken converteren
4. Tonen
5. Naar apparaat sturen
6. Nieuws ophalen
7. Bibliotheek
8. Apparaat
9. Op schijf opslaan
10. Verbinden/delen
11. Boeken verwijderen

2. Voorkeuren
3. Catalogi
4. Zoeken & sorteren
5. De zoek interface
6. Zoekopdrachten opslaan
7. Searching the full text of all books
8. Virtuele bibliotheken
9. Temporarily marking books
10. Raadt metadata op basis van bestandsnamen
11. Boekgegevens
12. Labelbrowser
13. Omslagenrooster
14. Omslagbrowser
15. Adding notes for authors, series, etc.
16. Snel-weergave
17. Taken
18. Toetsenbord sneltoetsen

Uw favoriete nieuws website toevoegen

1. Compleet automatisch ophalen

1. De Calibre blog

2. bbc.co.uk

2. Het ophaalproces aanpassen

1. Gebruik van de print versie van bbc.co.uk
2. Artikel stijlen vervangen
3. Splitsen en verdelen
4. Werkend voorbeeld

3. Tips voor ontwikkelen van nieuwe recepten

4. Verder lezen

5. API documentatie

1. API documentatie voor recepten

1. BasicNewsRecipe

De e-boek viewer

1. Starten van de e-boek viewer

2. Door een e-book navigeren

1. Bladwijzers
2. Inhoudsopgave
3. Navigeren op locatie
4. Referentiemodus

3. Tekst highlighten
4. Lees hardop
5. In de tekst zoeken
6. Links volgen met enkel het toetsenbord

7. De look and feel van de leeservaring personalizeren

8. Opzoeken in woordenboek
9. Kopieer tekst en afbeeldingen
10. Inzoomen op afbeeldingen
11. Toetsenbord sneltoetsen
12. Niet-opnieuw stroombare inhoud

13. Uw boek ontwerpen om goed te werken in calibre viewer

E-boek conversie

1. Inleiding

2. Uitstraling & gevoel

1. Lettertypen
2. Tekst
3. Opmaak
4. Stijlen
5. Transformeer stijlen
6. Transformeer HTML

3. Pagina opmaak
4. Heuristische verwerking
5. Zoeken & vervangen

6. Structuurdetectie

1. Hoofdstukken en pagina-eindes

2. Diversen

7. Inhoudsopgave

8. Gebruik afbeeldingen als hoofdstuktitels tijdens de conversie van HTML invoer-documenten

9. Gebruik tag attributen om de tekst te halen t.b.v. Inhoudsopgave

10. Hoe opties worden ingesteld/opgeslagen voor conversie

11. Formaat specifieke tips

1. Microsoft Word documenten omzetten

1. Oudere .doc bestanden

2. TXT documenten omzetten
3. PDF-documenten omzetten
4. Stripboek collecties
5. EPUB geavanceerde opmaak demo
6. ODT documenten omzetten

7. Naar PDF omzetten

1. Kop- en voetteksten

2. Afdrukbare inhoudsopgave

3. Aangepaste paginamarges voor individuele HTML-bestanden

E-boeken bewerken

Basis werkstroom

De Verkenner

1. Bestanden hernoemen
2. Bestanden samenvoegen
3. Volgorde van tekstbestand veranderen
4. Kaft markeren
5. Bestanden verwijderen
6. Bestanden exporteren

7. Nieuwe afbeeldingn/lettertype/enz. toevoegen of nieuwe lege bestanden maken

8. Bestanden vervangen

9. Het efficiënt linken van sylesheets aan HTML-bestanden

Zoeken & vervangen

1. Opgeslagen zoekopdrachten
2. Functie modus
3. Zoeken tussen HTML labels

Geautomatiseerde functies

1. Inhoudsopgave bewerken
2. Het boek controleren
3. Cover toevoegen
4. Insluiten van gerefereerde lettertypes
5. Ingevoegde lettertypes uitdunnen
6. Interpunctie verbeteren
7. Transformeer CSS eigenschappen
8. Ongebruikte CSS regels verwijderen
9. Repareer HTML
10. Alle bestanden leesbaarder maken
11. Een inline Inhoudsopgave invoegen
12. Semantiek instellen
13. Stijl informatie filteren
14. Boek ingewanden upgraden

Checkpoints

Het Live voorbeeld paneel

1. HTML bestanden splitsen

Het realtime CSS paneel

Diverse gereedschappen

1. De Inhoudsopgave weergave

2. Spellingscontrole van woorden in het boek

1. Nieuwe woordenboeken toevoegen

3. Speciale tekens invoegen
4. De code controle weergave
5. Controle van externe links
6. Externe bronnen downloaden
7. Bestanden sorteren in mappen op type

8. Importeren van bestanden in andere e-book formaten als EPUB

9. De rapporten functie

Speciale mogelijkheden in de code editor

1. Syntaxis markering
2. Contextgevoelige hulp
3. Auto-aanvullen
4. Fragmenten

De calibre Content server

1. Toegang krijgen tot de Content server vanaf andere toestellen

1. Toegang krijgen tot de server vanaf apparaten in uw thuisnetwerk

1. Problemen oplossen voor de thuisnetwerkverbinding

2. Toegang krijgen tot de server van overal op het internet

2. De server interface

1. De boekenlijst

2. De boekreader

3. Browserondersteuning

4. Offline support inschakelen

5. Gebruikersaccounts enkel beheren vanop de commandoregel

6. Integratie van de Calibre Content-server in andere servers

1. Een volledig virtuele host gebruiken

2. Met een URL prefix

7. Een dienst creëren voor de calibre server op een modern Linux systeem

E-books vergelijken

1. De vergelijking weergave begrijpen

2. Lancering van de vergelijkingstool

1. Twee e-book bestanden vergelijken

2. Vergelijk de ORIGINAL_FMT met FMT

3. Vergelijk een checkpoint met de huidige staat van het boek gedurende het bewerken

Metadata van e-boeken aanpassen

1. Metadata bewerken van één boek tegelijk

1. Metadata aan het downloaden
2. Boekformaten beheren
3. Alles over de omslagen

2. Metadate van meerdere boeken tegelijkertijd bewerken

1. Zoek en vervangen

2. Metadata aan het bulk downloaden

3. Adding extra data files to a book

Veel gestelde vragen

E-book formaat conversie

1. Welke conversie formaten worden ondersteund door Calibre?

2. Wat zijn de beste bronformaten voor omzetting?

3. Ik heb een PDF geconverteerd, maar het bestand heeft verschillende soorten problemen?

4. Hoe converteer ik mijn bestand met niet-Engelse karakters of smart quotes?

5. Hoe zit het met inhoudsopgaven in MOBI bestanden?

6. Hoe zet ik een set HTML-bestanden om in een specifieke volgorde?

7. De EPUB die ik maakte met Calibre is ongeldig?

8. Hoe maak ik gebruik van enkele van de geavanceerde features van de conversietools?

Toestel integratie

1. Welke apparaten worden door Calibre ondersteund?

2. Hoe kan ik helpen om mijn apparaat te laten ondersteunen door Calibre?

3. Mijn apparaat wordt niet herkend door Calibre?

4. Mijn apparaat is niet-standaard of ongebruikelijk. Hoe kan ik verbinding ermee maken?

5. Hoe gebruik ik Calibre op mijn iPad/iPhone/iPod touch?

1. De Content server gebruiken.

6. How do I use calibre with my Android phone/tablet or Kindle Fire?

1. Een USB kabel gebruiken

2. Door de lucht

7. Heb ik toegang tot mijn calibre boeken via de browser op mijn Kindle of ander leestoestel?

8. Ik kan geen e-mails verzenden vanuit 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. Ik krijg steeds een foutmelding wanneer ik Calibre gebruik met de 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. De omslagen voor mijn MOBI bestanden worden niet meer getoond in Kindle voor PC/Kindle voor Android/iPad enz.

14. Ik heb boeken overgezet op mijn Kindle, gebruik makend van Calibre. Waarom worden deze niet getoond?

Library management

1. Waar worden mijn boekbestanden opgeslagen?

2. Hoe gaat Calibre om met auteursnamen en sorteren?

3. Why doesn’t calibre let me store books in my own folder structure?

4. Waarom heeft Calibre geen kolom voor foo?

5. Kan ik ook een kolom krijgen die formaten of ISBN toont?

6. Hoe kan ik de Calibre gegevens van de ene computer naar de andere overzetten?

7. De boekenlijst in Calibre is leeg!

8. Waarom krijg ik foutmeldingen met met Calibre bibliotheek op een netwerk map/NAS?

Diversen

1. Amazon is stopping email delivery of MOBI files?

2. Ik wil dat Calibre nieuws download van mijn favoriete nieuws website.

3. Vanwaar de naam Calibre?

4. Why does calibre show only some of my fonts on macOS?

5. Calibre start niet in Windows?

6. Crashed of reageert niet Calibre regelmatig?

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. I heb het installatie bestand gedownload, waarom werkt het niet?

12. Mijn antivirus programma beweert dat Calibre een virus/trojaan paars is?

13. Hoe maak ik een backup van Calibre?

14. Hoe kan ik gekochte EPUB boeken gebruiken met Calibre? (of wat ik doen met .ascm bestanden)?

15. Ik krijg een “Toegang geweigerd” fout?

16. Hoe kan ik de metadata van de metagegevens lezen op mijn lezer?

17. Hoe kan ik Calibre instellen om mijn HTTP proxy te gebruiken?

18. Ik wil nieuwe functionaliteit toevoegen aan Calibre. Wat kan ik doen?

19. Waarom heeft Calibre geen automatische updates?

20. Hoe zit het met de licentie van Calibre?

21. Hoe start ik Calibre van mijn USB stick?

22. How do I run parts of calibre like news download and the Content server on my own Linux server?

Lesboeken

Uw favoriete nieuws website toevoegen

1. Compleet automatisch ophalen

1. De Calibre blog

2. bbc.co.uk

2. Het ophaalproces aanpassen

1. Gebruik van de print versie van bbc.co.uk
2. Artikel stijlen vervangen
3. Splitsen en verdelen
4. Werkend voorbeeld

3. Tips voor ontwikkelen van nieuwe recepten

4. Verder lezen

5. API documentatie

1. API documentatie voor recepten

Subgroepen beheren van boeken, bijvoorbeeld “genre”

1. Configuratie
2. Zoeken
3. Restricties
4. Handige sjabloonfuncties

XPath handleiding

1. Selecteren op naam van label
2. Selecteren op eigenschap
3. Selecteren op label inhoud
4. Voorbeeld e-boek
5. XPath ingebouwde functies

De calibre sjabloontaal

1. Basis sjablonen

2. Geavanceerde opmaak

3. Sjablonen gebruiken om aangepaste kolommen te definiëren

4. Templates and plugboards

5. Functies gebruiken in sjablonen - Eén Functie Modus

6. Algemene Programma Modus

7. Meer complexe programma’s in sjabloonexpressies - Sjabloon Programma Modus

8. Python Template Mode

9. Opgeslagen sjablonen

10. Bijkomende informatie voor sjablonen voorzien

11. Notes on the difference between modes

12. User-defined Python template functions

13. Extra opmerkingen voor opslaan/versturen van sjablonen

14. Tips

15. Functie referentie

1. Reference for all built-in template language functions

Alles over het gebruik van reguliere expressies in calibre

1. Allereerst een waarschuwing en bemoediging

2. Waar in calibre kan je reguliere expressies gebruiken?

3. Wat is een reguliere expressie eigenlijk?

4. Iets meer uitleg graag?

5. Dat klinkt niet heel moeilijk. Ga door.

6. Prachtig! Nu begint het ergens op te lijken!

7. Nou, deze speciale karakters zijn erg netjes en zo maar wat als ik een overeenkomst met een punt of vraagteken wil?

8. Wat zijn dan de meest nuttige sets?

9. Maar als ik een paar verschillende strings had die ik wilde laten overeenkomen worden dingen ingewikkeld?

10. U miste…

11. In het begin zei u dat er een manier was om een reguliere expressie hoofdletterongevoelig te maken?

12. Ik denk dat ik deze reguliere expressies begin te begrijpen nu … hoe gebruik ik ze in calibre?

1. Omzettingen
2. Boeken toevoegen
3. Massabewerking van metadata

13. Snelle referentie

1. Quick reference voor regexp syntax

14. Credits

Uw eigen plugins schrijven om de functionaliteit van calibre uit te breiden

1. Opbouw van een calibre plugin

2. Een Gebruikersinterface plugin

1. __init__.py
2. ui.py
3. main.py

4. Ophalen bronnen uit het plug-in ZIP bestand

5. Gebruikers-configuratie van uw plugin inschakelen

3. Boek plug-ins bewerken

1. main.py

4. Vertalingen toevoegen aan uw plugin
5. De plug-in API
6. Plug-ins foutopsporing
7. Meer voorbeelden van plugins
8. Uw plugins delen met anderen

Wiskunde typesetten in e-boeken

1. Een simpel HTML bestand met wiskunde

2. Meer informatie

Creëren van AZW3 • EPUB • MOBI-catalogi

1. Selecteer boeken voor de catalogus
2. Inbegrepen afdelingen
3. Voorvoegsels
4. Uitgesloten boeken
5. Uitgesloten genres
6. Overige opties
7. Aangepaste catalogus omslagen
8. Extra hulpbronnen

Virtuele bibliotheken

1. Virtuele bibliotheken maken

1. Voorbeelden van handige Virtuele bibliotheken

2. Werken met virtuele bibliotheken

3. Virtuele bibliotheken gebruiken in zoekopdrachten

4. Gebruik van extra restricties

Het calibre:// URL schema

1. Wissen naar een specifieke bibliotheek

2. Toon een specifiek boek in calibre

3. Open een specifiek boek in de e-boek viewer op een specifieke plaats

4. Naar boeken zoeken

5. Open a book details window on a book in some library

6. Open the notes associated with an author/series/etc.

7. Hex codering van URL parameters

calibre aanpassen

1. Omgevingsvariabelen
2. Truckjes
3. Iconen, sjablonen, enz. overschrijven
4. Maak uw eigen icon thema voor Calibre
5. calibre personaliseren met plugins

Opdrachtpromt Gedocumenteerde commando’s
calibre

1. [Opties]

calibre-customize

1. [Opties]

calibre-debug

1. [Opties]

calibre-server

1. [Opties]

calibre-smtp

1. [Opties]

calibredb

1. ALGEMENE OPTIES
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. INVOEROPTIES
2. UITVOEROPTIES
3. LOOK AND FEEL
4. HEURISTISCHE VERWERKING
5. ZOEK EN VERVANG
6. STRUCTUUR DETECTIE
7. INHOUDSOPGAVE
8. METAGEGEVENS
9. FOUTOPSPORING

ebook-edit

1. [Opties]

ebook-meta

1. [Opties]

ebook-polish

1. [Opties]

ebook-viewer

1. [Opties]

fetch-ebook-metadata

1. [Opties]

lrf2lrs

1. [Opties]

lrfviewer

1. [Opties]

lrs2lrf

1. [Opties]

web2disk

1. [Opties]

Ongedocumenteerde commando’s

Opzetten van Calibre in een development omgeving

1. Ontwerp filosofie

1. Code opmaak

2. De code verkrijgen

1. Uw aanpassingen insturen voor productie

3. Windows ontwikkel omgeving
4. macOS ontwikkelomgeving
5. Linux ontwikkel omgeving

6. Afzonderlijke “normale” en “ontwikkel” calibre installaties op dezelfde computer hebben

7. Debug tips

1. Gebruik van print statements

2. Een interactieve Python vertaler gebruiken

3. De Python foutzoeker als een externe foutzoeker gebruiken

4. De foutzoeker gebruiken in uw favoriete Python IDE

5. Willekeurige scripts uitvoeren in de calibre Python omgeving

8. Calibre gebruiken in uw projecten

1. Binaire installatie van Calibre

2. Broncode installatie op Linux

9. API installatie voor diverse onderdelen van Calibre

1. API documentatie voor recepten

1. BasicNewsRecipe

2. API documentatie voor plugins

1. Plugin
2. BestandsTypePlugin
3. Metadata plugins
4. Catalogus plugins
5. Metadata download plugins
6. Omzet plugins
7. Device drivers
8. User interface actions
9. Preferences plugins

3. API documentatie voor de gegevensbank interface

1. Cache

4. API documentatie voor e-book bewerking functies

1. Het container object

2. Component bestanden in een container beheren

3. Netjes afdrukken en auto-herstellen verwerkingsfouten

4. Boekomslagen beheren
5. Splitsen en samenvoegen van bestanden
6. Omslagen beheren
7. Werken met CSS
8. Werken met de inhoudsopgave
9. Bewerken Boekhulpmiddel
10. Controlling the editor’s user interface

Digital Rights Management (DRM)

1. Wat betekent DRM voor mij persoonlijk?

2. Wat doet DRM voor auteurs?

3. DRM en vrijheid

4. Waarom ondersteunt Calibre DRM niet?

5. Wat is de mening van calibre over contentproviders?

6. Hoe kan ik DRM helpen bestrijden?

Woordenlijst