diff --git a/Changelog.yaml b/Changelog.yaml
index e96c990cfc..71f7f1c52b 100644
--- a/Changelog.yaml
+++ b/Changelog.yaml
@@ -19,6 +19,124 @@
# new recipes:
# - title:
+- version: 0.7.44
+ date: 2011-02-04
+
+ new features:
+ - title: "Nook Color driver: Send downloaded news to the My Files/Magazines folder on the Nook Color. Also when getting the list of books on the device look at all folders in My Files, not just My Files/Books."
+
+ - title: "MOBI Output: Use the book uuid as the ASIN field and set cdetype to EBOK to allow Amazon furthest read tracking to work with calibre generated MOBI files."
+ tickets: [8721]
+
+ - title: "Comic input: Add an option to override the image size in the generated comic. Useful if you have a device whose screen size is not coverred by one of the available output profiles."
+ tickets: [7837]
+
+ - title: "Add a restore database option to the Library maintenance menu in the GUI"
+
+ - title: "TXT Output: Allow output in the textile markup language"
+
+ - title: "PML Output: Create multi-level Table of Contents"
+
+ - title: "Driver for the Archos 7O"
+
+ - title: "Search and Replace in the Bulk metadata dialog can now operate on the title_sort field as well"
+ tickets: [8732]
+
+ - title: "Allow changing the case of authors/tags/series etc. via the edit metadata dialog"
+
+ - title: "Connect/share menu: Re-organize to make it a little less easy to select email and delete instead of just email by mistake"
+
+ - title: "Heuristics: Improved Scene break detection and add option to control what scene breaks are replaced by."
+
+ - title: "SONY driver: Add option to not preserve aspect ratio of cover thumbnails."
+
+ - title: "BiBTeX catalog: Add on device column when available"
+
+ - title: "Add search to the plugin preferences dialog"
+
+ bug fixes:
+ - title: "Fix a bug that could cause fiels to be lost when changing metadata on east asian windows installs if the title and/or author is very long."
+ tickets: [8620]
+
+ - title: "Tag browser: Fix searching with items in a user category not owrking if the main category is hidden"
+ tickets: [8741]
+
+ - title: "Make completion for author/series/tags/etc. fields less disruptive"
+
+ - title: "Fix regression that broke the content server when user categories/custom columns are present"
+
+ - title: "Catalog generation: Handle user supplied templates more robustly"
+
+ - title: "Move the Tags to apply to newly added books option into Preferences->Adding books"
+ tickets: [8730]
+
+ - title: "Workaround for bug in Qt on OS X that caused crashes when reading metedata from two or more EPUB files with HTML covers that used embedded fonts. Now the embedded fonts are ignored on OS X."
+ tickets: [8643]
+
+ - title: "Fix regression that broke the use of the group searched terms tweak"
+ tickets: [8739]
+
+ - title: "Fix template program regression triggered by recursively calling the processor"
+
+ - title: "Fix mimetype sent by content server for PDB files"
+
+ - title: "OPF: Write title_sort as a calibre custom metadata field rather than as a file-as attribute on the title. This conforms to the OPF spec"
+ tickets: [7883]
+
+ - title: "SONY driver: Fix thumbnails being sent to SD card are sent to the wrong location. Also use correct thumbnail size so that the SONY does not regenerate the thumbnail on disconnect"
+
+ - title: "Do not discard the result of a conversion if the user opens the edit metadata dialog while the conversion is running"
+ tickets: [8672]
+
+ - title: "CHM Input: When the chm file lacks a hhc, lookf for index.html instead"
+ tickets: [8688]
+
+ - title: "EPUB Input: Filter some invalid media types from the spine"
+
+ - title: "RTF Input: More encoding handlig fixes."
+ tickets: [8678]
+
+ - title: "Linux binary build: Restore functioning of CALIBRE_DEVELOP_FROM, which was accidentally removed a few versions ago"
+
+ - title: "RTF Output: Retain html headings as rtf headings when converting to rtf. Also fix output of italics."
+ tickets: [8641, 8640]
+
+ - title: "LIT Input: Fix regression that broke handling of LIT files that contain txt data instead of html"
+
+ - title: "MOBI Input: Handle more non printing ASCII codes"
+ tickets: [8646]
+
+ - title: "Handle empty cover files more gracefully"
+ tickets: [8656]
+
+ - title: "Catalog geenration: Fix error when Pocketbook is connected and trying to geenrate catalog"
+ tickets: [8651]
+
+ - title: "Heuristics: Italicize common cases, reduce false positives."
+
+ - title: "Fix regression that caused reporting of device connection errors to break"
+
+ improved recipes:
+ - MSN Japan
+ - The Onion
+ - La Tribuna de
+ - Wall Street Journal
+ - "20 Minutos"
+ - LA Times
+ - Endgadget Japan
+ - Ledevoir
+ - Vijesti
+
+ new recipes:
+ - title: "Cinco Dias and BBC Mundo"
+ author: Luis Hernandez
+
+ - title: "Explosm"
+ author: Andromeda Rabbit
+
+ - title: "Cinco Dias"
+ author: Luis Hernandez
+
- version: 0.7.43
date: 2011-01-28
diff --git a/format_docs/compression/palmdoc.txt b/format_docs/compression/palmdoc.txt
new file mode 100644
index 0000000000..e5bcc95150
--- /dev/null
+++ b/format_docs/compression/palmdoc.txt
@@ -0,0 +1,54 @@
+About
+-----
+
+PalmDOC uses LZ77 compression techniques. DOC files can contain only compressed
+text. The format does not allow for any text formatting. This keeps files
+small, in keeping with the Palm philosophy. However, extensions to the format
+can use tags, such as HTML or PML, to include formatting within text. These
+extensions to PalmDoc are not interchangeable and are the basis for most eBook
+Reader formats on Palm devices.
+
+LZ77 algorithms achieve compression by replacing portions of the data with
+references to matching data that has already passed through both encoder and
+decoder. A match is encoded by a pair of numbers called a length-distance pair,
+which is equivalent to the statement "each of the next length characters is
+equal to the character exactly distance characters behind it in the
+uncompressed stream." (The "distance" is sometimes called the "offset" instead.)
+
+In the PalmDoc format, a length-distance pair is always encoded by a two-byte
+sequence. Of the 16 bits that make up these two bytes, 11 bits go to encoding
+the distance, 3 go to encoding the length, and the remaining two are used to
+make sure the decoder can identify the first byte as the beginning of such a
+two-byte sequence.
+
+PalmDoc combines LZ77 with a simple kind of byte pair compression.
+
+
+PalmDoc files are decoded as follows:
+-------------------------------------
+
+Read a byte from the compressed stream. If the byte is
+
+0x00: "1 literal" copy that byte unmodified to the decompressed stream.
+
+0x09 to 0x7f: "1 literal" copy that byte unmodified to the decompressed stream.
+
+0x01 to 0x08: "literals": the byte is interpreted as a count from 1 to 8, and
+that many literals are copied unmodified from the compressed stream to the
+decompressed stream.
+
+0x80 to 0xbf: "length, distance" pair: the 2 leftmost bits of this byte ('10')
+are discarded, and the following 6 bits are combined with the 8 bits of the
+next byte to make a 14 bit "distance, length" item. Those 14 bits are broken
+into 11 bits of distance backwards from the current location in the
+uncompressed text, and 3 bits of length to copy from that point
+(copying n+3 bytes, 3 to 10 bytes).
+
+0xc0 to 0xff: "byte pair": this byte is decoded into 2 characters: a space
+character, and a letter formed from this byte XORed with 0x80.
+
+Repeat from the beginning until there is no more bytes in the compressed file.
+
+PalmDOC data is always divided into 4096 byte blocks and the blocks are acted
+upon independently.
+
diff --git a/format_docs/compression/zip.txt b/format_docs/compression/zip.txt
new file mode 100644
index 0000000000..e658f9582b
--- /dev/null
+++ b/format_docs/compression/zip.txt
@@ -0,0 +1,3217 @@
+File: APPNOTE.TXT - .ZIP File Format Specification
+Version: 6.3.2
+Revised: September 28, 2007
+Copyright (c) 1989 - 2007 PKWARE Inc., All Rights Reserved.
+
+The use of certain technological aspects disclosed in the current
+APPNOTE is available pursuant to the below section entitled
+"Incorporating PKWARE Proprietary Technology into Your Product".
+
+I. Purpose
+----------
+
+This specification is intended to define a cross-platform,
+interoperable file storage and transfer format. Since its
+first publication in 1989, PKWARE has remained committed to
+ensuring the interoperability of the .ZIP file format through
+publication and maintenance of this specification. We trust that
+all .ZIP compatible vendors and application developers that have
+adopted and benefited from this format will share and support
+this commitment to interoperability.
+
+II. Contacting PKWARE
+---------------------
+
+ PKWARE, Inc.
+ 648 N. Plankinton Avenue, Suite 220
+ Milwaukee, WI 53203
+ +1-414-289-9788
+ +1-414-289-9789 FAX
+ zipformat@pkware.com
+
+III. Disclaimer
+---------------
+
+Although PKWARE will attempt to supply current and accurate
+information relating to its file formats, algorithms, and the
+subject programs, the possibility of error or omission cannot
+be eliminated. PKWARE therefore expressly disclaims any warranty
+that the information contained in the associated materials relating
+to the subject programs and/or the format of the files created or
+accessed by the subject programs and/or the algorithms used by
+the subject programs, or any other matter, is current, correct or
+accurate as delivered. Any risk of damage due to any possible
+inaccurate information is assumed by the user of the information.
+Furthermore, the information relating to the subject programs
+and/or the file formats created or accessed by the subject
+programs and/or the algorithms used by the subject programs is
+subject to change without notice.
+
+If the version of this file is marked as a NOTIFICATION OF CHANGE,
+the content defines an Early Feature Specification (EFS) change
+to the .ZIP file format that may be subject to modification prior
+to publication of the Final Feature Specification (FFS). This
+document may also contain information on Planned Feature
+Specifications (PFS) defining recognized future extensions.
+
+IV. Change Log
+--------------
+
+Version Change Description Date
+------- ------------------ ----------
+5.2 -Single Password Symmetric Encryption 06/02/2003
+ storage
+
+6.1.0 -Smartcard compatibility 01/20/2004
+ -Documentation on certificate storage
+
+6.2.0 -Introduction of Central Directory 04/26/2004
+ Encryption for encrypting metadata
+ -Added OS/X to Version Made By values
+
+6.2.1 -Added Extra Field placeholder for 04/01/2005
+ POSZIP using ID 0x4690
+
+ -Clarified size field on
+ "zip64 end of central directory record"
+
+6.2.2 -Documented Final Feature Specification 01/06/2006
+ for Strong Encryption
+
+ -Clarifications and typographical
+ corrections
+
+6.3.0 -Added tape positioning storage 09/29/2006
+ parameters
+
+ -Expanded list of supported hash algorithms
+
+ -Expanded list of supported compression
+ algorithms
+
+ -Expanded list of supported encryption
+ algorithms
+
+ -Added option for Unicode filename
+ storage
+
+ -Clarifications for consistent use
+ of Data Descriptor records
+
+ -Added additional "Extra Field"
+ definitions
+
+6.3.1 -Corrected standard hash values for 04/11/2007
+ SHA-256/384/512
+
+6.3.2 -Added compression method 97 09/28/2007
+
+ -Documented InfoZIP "Extra Field"
+ values for UTF-8 file name and
+ file comment storage
+
+V. General Format of a .ZIP file
+--------------------------------
+
+ Files stored in arbitrary order. Large .ZIP files can span multiple
+ volumes or be split into user-defined segment sizes. All values
+ are stored in little-endian byte order unless otherwise specified.
+
+ Overall .ZIP file format:
+
+ [local file header 1]
+ [file data 1]
+ [data descriptor 1]
+ .
+ .
+ .
+ [local file header n]
+ [file data n]
+ [data descriptor n]
+ [archive decryption header]
+ [archive extra data record]
+ [central directory]
+ [zip64 end of central directory record]
+ [zip64 end of central directory locator]
+ [end of central directory record]
+
+
+ A. Local file header:
+
+ local file header signature 4 bytes (0x04034b50)
+ version needed to extract 2 bytes
+ general purpose bit flag 2 bytes
+ compression method 2 bytes
+ last mod file time 2 bytes
+ last mod file date 2 bytes
+ crc-32 4 bytes
+ compressed size 4 bytes
+ uncompressed size 4 bytes
+ file name length 2 bytes
+ extra field length 2 bytes
+
+ file name (variable size)
+ extra field (variable size)
+
+ B. File data
+
+ Immediately following the local header for a file
+ is the compressed or stored data for the file.
+ The series of [local file header][file data][data
+ descriptor] repeats for each file in the .ZIP archive.
+
+ C. Data descriptor:
+
+ crc-32 4 bytes
+ compressed size 4 bytes
+ uncompressed size 4 bytes
+
+ This descriptor exists only if bit 3 of the general
+ purpose bit flag is set (see below). It is byte aligned
+ and immediately follows the last byte of compressed data.
+ This descriptor is used only when it was not possible to
+ seek in the output .ZIP file, e.g., when the output .ZIP file
+ was standard output or a non-seekable device. For ZIP64(tm) format
+ archives, the compressed and uncompressed sizes are 8 bytes each.
+
+ When compressing files, compressed and uncompressed sizes
+ should be stored in ZIP64 format (as 8 byte values) when a
+ files size exceeds 0xFFFFFFFF. However ZIP64 format may be
+ used regardless of the size of a file. When extracting, if
+ the zip64 extended information extra field is present for
+ the file the compressed and uncompressed sizes will be 8
+ byte values.
+
+ Although not originally assigned a signature, the value
+ 0x08074b50 has commonly been adopted as a signature value
+ for the data descriptor record. Implementers should be
+ aware that ZIP files may be encountered with or without this
+ signature marking data descriptors and should account for
+ either case when reading ZIP files to ensure compatibility.
+ When writing ZIP files, it is recommended to include the
+ signature value marking the data descriptor record. When
+ the signature is used, the fields currently defined for
+ the data descriptor record will immediately follow the
+ signature.
+
+ An extensible data descriptor will be released in a future
+ version of this APPNOTE. This new record is intended to
+ resolve conflicts with the use of this record going forward,
+ and to provide better support for streamed file processing.
+
+ When the Central Directory Encryption method is used, the data
+ descriptor record is not required, but may be used. If present,
+ and bit 3 of the general purpose bit field is set to indicate
+ its presence, the values in fields of the data descriptor
+ record should be set to binary zeros.
+
+ D. Archive decryption header:
+
+ The Archive Decryption Header is introduced in version 6.2
+ of the ZIP format specification. This record exists in support
+ of the Central Directory Encryption Feature implemented as part of
+ the Strong Encryption Specification as described in this document.
+ When the Central Directory Structure is encrypted, this decryption
+ header will precede the encrypted data segment. The encrypted
+ data segment will consist of the Archive extra data record (if
+ present) and the encrypted Central Directory Structure data.
+ The format of this data record is identical to the Decryption
+ header record preceding compressed file data. If the central
+ directory structure is encrypted, the location of the start of
+ this data record is determined using the Start of Central Directory
+ field in the Zip64 End of Central Directory record. Refer to the
+ section on the Strong Encryption Specification for information
+ on the fields used in the Archive Decryption Header record.
+
+
+ E. Archive extra data record:
+
+ archive extra data signature 4 bytes (0x08064b50)
+ extra field length 4 bytes
+ extra field data (variable size)
+
+ The Archive Extra Data Record is introduced in version 6.2
+ of the ZIP format specification. This record exists in support
+ of the Central Directory Encryption Feature implemented as part of
+ the Strong Encryption Specification as described in this document.
+ When present, this record immediately precedes the central
+ directory data structure. The size of this data record will be
+ included in the Size of the Central Directory field in the
+ End of Central Directory record. If the central directory structure
+ is compressed, but not encrypted, the location of the start of
+ this data record is determined using the Start of Central Directory
+ field in the Zip64 End of Central Directory record.
+
+
+ F. Central directory structure:
+
+ [file header 1]
+ .
+ .
+ .
+ [file header n]
+ [digital signature]
+
+ File header:
+
+ central file header signature 4 bytes (0x02014b50)
+ version made by 2 bytes
+ version needed to extract 2 bytes
+ general purpose bit flag 2 bytes
+ compression method 2 bytes
+ last mod file time 2 bytes
+ last mod file date 2 bytes
+ crc-32 4 bytes
+ compressed size 4 bytes
+ uncompressed size 4 bytes
+ file name length 2 bytes
+ extra field length 2 bytes
+ file comment length 2 bytes
+ disk number start 2 bytes
+ internal file attributes 2 bytes
+ external file attributes 4 bytes
+ relative offset of local header 4 bytes
+
+ file name (variable size)
+ extra field (variable size)
+ file comment (variable size)
+
+ Digital signature:
+
+ header signature 4 bytes (0x05054b50)
+ size of data 2 bytes
+ signature data (variable size)
+
+ With the introduction of the Central Directory Encryption
+ feature in version 6.2 of this specification, the Central
+ Directory Structure may be stored both compressed and encrypted.
+ Although not required, it is assumed when encrypting the
+ Central Directory Structure, that it will be compressed
+ for greater storage efficiency. Information on the
+ Central Directory Encryption feature can be found in the section
+ describing the Strong Encryption Specification. The Digital
+ Signature record will be neither compressed nor encrypted.
+
+ G. Zip64 end of central directory record
+
+ zip64 end of central dir
+ signature 4 bytes (0x06064b50)
+ size of zip64 end of central
+ directory record 8 bytes
+ version made by 2 bytes
+ version needed to extract 2 bytes
+ number of this disk 4 bytes
+ number of the disk with the
+ start of the central directory 4 bytes
+ total number of entries in the
+ central directory on this disk 8 bytes
+ total number of entries in the
+ central directory 8 bytes
+ size of the central directory 8 bytes
+ offset of start of central
+ directory with respect to
+ the starting disk number 8 bytes
+ zip64 extensible data sector (variable size)
+
+ The value stored into the "size of zip64 end of central
+ directory record" should be the size of the remaining
+ record and should not include the leading 12 bytes.
+
+ Size = SizeOfFixedFields + SizeOfVariableData - 12.
+
+ The above record structure defines Version 1 of the
+ zip64 end of central directory record. Version 1 was
+ implemented in versions of this specification preceding
+ 6.2 in support of the ZIP64 large file feature. The
+ introduction of the Central Directory Encryption feature
+ implemented in version 6.2 as part of the Strong Encryption
+ Specification defines Version 2 of this record structure.
+ Refer to the section describing the Strong Encryption
+ Specification for details on the version 2 format for
+ this record.
+
+ Special purpose data may reside in the zip64 extensible data
+ sector field following either a V1 or V2 version of this
+ record. To ensure identification of this special purpose data
+ it must include an identifying header block consisting of the
+ following:
+
+ Header ID - 2 bytes
+ Data Size - 4 bytes
+
+ The Header ID field indicates the type of data that is in the
+ data block that follows.
+
+ Data Size identifies the number of bytes that follow for this
+ data block type.
+
+ Multiple special purpose data blocks may be present, but each
+ must be preceded by a Header ID and Data Size field. Current
+ mappings of Header ID values supported in this field are as
+ defined in APPENDIX C.
+
+ H. Zip64 end of central directory locator
+
+ zip64 end of central dir locator
+ signature 4 bytes (0x07064b50)
+ number of the disk with the
+ start of the zip64 end of
+ central directory 4 bytes
+ relative offset of the zip64
+ end of central directory record 8 bytes
+ total number of disks 4 bytes
+
+ I. End of central directory record:
+
+ end of central dir signature 4 bytes (0x06054b50)
+ number of this disk 2 bytes
+ number of the disk with the
+ start of the central directory 2 bytes
+ total number of entries in the
+ central directory on this disk 2 bytes
+ total number of entries in
+ the central directory 2 bytes
+ size of the central directory 4 bytes
+ offset of start of central
+ directory with respect to
+ the starting disk number 4 bytes
+ .ZIP file comment length 2 bytes
+ .ZIP file comment (variable size)
+
+ J. Explanation of fields:
+
+ version made by (2 bytes)
+
+ The upper byte indicates the compatibility of the file
+ attribute information. If the external file attributes
+ are compatible with MS-DOS and can be read by PKZIP for
+ DOS version 2.04g then this value will be zero. If these
+ attributes are not compatible, then this value will
+ identify the host system on which the attributes are
+ compatible. Software can use this information to determine
+ the line record format for text files etc. The current
+ mappings are:
+
+ 0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)
+ 1 - Amiga 2 - OpenVMS
+ 3 - UNIX 4 - VM/CMS
+ 5 - Atari ST 6 - OS/2 H.P.F.S.
+ 7 - Macintosh 8 - Z-System
+ 9 - CP/M 10 - Windows NTFS
+ 11 - MVS (OS/390 - Z/OS) 12 - VSE
+ 13 - Acorn Risc 14 - VFAT
+ 15 - alternate MVS 16 - BeOS
+ 17 - Tandem 18 - OS/400
+ 19 - OS/X (Darwin) 20 thru 255 - unused
+
+ The lower byte indicates the ZIP specification version
+ (the version of this document) supported by the software
+ used to encode the file. The value/10 indicates the major
+ version number, and the value mod 10 is the minor version
+ number.
+
+ version needed to extract (2 bytes)
+
+ The minimum supported ZIP specification version needed to
+ extract the file, mapped as above. This value is based on
+ the specific format features a ZIP program must support to
+ be able to extract the file. If multiple features are
+ applied to a file, the minimum version should be set to the
+ feature having the highest value. New features or feature
+ changes affecting the published format specification will be
+ implemented using higher version numbers than the last
+ published value to avoid conflict.
+
+ Current minimum feature versions are as defined below:
+
+ 1.0 - Default value
+ 1.1 - File is a volume label
+ 2.0 - File is a folder (directory)
+ 2.0 - File is compressed using Deflate compression
+ 2.0 - File is encrypted using traditional PKWARE encryption
+ 2.1 - File is compressed using Deflate64(tm)
+ 2.5 - File is compressed using PKWARE DCL Implode
+ 2.7 - File is a patch data set
+ 4.5 - File uses ZIP64 format extensions
+ 4.6 - File is compressed using BZIP2 compression*
+ 5.0 - File is encrypted using DES
+ 5.0 - File is encrypted using 3DES
+ 5.0 - File is encrypted using original RC2 encryption
+ 5.0 - File is encrypted using RC4 encryption
+ 5.1 - File is encrypted using AES encryption
+ 5.1 - File is encrypted using corrected RC2 encryption**
+ 5.2 - File is encrypted using corrected RC2-64 encryption**
+ 6.1 - File is encrypted using non-OAEP key wrapping***
+ 6.2 - Central directory encryption
+ 6.3 - File is compressed using LZMA
+ 6.3 - File is compressed using PPMd+
+ 6.3 - File is encrypted using Blowfish
+ 6.3 - File is encrypted using Twofish
+
+
+ * Early 7.x (pre-7.2) versions of PKZIP incorrectly set the
+ version needed to extract for BZIP2 compression to be 50
+ when it should have been 46.
+
+ ** Refer to the section on Strong Encryption Specification
+ for additional information regarding RC2 corrections.
+
+ *** Certificate encryption using non-OAEP key wrapping is the
+ intended mode of operation for all versions beginning with 6.1.
+ Support for OAEP key wrapping should only be used for
+ backward compatibility when sending ZIP files to be opened by
+ versions of PKZIP older than 6.1 (5.0 or 6.0).
+
+ + Files compressed using PPMd should set the version
+ needed to extract field to 6.3, however, not all ZIP
+ programs enforce this and may be unable to decompress
+ data files compressed using PPMd if this value is set.
+
+ When using ZIP64 extensions, the corresponding value in the
+ zip64 end of central directory record should also be set.
+ This field should be set appropriately to indicate whether
+ Version 1 or Version 2 format is in use.
+
+ general purpose bit flag: (2 bytes)
+
+ Bit 0: If set, indicates that the file is encrypted.
+
+ (For Method 6 - Imploding)
+ Bit 1: If the compression method used was type 6,
+ Imploding, then this bit, if set, indicates
+ an 8K sliding dictionary was used. If clear,
+ then a 4K sliding dictionary was used.
+ Bit 2: If the compression method used was type 6,
+ Imploding, then this bit, if set, indicates
+ 3 Shannon-Fano trees were used to encode the
+ sliding dictionary output. If clear, then 2
+ Shannon-Fano trees were used.
+
+ (For Methods 8 and 9 - Deflating)
+ Bit 2 Bit 1
+ 0 0 Normal (-en) compression option was used.
+ 0 1 Maximum (-exx/-ex) compression option was used.
+ 1 0 Fast (-ef) compression option was used.
+ 1 1 Super Fast (-es) compression option was used.
+
+ (For Method 14 - LZMA)
+ Bit 1: If the compression method used was type 14,
+ LZMA, then this bit, if set, indicates
+ an end-of-stream (EOS) marker is used to
+ mark the end of the compressed data stream.
+ If clear, then an EOS marker is not present
+ and the compressed data size must be known
+ to extract.
+
+ Note: Bits 1 and 2 are undefined if the compression
+ method is any other.
+
+ Bit 3: If this bit is set, the fields crc-32, compressed
+ size and uncompressed size are set to zero in the
+ local header. The correct values are put in the
+ data descriptor immediately following the compressed
+ data. (Note: PKZIP version 2.04g for DOS only
+ recognizes this bit for method 8 compression, newer
+ versions of PKZIP recognize this bit for any
+ compression method.)
+
+ Bit 4: Reserved for use with method 8, for enhanced
+ deflating.
+
+ Bit 5: If this bit is set, this indicates that the file is
+ compressed patched data. (Note: Requires PKZIP
+ version 2.70 or greater)
+
+ Bit 6: Strong encryption. If this bit is set, you should
+ set the version needed to extract value to at least
+ 50 and you must also set bit 0. If AES encryption
+ is used, the version needed to extract value must
+ be at least 51.
+
+ Bit 7: Currently unused.
+
+ Bit 8: Currently unused.
+
+ Bit 9: Currently unused.
+
+ Bit 10: Currently unused.
+
+ Bit 11: Language encoding flag (EFS). If this bit is set,
+ the filename and comment fields for this file
+ must be encoded using UTF-8. (see APPENDIX D)
+
+ Bit 12: Reserved by PKWARE for enhanced compression.
+
+ Bit 13: Used when encrypting the Central Directory to indicate
+ selected data values in the Local Header are masked to
+ hide their actual values. See the section describing
+ the Strong Encryption Specification for details.
+
+ Bit 14: Reserved by PKWARE.
+
+ Bit 15: Reserved by PKWARE.
+
+ compression method: (2 bytes)
+
+ (see accompanying documentation for algorithm
+ descriptions)
+
+ 0 - The file is stored (no compression)
+ 1 - The file is Shrunk
+ 2 - The file is Reduced with compression factor 1
+ 3 - The file is Reduced with compression factor 2
+ 4 - The file is Reduced with compression factor 3
+ 5 - The file is Reduced with compression factor 4
+ 6 - The file is Imploded
+ 7 - Reserved for Tokenizing compression algorithm
+ 8 - The file is Deflated
+ 9 - Enhanced Deflating using Deflate64(tm)
+ 10 - PKWARE Data Compression Library Imploding (old IBM TERSE)
+ 11 - Reserved by PKWARE
+ 12 - File is compressed using BZIP2 algorithm
+ 13 - Reserved by PKWARE
+ 14 - LZMA (EFS)
+ 15 - Reserved by PKWARE
+ 16 - Reserved by PKWARE
+ 17 - Reserved by PKWARE
+ 18 - File is compressed using IBM TERSE (new)
+ 19 - IBM LZ77 z Architecture (PFS)
+ 97 - WavPack compressed data
+ 98 - PPMd version I, Rev 1
+
+ date and time fields: (2 bytes each)
+
+ The date and time are encoded in standard MS-DOS format.
+ If input came from standard input, the date and time are
+ those at which compression was started for this data.
+ If encrypting the central directory and general purpose bit
+ flag 13 is set indicating masking, the value stored in the
+ Local Header will be zero.
+
+ CRC-32: (4 bytes)
+
+ The CRC-32 algorithm was generously contributed by
+ David Schwaderer and can be found in his excellent
+ book "C Programmers Guide to NetBIOS" published by
+ Howard W. Sams & Co. Inc. The 'magic number' for
+ the CRC is 0xdebb20e3. The proper CRC pre and post
+ conditioning is used, meaning that the CRC register
+ is pre-conditioned with all ones (a starting value
+ of 0xffffffff) and the value is post-conditioned by
+ taking the one's complement of the CRC residual.
+ If bit 3 of the general purpose flag is set, this
+ field is set to zero in the local header and the correct
+ value is put in the data descriptor and in the central
+ directory. When encrypting the central directory, if the
+ local header is not in ZIP64 format and general purpose
+ bit flag 13 is set indicating masking, the value stored
+ in the Local Header will be zero.
+
+ compressed size: (4 bytes)
+ uncompressed size: (4 bytes)
+
+ The size of the file compressed and uncompressed,
+ respectively. When a decryption header is present it will
+ be placed in front of the file data and the value of the
+ compressed file size will include the bytes of the decryption
+ header. If bit 3 of the general purpose bit flag is set,
+ these fields are set to zero in the local header and the
+ correct values are put in the data descriptor and
+ in the central directory. If an archive is in ZIP64 format
+ and the value in this field is 0xFFFFFFFF, the size will be
+ in the corresponding 8 byte ZIP64 extended information
+ extra field. When encrypting the central directory, if the
+ local header is not in ZIP64 format and general purpose bit
+ flag 13 is set indicating masking, the value stored for the
+ uncompressed size in the Local Header will be zero.
+
+ file name length: (2 bytes)
+ extra field length: (2 bytes)
+ file comment length: (2 bytes)
+
+ The length of the file name, extra field, and comment
+ fields respectively. The combined length of any
+ directory record and these three fields should not
+ generally exceed 65,535 bytes. If input came from standard
+ input, the file name length is set to zero.
+
+ disk number start: (2 bytes)
+
+ The number of the disk on which this file begins. If an
+ archive is in ZIP64 format and the value in this field is
+ 0xFFFF, the size will be in the corresponding 4 byte zip64
+ extended information extra field.
+
+ internal file attributes: (2 bytes)
+
+ Bits 1 and 2 are reserved for use by PKWARE.
+
+ The lowest bit of this field indicates, if set, that
+ the file is apparently an ASCII or text file. If not
+ set, that the file apparently contains binary data.
+ The remaining bits are unused in version 1.0.
+
+ The 0x0002 bit of this field indicates, if set, that a
+ 4 byte variable record length control field precedes each
+ logical record indicating the length of the record. The
+ record length control field is stored in little-endian byte
+ order. This flag is independent of text control characters,
+ and if used in conjunction with text data, includes any
+ control characters in the total length of the record. This
+ value is provided for mainframe data transfer support.
+
+ external file attributes: (4 bytes)
+
+ The mapping of the external attributes is
+ host-system dependent (see 'version made by'). For
+ MS-DOS, the low order byte is the MS-DOS directory
+ attribute byte. If input came from standard input, this
+ field is set to zero.
+
+ relative offset of local header: (4 bytes)
+
+ This is the offset from the start of the first disk on
+ which this file appears, to where the local header should
+ be found. If an archive is in ZIP64 format and the value
+ in this field is 0xFFFFFFFF, the size will be in the
+ corresponding 8 byte zip64 extended information extra field.
+
+ file name: (Variable)
+
+ The name of the file, with optional relative path.
+ The path stored should not contain a drive or
+ device letter, or a leading slash. All slashes
+ should be forward slashes '/' as opposed to
+ backwards slashes '\' for compatibility with Amiga
+ and UNIX file systems etc. If input came from standard
+ input, there is no file name field. If encrypting
+ the central directory and general purpose bit flag 13 is set
+ indicating masking, the file name stored in the Local Header
+ will not be the actual file name. A masking value consisting
+ of a unique hexadecimal value will be stored. This value will
+ be sequentially incremented for each file in the archive. See
+ the section on the Strong Encryption Specification for details
+ on retrieving the encrypted file name.
+
+ extra field: (Variable)
+
+ This is for expansion. If additional information
+ needs to be stored for special needs or for specific
+ platforms, it should be stored here. Earlier versions
+ of the software can then safely skip this file, and
+ find the next file or header. This field will be 0
+ length in version 1.0.
+
+ In order to allow different programs and different types
+ of information to be stored in the 'extra' field in .ZIP
+ files, the following structure should be used for all
+ programs storing data in this field:
+
+ header1+data1 + header2+data2 . . .
+
+ Each header should consist of:
+
+ Header ID - 2 bytes
+ Data Size - 2 bytes
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ The Header ID field indicates the type of data that is in
+ the following data block.
+
+ Header ID's of 0 thru 31 are reserved for use by PKWARE.
+ The remaining ID's can be used by third party vendors for
+ proprietary usage.
+
+ The current Header ID mappings defined by PKWARE are:
+
+ 0x0001 Zip64 extended information extra field
+ 0x0007 AV Info
+ 0x0008 Reserved for extended language encoding data (PFS)
+ (see APPENDIX D)
+ 0x0009 OS/2
+ 0x000a NTFS
+ 0x000c OpenVMS
+ 0x000d UNIX
+ 0x000e Reserved for file stream and fork descriptors
+ 0x000f Patch Descriptor
+ 0x0014 PKCS#7 Store for X.509 Certificates
+ 0x0015 X.509 Certificate ID and Signature for
+ individual file
+ 0x0016 X.509 Certificate ID for Central Directory
+ 0x0017 Strong Encryption Header
+ 0x0018 Record Management Controls
+ 0x0019 PKCS#7 Encryption Recipient Certificate List
+ 0x0065 IBM S/390 (Z390), AS/400 (I400) attributes
+ - uncompressed
+ 0x0066 Reserved for IBM S/390 (Z390), AS/400 (I400)
+ attributes - compressed
+ 0x4690 POSZIP 4690 (reserved)
+
+ Third party mappings commonly used are:
+
+
+ 0x07c8 Macintosh
+ 0x2605 ZipIt Macintosh
+ 0x2705 ZipIt Macintosh 1.3.5+
+ 0x2805 ZipIt Macintosh 1.3.5+
+ 0x334d Info-ZIP Macintosh
+ 0x4341 Acorn/SparkFS
+ 0x4453 Windows NT security descriptor (binary ACL)
+ 0x4704 VM/CMS
+ 0x470f MVS
+ 0x4b46 FWKCS MD5 (see below)
+ 0x4c41 OS/2 access control list (text ACL)
+ 0x4d49 Info-ZIP OpenVMS
+ 0x4f4c Xceed original location extra field
+ 0x5356 AOS/VS (ACL)
+ 0x5455 extended timestamp
+ 0x554e Xceed unicode extra field
+ 0x5855 Info-ZIP UNIX (original, also OS/2, NT, etc)
+ 0x6375 Info-ZIP Unicode Comment Extra Field
+ 0x6542 BeOS/BeBox
+ 0x7075 Info-ZIP Unicode Path Extra Field
+ 0x756e ASi UNIX
+ 0x7855 Info-ZIP UNIX (new)
+ 0xa220 Microsoft Open Packaging Growth Hint
+ 0xfd4a SMS/QDOS
+
+ Detailed descriptions of Extra Fields defined by third
+ party mappings will be documented as information on
+ these data structures is made available to PKWARE.
+ PKWARE does not guarantee the accuracy of any published
+ third party data.
+
+ The Data Size field indicates the size of the following
+ data block. Programs can use this value to skip to the
+ next header block, passing over any data blocks that are
+ not of interest.
+
+ Note: As stated above, the size of the entire .ZIP file
+ header, including the file name, comment, and extra
+ field should not exceed 64K in size.
+
+ In case two different programs should appropriate the same
+ Header ID value, it is strongly recommended that each
+ program place a unique signature of at least two bytes in
+ size (and preferably 4 bytes or bigger) at the start of
+ each data area. Every program should verify that its
+ unique signature is present, in addition to the Header ID
+ value being correct, before assuming that it is a block of
+ known type.
+
+ -Zip64 Extended Information Extra Field (0x0001):
+
+ The following is the layout of the zip64 extended
+ information "extra" block. If one of the size or
+ offset fields in the Local or Central directory
+ record is too small to hold the required data,
+ a Zip64 extended information record is created.
+ The order of the fields in the zip64 extended
+ information record is fixed, but the fields will
+ only appear if the corresponding Local or Central
+ directory record field is set to 0xFFFF or 0xFFFFFFFF.
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (ZIP64) 0x0001 2 bytes Tag for this "extra" block type
+ Size 2 bytes Size of this "extra" block
+ Original
+ Size 8 bytes Original uncompressed file size
+ Compressed
+ Size 8 bytes Size of compressed data
+ Relative Header
+ Offset 8 bytes Offset of local header record
+ Disk Start
+ Number 4 bytes Number of the disk on which
+ this file starts
+
+ This entry in the Local header must include BOTH original
+ and compressed file size fields. If encrypting the
+ central directory and bit 13 of the general purpose bit
+ flag is set indicating masking, the value stored in the
+ Local Header for the original file size will be zero.
+
+
+ -OS/2 Extra Field (0x0009):
+
+ The following is the layout of the OS/2 attributes "extra"
+ block. (Last Revision 09/05/95)
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (OS/2) 0x0009 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size for the following data block
+ BSize 4 bytes Uncompressed Block Size
+ CType 2 bytes Compression type
+ EACRC 4 bytes CRC value for uncompress block
+ (var) variable Compressed block
+
+ The OS/2 extended attribute structure (FEA2LIST) is
+ compressed and then stored in it's entirety within this
+ structure. There will only ever be one "block" of data in
+ VarFields[].
+
+ -NTFS Extra Field (0x000a):
+
+ The following is the layout of the NTFS attributes
+ "extra" block. (Note: At this time the Mtime, Atime
+ and Ctime values may be used on any WIN32 system.)
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (NTFS) 0x000a 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of the total "extra" block
+ Reserved 4 bytes Reserved for future use
+ Tag1 2 bytes NTFS attribute tag value #1
+ Size1 2 bytes Size of attribute #1, in bytes
+ (var.) Size1 Attribute #1 data
+ .
+ .
+ .
+ TagN 2 bytes NTFS attribute tag value #N
+ SizeN 2 bytes Size of attribute #N, in bytes
+ (var.) SizeN Attribute #N data
+
+ For NTFS, values for Tag1 through TagN are as follows:
+ (currently only one set of attributes is defined for NTFS)
+
+ Tag Size Description
+ ----- ---- -----------
+ 0x0001 2 bytes Tag for attribute #1
+ Size1 2 bytes Size of attribute #1, in bytes
+ Mtime 8 bytes File last modification time
+ Atime 8 bytes File last access time
+ Ctime 8 bytes File creation time
+
+ -OpenVMS Extra Field (0x000c):
+
+ The following is the layout of the OpenVMS attributes
+ "extra" block.
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (VMS) 0x000c 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of the total "extra" block
+ CRC 4 bytes 32-bit CRC for remainder of the block
+ Tag1 2 bytes OpenVMS attribute tag value #1
+ Size1 2 bytes Size of attribute #1, in bytes
+ (var.) Size1 Attribute #1 data
+ .
+ .
+ .
+ TagN 2 bytes OpenVMS attribute tag value #N
+ SizeN 2 bytes Size of attribute #N, in bytes
+ (var.) SizeN Attribute #N data
+
+ Rules:
+
+ 1. There will be one or more of attributes present, which
+ will each be preceded by the above TagX & SizeX values.
+ These values are identical to the ATR$C_XXXX and
+ ATR$S_XXXX constants which are defined in ATR.H under
+ OpenVMS C. Neither of these values will ever be zero.
+
+ 2. No word alignment or padding is performed.
+
+ 3. A well-behaved PKZIP/OpenVMS program should never produce
+ more than one sub-block with the same TagX value. Also,
+ there will never be more than one "extra" block of type
+ 0x000c in a particular directory record.
+
+ -UNIX Extra Field (0x000d):
+
+ The following is the layout of the UNIX "extra" block.
+ Note: all fields are stored in Intel low-byte/high-byte
+ order.
+
+ Value Size Description
+ ----- ---- -----------
+ (UNIX) 0x000d 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size for the following data block
+ Atime 4 bytes File last access time
+ Mtime 4 bytes File last modification time
+ Uid 2 bytes File user ID
+ Gid 2 bytes File group ID
+ (var) variable Variable length data field
+
+ The variable length data field will contain file type
+ specific data. Currently the only values allowed are
+ the original "linked to" file names for hard or symbolic
+ links, and the major and minor device node numbers for
+ character and block device nodes. Since device nodes
+ cannot be either symbolic or hard links, only one set of
+ variable length data is stored. Link files will have the
+ name of the original file stored. This name is NOT NULL
+ terminated. Its size can be determined by checking TSize -
+ 12. Device entries will have eight bytes stored as two 4
+ byte entries (in little endian format). The first entry
+ will be the major device number, and the second the minor
+ device number.
+
+ -PATCH Descriptor Extra Field (0x000f):
+
+ The following is the layout of the Patch Descriptor "extra"
+ block.
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (Patch) 0x000f 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of the total "extra" block
+ Version 2 bytes Version of the descriptor
+ Flags 4 bytes Actions and reactions (see below)
+ OldSize 4 bytes Size of the file about to be patched
+ OldCRC 4 bytes 32-bit CRC of the file to be patched
+ NewSize 4 bytes Size of the resulting file
+ NewCRC 4 bytes 32-bit CRC of the resulting file
+
+ Actions and reactions
+
+ Bits Description
+ ---- ----------------
+ 0 Use for auto detection
+ 1 Treat as a self-patch
+ 2-3 RESERVED
+ 4-5 Action (see below)
+ 6-7 RESERVED
+ 8-9 Reaction (see below) to absent file
+ 10-11 Reaction (see below) to newer file
+ 12-13 Reaction (see below) to unknown file
+ 14-15 RESERVED
+ 16-31 RESERVED
+
+ Actions
+
+ Action Value
+ ------ -----
+ none 0
+ add 1
+ delete 2
+ patch 3
+
+ Reactions
+
+ Reaction Value
+ -------- -----
+ ask 0
+ skip 1
+ ignore 2
+ fail 3
+
+ Patch support is provided by PKPatchMaker(tm) technology and is
+ covered under U.S. Patents and Patents Pending. The use or
+ implementation in a product of certain technological aspects set
+ forth in the current APPNOTE, including those with regard to
+ strong encryption, patching, or extended tape operations requires
+ a license from PKWARE. Please contact PKWARE with regard to
+ acquiring a license.
+
+ -PKCS#7 Store for X.509 Certificates (0x0014):
+
+ This field contains information about each of the certificates
+ files may be signed with. When the Central Directory Encryption
+ feature is enabled for a ZIP file, this record will appear in
+ the Archive Extra Data Record, otherwise it will appear in the
+ first central directory record and will be ignored in any
+ other record.
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (Store) 0x0014 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of the store data
+ TData TSize Data about the store
+
+
+ -X.509 Certificate ID and Signature for individual file (0x0015):
+
+ This field contains the information about which certificate in
+ the PKCS#7 store was used to sign a particular file. It also
+ contains the signature data. This field can appear multiple
+ times, but can only appear once per certificate.
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (CID) 0x0015 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of data that follows
+ TData TSize Signature Data
+
+ -X.509 Certificate ID and Signature for central directory (0x0016):
+
+ This field contains the information about which certificate in
+ the PKCS#7 store was used to sign the central directory structure.
+ When the Central Directory Encryption feature is enabled for a
+ ZIP file, this record will appear in the Archive Extra Data Record,
+ otherwise it will appear in the first central directory record.
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (CDID) 0x0016 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of data that follows
+ TData TSize Data
+
+ -Strong Encryption Header (0x0017):
+
+ Value Size Description
+ ----- ---- -----------
+ 0x0017 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of data that follows
+ Format 2 bytes Format definition for this record
+ AlgID 2 bytes Encryption algorithm identifier
+ Bitlen 2 bytes Bit length of encryption key
+ Flags 2 bytes Processing flags
+ CertData TSize-8 Certificate decryption extra field data
+ (refer to the explanation for CertData
+ in the section describing the
+ Certificate Processing Method under
+ the Strong Encryption Specification)
+
+
+ -Record Management Controls (0x0018):
+
+ Value Size Description
+ ----- ---- -----------
+(Rec-CTL) 0x0018 2 bytes Tag for this "extra" block type
+ CSize 2 bytes Size of total extra block data
+ Tag1 2 bytes Record control attribute 1
+ Size1 2 bytes Size of attribute 1, in bytes
+ Data1 Size1 Attribute 1 data
+ .
+ .
+ .
+ TagN 2 bytes Record control attribute N
+ SizeN 2 bytes Size of attribute N, in bytes
+ DataN SizeN Attribute N data
+
+
+ -PKCS#7 Encryption Recipient Certificate List (0x0019):
+
+ This field contains information about each of the certificates
+ used in encryption processing and it can be used to identify who is
+ allowed to decrypt encrypted files. This field should only appear
+ in the archive extra data record. This field is not required and
+ serves only to aide archive modifications by preserving public
+ encryption key data. Individual security requirements may dictate
+ that this data be omitted to deter information exposure.
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (CStore) 0x0019 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of the store data
+ TData TSize Data about the store
+
+ TData:
+
+ Value Size Description
+ ----- ---- -----------
+ Version 2 bytes Format version number - must 0x0001 at this time
+ CStore (var) PKCS#7 data blob
+
+
+ -MVS Extra Field (0x0065):
+
+ The following is the layout of the MVS "extra" block.
+ Note: Some fields are stored in Big Endian format.
+ All text is in EBCDIC format unless otherwise specified.
+
+ Value Size Description
+ ----- ---- -----------
+ (MVS) 0x0065 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size for the following data block
+ ID 4 bytes EBCDIC "Z390" 0xE9F3F9F0 or
+ "T4MV" for TargetFour
+ (var) TSize-4 Attribute data (see APPENDIX B)
+
+
+ -OS/400 Extra Field (0x0065):
+
+ The following is the layout of the OS/400 "extra" block.
+ Note: Some fields are stored in Big Endian format.
+ All text is in EBCDIC format unless otherwise specified.
+
+ Value Size Description
+ ----- ---- -----------
+ (OS400) 0x0065 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size for the following data block
+ ID 4 bytes EBCDIC "I400" 0xC9F4F0F0 or
+ "T4MV" for TargetFour
+ (var) TSize-4 Attribute data (see APPENDIX A)
+
+
+ Third-party Mappings:
+
+ -ZipIt Macintosh Extra Field (long) (0x2605):
+
+ The following is the layout of the ZipIt extra block
+ for Macintosh. The local-header and central-header versions
+ are identical. This block must be present if the file is
+ stored MacBinary-encoded and it should not be used if the file
+ is not stored MacBinary-encoded.
+
+ Value Size Description
+ ----- ---- -----------
+ (Mac2) 0x2605 Short tag for this extra block type
+ TSize Short total data size for this block
+ "ZPIT" beLong extra-field signature
+ FnLen Byte length of FileName
+ FileName variable full Macintosh filename
+ FileType Byte[4] four-byte Mac file type string
+ Creator Byte[4] four-byte Mac creator string
+
+
+ -ZipIt Macintosh Extra Field (short, for files) (0x2705):
+
+ The following is the layout of a shortened variant of the
+ ZipIt extra block for Macintosh (without "full name" entry).
+ This variant is used by ZipIt 1.3.5 and newer for entries of
+ files (not directories) that do not have a MacBinary encoded
+ file. The local-header and central-header versions are identical.
+
+ Value Size Description
+ ----- ---- -----------
+ (Mac2b) 0x2705 Short tag for this extra block type
+ TSize Short total data size for this block (12)
+ "ZPIT" beLong extra-field signature
+ FileType Byte[4] four-byte Mac file type string
+ Creator Byte[4] four-byte Mac creator string
+ fdFlags beShort attributes from FInfo.frFlags,
+ may be omitted
+ 0x0000 beShort reserved, may be omitted
+
+
+ -ZipIt Macintosh Extra Field (short, for directories) (0x2805):
+
+ The following is the layout of a shortened variant of the
+ ZipIt extra block for Macintosh used only for directory
+ entries. This variant is used by ZipIt 1.3.5 and newer to
+ save some optional Mac-specific information about directories.
+ The local-header and central-header versions are identical.
+
+ Value Size Description
+ ----- ---- -----------
+ (Mac2c) 0x2805 Short tag for this extra block type
+ TSize Short total data size for this block (12)
+ "ZPIT" beLong extra-field signature
+ frFlags beShort attributes from DInfo.frFlags, may
+ be omitted
+ View beShort ZipIt view flag, may be omitted
+
+
+ The View field specifies ZipIt-internal settings as follows:
+
+ Bits of the Flags:
+ bit 0 if set, the folder is shown expanded (open)
+ when the archive contents are viewed in ZipIt.
+ bits 1-15 reserved, zero;
+
+
+ -FWKCS MD5 Extra Field (0x4b46):
+
+ The FWKCS Contents_Signature System, used in
+ automatically identifying files independent of file name,
+ optionally adds and uses an extra field to support the
+ rapid creation of an enhanced contents_signature:
+
+ Header ID = 0x4b46
+ Data Size = 0x0013
+ Preface = 'M','D','5'
+ followed by 16 bytes containing the uncompressed file's
+ 128_bit MD5 hash(1), low byte first.
+
+ When FWKCS revises a .ZIP file central directory to add
+ this extra field for a file, it also replaces the
+ central directory entry for that file's uncompressed
+ file length with a measured value.
+
+ FWKCS provides an option to strip this extra field, if
+ present, from a .ZIP file central directory. In adding
+ this extra field, FWKCS preserves .ZIP file Authenticity
+ Verification; if stripping this extra field, FWKCS
+ preserves all versions of AV through PKZIP version 2.04g.
+
+ FWKCS, and FWKCS Contents_Signature System, are
+ trademarks of Frederick W. Kantor.
+
+ (1) R. Rivest, RFC1321.TXT, MIT Laboratory for Computer
+ Science and RSA Data Security, Inc., April 1992.
+ ll.76-77: "The MD5 algorithm is being placed in the
+ public domain for review and possible adoption as a
+ standard."
+
+
+ -Info-ZIP Unicode Comment Extra Field (0x6375):
+
+ Stores the UTF-8 version of the file comment as stored in the
+ central directory header. (Last Revision 20070912)
+
+ Value Size Description
+ ----- ---- -----------
+ (UCom) 0x6375 Short tag for this extra block type ("uc")
+ TSize Short total data size for this block
+ Version 1 byte version of this extra field, currently 1
+ ComCRC32 4 bytes Comment Field CRC32 Checksum
+ UnicodeCom Variable UTF-8 version of the entry comment
+
+ Currently Version is set to the number 1. If there is a need
+ to change this field, the version will be incremented. Changes
+ may not be backward compatible so this extra field should not be
+ used if the version is not recognized.
+
+ The ComCRC32 is the standard zip CRC32 checksum of the File Comment
+ field in the central directory header. This is used to verify that
+ the comment field has not changed since the Unicode Comment extra field
+ was created. This can happen if a utility changes the File Comment
+ field but does not update the UTF-8 Comment extra field. If the CRC
+ check fails, this Unicode Comment extra field should be ignored and
+ the File Comment field in the header should be used instead.
+
+ The UnicodeCom field is the UTF-8 version of the File Comment field
+ in the header. As UnicodeCom is defined to be UTF-8, no UTF-8 byte
+ order mark (BOM) is used. The length of this field is determined by
+ subtracting the size of the previous fields from TSize. If both the
+ File Name and Comment fields are UTF-8, the new General Purpose Bit
+ Flag, bit 11 (Language encoding flag (EFS)), can be used to indicate
+ both the header File Name and Comment fields are UTF-8 and, in this
+ case, the Unicode Path and Unicode Comment extra fields are not
+ needed and should not be created. Note that, for backward
+ compatibility, bit 11 should only be used if the native character set
+ of the paths and comments being zipped up are already in UTF-8. It is
+ expected that the same file comment storage method, either general
+ purpose bit 11 or extra fields, be used in both the Local and Central
+ Directory Header for a file.
+
+
+ -Info-ZIP Unicode Path Extra Field (0x7075):
+
+ Stores the UTF-8 version of the file name field as stored in the
+ local header and central directory header. (Last Revision 20070912)
+
+ Value Size Description
+ ----- ---- -----------
+ (UPath) 0x7075 Short tag for this extra block type ("up")
+ TSize Short total data size for this block
+ Version 1 byte version of this extra field, currently 1
+ NameCRC32 4 bytes File Name Field CRC32 Checksum
+ UnicodeName Variable UTF-8 version of the entry File Name
+
+ Currently Version is set to the number 1. If there is a need
+ to change this field, the version will be incremented. Changes
+ may not be backward compatible so this extra field should not be
+ used if the version is not recognized.
+
+ The NameCRC32 is the standard zip CRC32 checksum of the File Name
+ field in the header. This is used to verify that the header
+ File Name field has not changed since the Unicode Path extra field
+ was created. This can happen if a utility renames the File Name but
+ does not update the UTF-8 path extra field. If the CRC check fails,
+ this UTF-8 Path Extra Field should be ignored and the File Name field
+ in the header should be used instead.
+
+ The UnicodeName is the UTF-8 version of the contents of the File Name
+ field in the header. As UnicodeName is defined to be UTF-8, no UTF-8
+ byte order mark (BOM) is used. The length of this field is determined
+ by subtracting the size of the previous fields from TSize. If both
+ the File Name and Comment fields are UTF-8, the new General Purpose
+ Bit Flag, bit 11 (Language encoding flag (EFS)), can be used to
+ indicate that both the header File Name and Comment fields are UTF-8
+ and, in this case, the Unicode Path and Unicode Comment extra fields
+ are not needed and should not be created. Note that, for backward
+ compatibility, bit 11 should only be used if the native character set
+ of the paths and comments being zipped up are already in UTF-8. It is
+ expected that the same file name storage method, either general
+ purpose bit 11 or extra fields, be used in both the Local and Central
+ Directory Header for a file.
+
+
+ -Microsoft Open Packaging Growth Hint (0xa220):
+
+ Value Size Description
+ ----- ---- -----------
+ 0xa220 Short tag for this extra block type
+ TSize Short size of Sig + PadVal + Padding
+ Sig Short verification signature (A028)
+ PadVal Short Initial padding value
+ Padding variable filled with NULL characters
+
+
+ file comment: (Variable)
+
+ The comment for this file.
+
+ number of this disk: (2 bytes)
+
+ The number of this disk, which contains central
+ directory end record. If an archive is in ZIP64 format
+ and the value in this field is 0xFFFF, the size will
+ be in the corresponding 4 byte zip64 end of central
+ directory field.
+
+
+ number of the disk with the start of the central
+ directory: (2 bytes)
+
+ The number of the disk on which the central
+ directory starts. If an archive is in ZIP64 format
+ and the value in this field is 0xFFFF, the size will
+ be in the corresponding 4 byte zip64 end of central
+ directory field.
+
+ total number of entries in the central dir on
+ this disk: (2 bytes)
+
+ The number of central directory entries on this disk.
+ If an archive is in ZIP64 format and the value in
+ this field is 0xFFFF, the size will be in the
+ corresponding 8 byte zip64 end of central
+ directory field.
+
+ total number of entries in the central dir: (2 bytes)
+
+ The total number of files in the .ZIP file. If an
+ archive is in ZIP64 format and the value in this field
+ is 0xFFFF, the size will be in the corresponding 8 byte
+ zip64 end of central directory field.
+
+ size of the central directory: (4 bytes)
+
+ The size (in bytes) of the entire central directory.
+ If an archive is in ZIP64 format and the value in
+ this field is 0xFFFFFFFF, the size will be in the
+ corresponding 8 byte zip64 end of central
+ directory field.
+
+ offset of start of central directory with respect to
+ the starting disk number: (4 bytes)
+
+ Offset of the start of the central directory on the
+ disk on which the central directory starts. If an
+ archive is in ZIP64 format and the value in this
+ field is 0xFFFFFFFF, the size will be in the
+ corresponding 8 byte zip64 end of central
+ directory field.
+
+ .ZIP file comment length: (2 bytes)
+
+ The length of the comment for this .ZIP file.
+
+ .ZIP file comment: (Variable)
+
+ The comment for this .ZIP file. ZIP file comment data
+ is stored unsecured. No encryption or data authentication
+ is applied to this area at this time. Confidential information
+ should not be stored in this section.
+
+ zip64 extensible data sector (variable size)
+
+ (currently reserved for use by PKWARE)
+
+
+ K. Splitting and Spanning ZIP files
+
+ Spanning is the process of segmenting a ZIP file across
+ multiple removable media. This support has typically only
+ been provided for DOS formatted floppy diskettes.
+
+ File splitting is a newer derivative of spanning.
+ Splitting follows the same segmentation process as
+ spanning, however, it does not require writing each
+ segment to a unique removable medium and instead supports
+ placing all pieces onto local or non-removable locations
+ such as file systems, local drives, folders, etc...
+
+ A key difference between spanned and split ZIP files is
+ that all pieces of a spanned ZIP file have the same name.
+ Since each piece is written to a separate volume, no name
+ collisions occur and each segment can reuse the original
+ .ZIP file name given to the archive.
+
+ Sequence ordering for DOS spanned archives uses the DOS
+ volume label to determine segment numbers. Volume labels
+ for each segment are written using the form PKBACK#xxx,
+ where xxx is the segment number written as a decimal
+ value from 001 - nnn.
+
+ Split ZIP files are typically written to the same location
+ and are subject to name collisions if the spanned name
+ format is used since each segment will reside on the same
+ drive. To avoid name collisions, split archives are named
+ as follows.
+
+ Segment 1 = filename.z01
+ Segment n-1 = filename.z(n-1)
+ Segment n = filename.zip
+
+ The .ZIP extension is used on the last segment to support
+ quickly reading the central directory. The segment number
+ n should be a decimal value.
+
+ Spanned ZIP files may be PKSFX Self-extracting ZIP files.
+ PKSFX files may also be split, however, in this case
+ the first segment must be named filename.exe. The first
+ segment of a split PKSFX archive must be large enough to
+ include the entire executable program.
+
+ Capacities for split archives are as follows.
+
+ Maximum number of segments = 4,294,967,295 - 1
+ Maximum .ZIP segment size = 4,294,967,295 bytes
+ Minimum segment size = 64K
+ Maximum PKSFX segment size = 2,147,483,647 bytes
+
+ Segment sizes may be different however by convention, all
+ segment sizes should be the same with the exception of the
+ last, which may be smaller. Local and central directory
+ header records must never be split across a segment boundary.
+ When writing a header record, if the number of bytes remaining
+ within a segment is less than the size of the header record,
+ end the current segment and write the header at the start
+ of the next segment. The central directory may span segment
+ boundaries, but no single record in the central directory
+ should be split across segments.
+
+ Spanned/Split archives created using PKZIP for Windows
+ (V2.50 or greater), PKZIP Command Line (V2.50 or greater),
+ or PKZIP Explorer will include a special spanning
+ signature as the first 4 bytes of the first segment of
+ the archive. This signature (0x08074b50) will be
+ followed immediately by the local header signature for
+ the first file in the archive.
+
+ A special spanning marker may also appear in spanned/split
+ archives if the spanning or splitting process starts but
+ only requires one segment. In this case the 0x08074b50
+ signature will be replaced with the temporary spanning
+ marker signature of 0x30304b50. Split archives can
+ only be uncompressed by other versions of PKZIP that
+ know how to create a split archive.
+
+ The signature value 0x08074b50 is also used by some
+ ZIP implementations as a marker for the Data Descriptor
+ record. Conflict in this alternate assignment can be
+ avoided by ensuring the position of the signature
+ within the ZIP file to determine the use for which it
+ is intended.
+
+ L. General notes:
+
+ 1) All fields unless otherwise noted are unsigned and stored
+ in Intel low-byte:high-byte, low-word:high-word order.
+
+ 2) String fields are not null terminated, since the
+ length is given explicitly.
+
+ 3) The entries in the central directory may not necessarily
+ be in the same order that files appear in the .ZIP file.
+
+ 4) If one of the fields in the end of central directory
+ record is too small to hold required data, the field
+ should be set to -1 (0xFFFF or 0xFFFFFFFF) and the
+ ZIP64 format record should be created.
+
+ 5) The end of central directory record and the
+ Zip64 end of central directory locator record must
+ reside on the same disk when splitting or spanning
+ an archive.
+
+VI. Explanation of compression methods
+--------------------------------------
+
+UnShrinking - Method 1
+----------------------
+
+Shrinking is a Dynamic Ziv-Lempel-Welch compression algorithm
+with partial clearing. The initial code size is 9 bits, and
+the maximum code size is 13 bits. Shrinking differs from
+conventional Dynamic Ziv-Lempel-Welch implementations in several
+respects:
+
+1) The code size is controlled by the compressor, and is not
+ automatically increased when codes larger than the current
+ code size are created (but not necessarily used). When
+ the decompressor encounters the code sequence 256
+ (decimal) followed by 1, it should increase the code size
+ read from the input stream to the next bit size. No
+ blocking of the codes is performed, so the next code at
+ the increased size should be read from the input stream
+ immediately after where the previous code at the smaller
+ bit size was read. Again, the decompressor should not
+ increase the code size used until the sequence 256,1 is
+ encountered.
+
+2) When the table becomes full, total clearing is not
+ performed. Rather, when the compressor emits the code
+ sequence 256,2 (decimal), the decompressor should clear
+ all leaf nodes from the Ziv-Lempel tree, and continue to
+ use the current code size. The nodes that are cleared
+ from the Ziv-Lempel tree are then re-used, with the lowest
+ code value re-used first, and the highest code value
+ re-used last. The compressor can emit the sequence 256,2
+ at any time.
+
+Expanding - Methods 2-5
+-----------------------
+
+The Reducing algorithm is actually a combination of two
+distinct algorithms. The first algorithm compresses repeated
+byte sequences, and the second algorithm takes the compressed
+stream from the first algorithm and applies a probabilistic
+compression method.
+
+The probabilistic compression stores an array of 'follower
+sets' S(j), for j=0 to 255, corresponding to each possible
+ASCII character. Each set contains between 0 and 32
+characters, to be denoted as S(j)[0],...,S(j)[m], where m<32.
+The sets are stored at the beginning of the data area for a
+Reduced file, in reverse order, with S(255) first, and S(0)
+last.
+
+The sets are encoded as { N(j), S(j)[0],...,S(j)[N(j)-1] },
+where N(j) is the size of set S(j). N(j) can be 0, in which
+case the follower set for S(j) is empty. Each N(j) value is
+encoded in 6 bits, followed by N(j) eight bit character values
+corresponding to S(j)[0] to S(j)[N(j)-1] respectively. If
+N(j) is 0, then no values for S(j) are stored, and the value
+for N(j-1) immediately follows.
+
+Immediately after the follower sets, is the compressed data
+stream. The compressed data stream can be interpreted for the
+probabilistic decompression as follows:
+
+let Last-Character <- 0.
+loop until done
+ if the follower set S(Last-Character) is empty then
+ read 8 bits from the input stream, and copy this
+ value to the output stream.
+ otherwise if the follower set S(Last-Character) is non-empty then
+ read 1 bit from the input stream.
+ if this bit is not zero then
+ read 8 bits from the input stream, and copy this
+ value to the output stream.
+ otherwise if this bit is zero then
+ read B(N(Last-Character)) bits from the input
+ stream, and assign this value to I.
+ Copy the value of S(Last-Character)[I] to the
+ output stream.
+
+ assign the last value placed on the output stream to
+ Last-Character.
+end loop
+
+B(N(j)) is defined as the minimal number of bits required to
+encode the value N(j)-1.
+
+The decompressed stream from above can then be expanded to
+re-create the original file as follows:
+
+let State <- 0.
+
+loop until done
+ read 8 bits from the input stream into C.
+ case State of
+ 0: if C is not equal to DLE (144 decimal) then
+ copy C to the output stream.
+ otherwise if C is equal to DLE then
+ let State <- 1.
+
+ 1: if C is non-zero then
+ let V <- C.
+ let Len <- L(V)
+ let State <- F(Len).
+ otherwise if C is zero then
+ copy the value 144 (decimal) to the output stream.
+ let State <- 0
+
+ 2: let Len <- Len + C
+ let State <- 3.
+
+ 3: move backwards D(V,C) bytes in the output stream
+ (if this position is before the start of the output
+ stream, then assume that all the data before the
+ start of the output stream is filled with zeros).
+ copy Len+3 bytes from this position to the output stream.
+ let State <- 0.
+ end case
+end loop
+
+The functions F,L, and D are dependent on the 'compression
+factor', 1 through 4, and are defined as follows:
+
+For compression factor 1:
+ L(X) equals the lower 7 bits of X.
+ F(X) equals 2 if X equals 127 otherwise F(X) equals 3.
+ D(X,Y) equals the (upper 1 bit of X) * 256 + Y + 1.
+For compression factor 2:
+ L(X) equals the lower 6 bits of X.
+ F(X) equals 2 if X equals 63 otherwise F(X) equals 3.
+ D(X,Y) equals the (upper 2 bits of X) * 256 + Y + 1.
+For compression factor 3:
+ L(X) equals the lower 5 bits of X.
+ F(X) equals 2 if X equals 31 otherwise F(X) equals 3.
+ D(X,Y) equals the (upper 3 bits of X) * 256 + Y + 1.
+For compression factor 4:
+ L(X) equals the lower 4 bits of X.
+ F(X) equals 2 if X equals 15 otherwise F(X) equals 3.
+ D(X,Y) equals the (upper 4 bits of X) * 256 + Y + 1.
+
+Imploding - Method 6
+--------------------
+
+The Imploding algorithm is actually a combination of two distinct
+algorithms. The first algorithm compresses repeated byte
+sequences using a sliding dictionary. The second algorithm is
+used to compress the encoding of the sliding dictionary output,
+using multiple Shannon-Fano trees.
+
+The Imploding algorithm can use a 4K or 8K sliding dictionary
+size. The dictionary size used can be determined by bit 1 in the
+general purpose flag word; a 0 bit indicates a 4K dictionary
+while a 1 bit indicates an 8K dictionary.
+
+The Shannon-Fano trees are stored at the start of the compressed
+file. The number of trees stored is defined by bit 2 in the
+general purpose flag word; a 0 bit indicates two trees stored, a
+1 bit indicates three trees are stored. If 3 trees are stored,
+the first Shannon-Fano tree represents the encoding of the
+Literal characters, the second tree represents the encoding of
+the Length information, the third represents the encoding of the
+Distance information. When 2 Shannon-Fano trees are stored, the
+Length tree is stored first, followed by the Distance tree.
+
+The Literal Shannon-Fano tree, if present is used to represent
+the entire ASCII character set, and contains 256 values. This
+tree is used to compress any data not compressed by the sliding
+dictionary algorithm. When this tree is present, the Minimum
+Match Length for the sliding dictionary is 3. If this tree is
+not present, the Minimum Match Length is 2.
+
+The Length Shannon-Fano tree is used to compress the Length part
+of the (length,distance) pairs from the sliding dictionary
+output. The Length tree contains 64 values, ranging from the
+Minimum Match Length, to 63 plus the Minimum Match Length.
+
+The Distance Shannon-Fano tree is used to compress the Distance
+part of the (length,distance) pairs from the sliding dictionary
+output. The Distance tree contains 64 values, ranging from 0 to
+63, representing the upper 6 bits of the distance value. The
+distance values themselves will be between 0 and the sliding
+dictionary size, either 4K or 8K.
+
+The Shannon-Fano trees themselves are stored in a compressed
+format. The first byte of the tree data represents the number of
+bytes of data representing the (compressed) Shannon-Fano tree
+minus 1. The remaining bytes represent the Shannon-Fano tree
+data encoded as:
+
+ High 4 bits: Number of values at this bit length + 1. (1 - 16)
+ Low 4 bits: Bit Length needed to represent value + 1. (1 - 16)
+
+The Shannon-Fano codes can be constructed from the bit lengths
+using the following algorithm:
+
+1) Sort the Bit Lengths in ascending order, while retaining the
+ order of the original lengths stored in the file.
+
+2) Generate the Shannon-Fano trees:
+
+ Code <- 0
+ CodeIncrement <- 0
+ LastBitLength <- 0
+ i <- number of Shannon-Fano codes - 1 (either 255 or 63)
+
+ loop while i >= 0
+ Code = Code + CodeIncrement
+ if BitLength(i) <> LastBitLength then
+ LastBitLength=BitLength(i)
+ CodeIncrement = 1 shifted left (16 - LastBitLength)
+ ShannonCode(i) = Code
+ i <- i - 1
+ end loop
+
+3) Reverse the order of all the bits in the above ShannonCode()
+ vector, so that the most significant bit becomes the least
+ significant bit. For example, the value 0x1234 (hex) would
+ become 0x2C48 (hex).
+
+4) Restore the order of Shannon-Fano codes as originally stored
+ within the file.
+
+Example:
+
+ This example will show the encoding of a Shannon-Fano tree
+ of size 8. Notice that the actual Shannon-Fano trees used
+ for Imploding are either 64 or 256 entries in size.
+
+Example: 0x02, 0x42, 0x01, 0x13
+
+ The first byte indicates 3 values in this table. Decoding the
+ bytes:
+ 0x42 = 5 codes of 3 bits long
+ 0x01 = 1 code of 2 bits long
+ 0x13 = 2 codes of 4 bits long
+
+ This would generate the original bit length array of:
+ (3, 3, 3, 3, 3, 2, 4, 4)
+
+ There are 8 codes in this table for the values 0 thru 7. Using
+ the algorithm to obtain the Shannon-Fano codes produces:
+
+ Reversed Order Original
+Val Sorted Constructed Code Value Restored Length
+--- ------ ----------------- -------- -------- ------
+0: 2 1100000000000000 11 101 3
+1: 3 1010000000000000 101 001 3
+2: 3 1000000000000000 001 110 3
+3: 3 0110000000000000 110 010 3
+4: 3 0100000000000000 010 100 3
+5: 3 0010000000000000 100 11 2
+6: 4 0001000000000000 1000 1000 4
+7: 4 0000000000000000 0000 0000 4
+
+The values in the Val, Order Restored and Original Length columns
+now represent the Shannon-Fano encoding tree that can be used for
+decoding the Shannon-Fano encoded data. How to parse the
+variable length Shannon-Fano values from the data stream is beyond
+the scope of this document. (See the references listed at the end of
+this document for more information.) However, traditional decoding
+schemes used for Huffman variable length decoding, such as the
+Greenlaw algorithm, can be successfully applied.
+
+The compressed data stream begins immediately after the
+compressed Shannon-Fano data. The compressed data stream can be
+interpreted as follows:
+
+loop until done
+ read 1 bit from input stream.
+
+ if this bit is non-zero then (encoded data is literal data)
+ if Literal Shannon-Fano tree is present
+ read and decode character using Literal Shannon-Fano tree.
+ otherwise
+ read 8 bits from input stream.
+ copy character to the output stream.
+ otherwise (encoded data is sliding dictionary match)
+ if 8K dictionary size
+ read 7 bits for offset Distance (lower 7 bits of offset).
+ otherwise
+ read 6 bits for offset Distance (lower 6 bits of offset).
+
+ using the Distance Shannon-Fano tree, read and decode the
+ upper 6 bits of the Distance value.
+
+ using the Length Shannon-Fano tree, read and decode
+ the Length value.
+
+ Length <- Length + Minimum Match Length
+
+ if Length = 63 + Minimum Match Length
+ read 8 bits from the input stream,
+ add this value to Length.
+
+ move backwards Distance+1 bytes in the output stream, and
+ copy Length characters from this position to the output
+ stream. (if this position is before the start of the output
+ stream, then assume that all the data before the start of
+ the output stream is filled with zeros).
+end loop
+
+Tokenizing - Method 7
+---------------------
+
+This method is not used by PKZIP.
+
+Deflating - Method 8
+--------------------
+
+The Deflate algorithm is similar to the Implode algorithm using
+a sliding dictionary of up to 32K with secondary compression
+from Huffman/Shannon-Fano codes.
+
+The compressed data is stored in blocks with a header describing
+the block and the Huffman codes used in the data block. The header
+format is as follows:
+
+ Bit 0: Last Block bit This bit is set to 1 if this is the last
+ compressed block in the data.
+ Bits 1-2: Block type
+ 00 (0) - Block is stored - All stored data is byte aligned.
+ Skip bits until next byte, then next word = block
+ length, followed by the ones compliment of the block
+ length word. Remaining data in block is the stored
+ data.
+
+ 01 (1) - Use fixed Huffman codes for literal and distance codes.
+ Lit Code Bits Dist Code Bits
+ --------- ---- --------- ----
+ 0 - 143 8 0 - 31 5
+ 144 - 255 9
+ 256 - 279 7
+ 280 - 287 8
+
+ Literal codes 286-287 and distance codes 30-31 are
+ never used but participate in the huffman construction.
+
+ 10 (2) - Dynamic Huffman codes. (See expanding Huffman codes)
+
+ 11 (3) - Reserved - Flag a "Error in compressed data" if seen.
+
+Expanding Huffman Codes
+-----------------------
+If the data block is stored with dynamic Huffman codes, the Huffman
+codes are sent in the following compressed format:
+
+ 5 Bits: # of Literal codes sent - 256 (256 - 286)
+ All other codes are never sent.
+ 5 Bits: # of Dist codes - 1 (1 - 32)
+ 4 Bits: # of Bit Length codes - 3 (3 - 19)
+
+The Huffman codes are sent as bit lengths and the codes are built as
+described in the implode algorithm. The bit lengths themselves are
+compressed with Huffman codes. There are 19 bit length codes:
+
+ 0 - 15: Represent bit lengths of 0 - 15
+ 16: Copy the previous bit length 3 - 6 times.
+ The next 2 bits indicate repeat length (0 = 3, ... ,3 = 6)
+ Example: Codes 8, 16 (+2 bits 11), 16 (+2 bits 10) will
+ expand to 12 bit lengths of 8 (1 + 6 + 5)
+ 17: Repeat a bit length of 0 for 3 - 10 times. (3 bits of length)
+ 18: Repeat a bit length of 0 for 11 - 138 times (7 bits of length)
+
+The lengths of the bit length codes are sent packed 3 bits per value
+(0 - 7) in the following order:
+
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+
+The Huffman codes should be built as described in the Implode algorithm
+except codes are assigned starting at the shortest bit length, i.e. the
+shortest code should be all 0's rather than all 1's. Also, codes with
+a bit length of zero do not participate in the tree construction. The
+codes are then used to decode the bit lengths for the literal and
+distance tables.
+
+The bit lengths for the literal tables are sent first with the number
+of entries sent described by the 5 bits sent earlier. There are up
+to 286 literal characters; the first 256 represent the respective 8
+bit character, code 256 represents the End-Of-Block code, the remaining
+29 codes represent copy lengths of 3 thru 258. There are up to 30
+distance codes representing distances from 1 thru 32k as described
+below.
+
+ Length Codes
+ ------------
+ Extra Extra Extra Extra
+ Code Bits Length Code Bits Lengths Code Bits Lengths Code Bits Length(s)
+ ---- ---- ------ ---- ---- ------- ---- ---- ------- ---- ---- ---------
+ 257 0 3 265 1 11,12 273 3 35-42 281 5 131-162
+ 258 0 4 266 1 13,14 274 3 43-50 282 5 163-194
+ 259 0 5 267 1 15,16 275 3 51-58 283 5 195-226
+ 260 0 6 268 1 17,18 276 3 59-66 284 5 227-257
+ 261 0 7 269 2 19-22 277 4 67-82 285 0 258
+ 262 0 8 270 2 23-26 278 4 83-98
+ 263 0 9 271 2 27-30 279 4 99-114
+ 264 0 10 272 2 31-34 280 4 115-130
+
+ Distance Codes
+ --------------
+ Extra Extra Extra Extra
+ Code Bits Dist Code Bits Dist Code Bits Distance Code Bits Distance
+ ---- ---- ---- ---- ---- ------ ---- ---- -------- ---- ---- --------
+ 0 0 1 8 3 17-24 16 7 257-384 24 11 4097-6144
+ 1 0 2 9 3 25-32 17 7 385-512 25 11 6145-8192
+ 2 0 3 10 4 33-48 18 8 513-768 26 12 8193-12288
+ 3 0 4 11 4 49-64 19 8 769-1024 27 12 12289-16384
+ 4 1 5,6 12 5 65-96 20 9 1025-1536 28 13 16385-24576
+ 5 1 7,8 13 5 97-128 21 9 1537-2048 29 13 24577-32768
+ 6 2 9-12 14 6 129-192 22 10 2049-3072
+ 7 2 13-16 15 6 193-256 23 10 3073-4096
+
+The compressed data stream begins immediately after the
+compressed header data. The compressed data stream can be
+interpreted as follows:
+
+do
+ read header from input stream.
+
+ if stored block
+ skip bits until byte aligned
+ read count and 1's compliment of count
+ copy count bytes data block
+ otherwise
+ loop until end of block code sent
+ decode literal character from input stream
+ if literal < 256
+ copy character to the output stream
+ otherwise
+ if literal = end of block
+ break from loop
+ otherwise
+ decode distance from input stream
+
+ move backwards distance bytes in the output stream, and
+ copy length characters from this position to the output
+ stream.
+ end loop
+while not last block
+
+if data descriptor exists
+ skip bits until byte aligned
+ read crc and sizes
+endif
+
+Enhanced Deflating - Method 9
+-----------------------------
+
+The Enhanced Deflating algorithm is similar to Deflate but
+uses a sliding dictionary of up to 64K. Deflate64(tm) is supported
+by the Deflate extractor.
+
+BZIP2 - Method 12
+-----------------
+
+BZIP2 is an open-source data compression algorithm developed by
+Julian Seward. Information and source code for this algorithm
+can be found on the internet.
+
+LZMA - Method 14 (EFS)
+----------------------
+
+LZMA is a block-oriented, general purpose data compression algorithm
+developed and maintained by Igor Pavlov. It is a derivative of LZ77
+that utilizes Markov chains and a range coder. Information and
+source code for this algorithm can be found on the internet. Consult
+with the author of this algorithm for information on terms or
+restrictions on use.
+
+Support for LZMA within the ZIP format is defined as follows:
+
+The Compression method field within the ZIP Local and Central
+Header records will be set to the value 14 to indicate data was
+compressed using LZMA.
+
+The Version needed to extract field within the ZIP Local and
+Central Header records will be set to 6.3 to indicate the
+minimum ZIP format version supporting this feature.
+
+File data compressed using the LZMA algorithm must be placed
+immediately following the Local Header for the file. If a
+standard ZIP encryption header is required, it will follow
+the Local Header and will precede the LZMA compressed file
+data segment. The location of LZMA compressed data segment
+within the ZIP format will be as shown:
+
+ [local header file 1]
+ [encryption header file 1]
+ [LZMA compressed data segment for file 1]
+ [data descriptor 1]
+ [local header file 2]
+
+The encryption header and data descriptor records may
+be conditionally present. The LZMA Compressed Data Segment
+will consist of an LZMA Properties Header followed by the
+LZMA Compressed Data as shown:
+
+ [LZMA properties header for file 1]
+ [LZMA compressed data for file 1]
+
+The LZMA Compressed Data will be stored as provided by the
+LZMA compression library. Compressed size, uncompressed
+size and other file characteristics about the file being
+compressed must be stored in standard ZIP storage format.
+
+The LZMA Properties Header will store specific data required to
+decompress the LZMA compressed Data. This data is set by the
+LZMA compression engine using the function WriteCoderProperties()
+as documented within the LZMA SDK.
+
+Storage fields for the property information within the LZMA
+Properties Header are as follows:
+
+ LZMA Version Information 2 bytes
+ LZMA Properties Size 2 bytes
+ LZMA Properties Data variable, defined by "LZMA Properties Size"
+
+LZMA Version Information - this field identifies which version of
+ the LZMA SDK was used to compress a file. The first byte will
+ store the major version number of the LZMA SDK and the second
+ byte will store the minor number.
+
+LZMA Properties Size - this field defines the size of the remaining
+ property data. Typically this size should be determined by the
+ version of the SDK. This size field is included as a convenience
+ and to help avoid any ambiguity should it arise in the future due
+ to changes in this compression algorithm.
+
+LZMA Property Data - this variable sized field records the required
+ values for the decompressor as defined by the LZMA SDK. The
+ data stored in this field should be obtained using the
+ WriteCoderProperties() in the version of the SDK defined by
+ the "LZMA Version Information" field.
+
+The layout of the "LZMA Properties Data" field is a function of the
+LZMA compression algorithm. It is possible that this layout may be
+changed by the author over time. The data layout in version 4.32
+of the LZMA SDK defines a 5 byte array that uses 4 bytes to store
+the dictionary size in little-endian order. This is preceded by a
+single packed byte as the first element of the array that contains
+the following fields:
+
+ PosStateBits
+ LiteralPosStateBits
+ LiteralContextBits
+
+Refer to the LZMA documentation for a more detailed explanation of
+these fields.
+
+Data compressed with method 14, LZMA, may include an end-of-stream
+(EOS) marker ending the compressed data stream. This marker is not
+required, but its use is highly recommended to facilitate processing
+and implementers should include the EOS marker whenever possible.
+When the EOS marker is used, general purpose bit 1 must be set. If
+general purpose bit 1 is not set, the EOS marker is not present.
+
+WavPack - Method 97
+-------------------
+
+Information describing the use of compression method 97 is
+provided by WinZIP International, LLC. This method relies on the
+open source WavPack audio compression utility developed by David Bryant.
+Information on WavPack is available at www.wavpack.com. Please consult
+with the author of this algorithm for information on terms and
+restrictions on use.
+
+WavPack data for a file begins immediately after the end of the
+local header data. This data is the output from WavPack compression
+routines. Within the ZIP file, the use of WavPack compression is
+indicated by setting the compression method field to a value of 97
+in both the local header and the central directory header. The Version
+needed to extract and version made by fields use the same values as are
+used for data compressed using the Deflate algorithm.
+
+An implementation note for storing digital sample data when using
+WavPack compression within ZIP files is that all of the bytes of
+the sample data should be compressed. This includes any unused
+bits up to the byte boundary. An example is a 2 byte sample that
+uses only 12 bits for the sample data with 4 unused bits. If only
+12 bits are passed as the sample size to the WavPack routines, the 4
+unused bits will be set to 0 on extraction regardless of their original
+state. To avoid this, the full 16 bits of the sample data size
+should be provided.
+
+PPMd - Method 98
+----------------
+
+PPMd is a data compression algorithm developed by Dmitry Shkarin
+which includes a carryless rangecoder developed by Dmitry Subbotin.
+This algorithm is based on predictive phrase matching on multiple
+order contexts. Information and source code for this algorithm
+can be found on the internet. Consult with the author of this
+algorithm for information on terms or restrictions on use.
+
+Support for PPMd within the ZIP format currently is provided only
+for version I, revision 1 of the algorithm. Storage requirements
+for using this algorithm are as follows:
+
+Parameters needed to control the algorithm are stored in the two
+bytes immediately preceding the compressed data. These bytes are
+used to store the following fields:
+
+Model order - sets the maximum model order, default is 8, possible
+ values are from 2 to 16 inclusive
+
+Sub-allocator size - sets the size of sub-allocator in MB, default is 50,
+ possible values are from 1MB to 256MB inclusive
+
+Model restoration method - sets the method used to restart context
+ model at memory insufficiency, values are:
+
+ 0 - restarts model from scratch - default
+ 1 - cut off model - decreases performance by as much as 2x
+ 2 - freeze context tree - not recommended
+
+An example for packing these fields into the 2 byte storage field is
+illustrated below. These values are stored in Intel low-byte/high-byte
+order.
+
+wPPMd = (Model order - 1) +
+ ((Sub-allocator size - 1) << 4) +
+ (Model restoration method << 12)
+
+
+VII. Traditional PKWARE Encryption
+----------------------------------
+
+The following information discusses the decryption steps
+required to support traditional PKWARE encryption. This
+form of encryption is considered weak by today's standards
+and its use is recommended only for situations with
+low security needs or for compatibility with older .ZIP
+applications.
+
+Decryption
+----------
+
+PKWARE is grateful to Mr. Roger Schlafly for his expert contribution
+towards the development of PKWARE's traditional encryption.
+
+PKZIP encrypts the compressed data stream. Encrypted files must
+be decrypted before they can be extracted.
+
+Each encrypted file has an extra 12 bytes stored at the start of
+the data area defining the encryption header for that file. The
+encryption header is originally set to random values, and then
+itself encrypted, using three, 32-bit keys. The key values are
+initialized using the supplied encryption password. After each byte
+is encrypted, the keys are then updated using pseudo-random number
+generation techniques in combination with the same CRC-32 algorithm
+used in PKZIP and described elsewhere in this document.
+
+The following is the basic steps required to decrypt a file:
+
+1) Initialize the three 32-bit keys with the password.
+2) Read and decrypt the 12-byte encryption header, further
+ initializing the encryption keys.
+3) Read and decrypt the compressed data stream using the
+ encryption keys.
+
+Step 1 - Initializing the encryption keys
+-----------------------------------------
+
+Key(0) <- 305419896
+Key(1) <- 591751049
+Key(2) <- 878082192
+
+loop for i <- 0 to length(password)-1
+ update_keys(password(i))
+end loop
+
+Where update_keys() is defined as:
+
+update_keys(char):
+ Key(0) <- crc32(key(0),char)
+ Key(1) <- Key(1) + (Key(0) & 000000ffH)
+ Key(1) <- Key(1) * 134775813 + 1
+ Key(2) <- crc32(key(2),key(1) >> 24)
+end update_keys
+
+Where crc32(old_crc,char) is a routine that given a CRC value and a
+character, returns an updated CRC value after applying the CRC-32
+algorithm described elsewhere in this document.
+
+Step 2 - Decrypting the encryption header
+-----------------------------------------
+
+The purpose of this step is to further initialize the encryption
+keys, based on random data, to render a plaintext attack on the
+data ineffective.
+
+Read the 12-byte encryption header into Buffer, in locations
+Buffer(0) thru Buffer(11).
+
+loop for i <- 0 to 11
+ C <- buffer(i) ^ decrypt_byte()
+ update_keys(C)
+ buffer(i) <- C
+end loop
+
+Where decrypt_byte() is defined as:
+
+unsigned char decrypt_byte()
+ local unsigned short temp
+ temp <- Key(2) | 2
+ decrypt_byte <- (temp * (temp ^ 1)) >> 8
+end decrypt_byte
+
+After the header is decrypted, the last 1 or 2 bytes in Buffer
+should be the high-order word/byte of the CRC for the file being
+decrypted, stored in Intel low-byte/high-byte order. Versions of
+PKZIP prior to 2.0 used a 2 byte CRC check; a 1 byte CRC check is
+used on versions after 2.0. This can be used to test if the password
+supplied is correct or not.
+
+Step 3 - Decrypting the compressed data stream
+----------------------------------------------
+
+The compressed data stream can be decrypted as follows:
+
+loop until done
+ read a character into C
+ Temp <- C ^ decrypt_byte()
+ update_keys(temp)
+ output Temp
+end loop
+
+
+VIII. Strong Encryption Specification
+-------------------------------------
+
+The Strong Encryption technology defined in this specification is
+covered under a pending patent application. The use or implementation
+in a product of certain technological aspects set forth in the current
+APPNOTE, including those with regard to strong encryption, patching,
+or extended tape operations requires a license from PKWARE. Portions
+of this Strong Encryption technology are available for use at no charge.
+Contact PKWARE for licensing terms and conditions. Refer to section II
+of this APPNOTE (Contacting PKWARE) for information on how to
+contact PKWARE.
+
+Version 5.x of this specification introduced support for strong
+encryption algorithms. These algorithms can be used with either
+a password or an X.509v3 digital certificate to encrypt each file.
+This format specification supports either password or certificate
+based encryption to meet the security needs of today, to enable
+interoperability between users within both PKI and non-PKI
+environments, and to ensure interoperability between different
+computing platforms that are running a ZIP program.
+
+Password based encryption is the most common form of encryption
+people are familiar with. However, inherent weaknesses with
+passwords (e.g. susceptibility to dictionary/brute force attack)
+as well as password management and support issues make certificate
+based encryption a more secure and scalable option. Industry
+efforts and support are defining and moving towards more advanced
+security solutions built around X.509v3 digital certificates and
+Public Key Infrastructures(PKI) because of the greater scalability,
+administrative options, and more robust security over traditional
+password based encryption.
+
+Most standard encryption algorithms are supported with this
+specification. Reference implementations for many of these
+algorithms are available from either commercial or open source
+distributors. Readily available cryptographic toolkits make
+implementation of the encryption features straight-forward.
+This document is not intended to provide a treatise on data
+encryption principles or theory. Its purpose is to document the
+data structures required for implementing interoperable data
+encryption within the .ZIP format. It is strongly recommended that
+you have a good understanding of data encryption before reading
+further.
+
+The algorithms introduced in Version 5.0 of this specification
+include:
+
+ RC2 40 bit, 64 bit, and 128 bit
+ RC4 40 bit, 64 bit, and 128 bit
+ DES
+ 3DES 112 bit and 168 bit
+
+Version 5.1 adds support for the following:
+
+ AES 128 bit, 192 bit, and 256 bit
+
+
+Version 6.1 introduces encryption data changes to support
+interoperability with Smartcard and USB Token certificate storage
+methods which do not support the OAEP strengthening standard.
+
+Version 6.2 introduces support for encrypting metadata by compressing
+and encrypting the central directory data structure to reduce information
+leakage. Information leakage can occur in legacy ZIP applications
+through exposure of information about a file even though that file is
+stored encrypted. The information exposed consists of file
+characteristics stored within the records and fields defined by this
+specification. This includes data such as a files name, its original
+size, timestamp and CRC32 value.
+
+Version 6.3 introduces support for encrypting data using the Blowfish
+and Twofish algorithms. These are symmetric block ciphers developed
+by Bruce Schneier. Blowfish supports using a variable length key from
+32 to 448 bits. Block size is 64 bits. Implementations should use 16
+rounds and the only mode supported within ZIP files is CBC. Twofish
+supports key sizes 128, 192 and 256 bits. Block size is 128 bits.
+Implementations should use 16 rounds and the only mode supported within
+ZIP files is CBC. Information and source code for both Blowfish and
+Twofish algorithms can be found on the internet. Consult with the author
+of these algorithms for information on terms or restrictions on use.
+
+Central Directory Encryption provides greater protection against
+information leakage by encrypting the Central Directory structure and
+by masking key values that are replicated in the unencrypted Local
+Header. ZIP compatible programs that cannot interpret an encrypted
+Central Directory structure cannot rely on the data in the corresponding
+Local Header for decompression information.
+
+Extra Field records that may contain information about a file that should
+not be exposed should not be stored in the Local Header and should only
+be written to the Central Directory where they can be encrypted. This
+design currently does not support streaming. Information in the End of
+Central Directory record, the Zip64 End of Central Directory Locator,
+and the Zip64 End of Central Directory records are not encrypted. Access
+to view data on files within a ZIP file with an encrypted Central Directory
+requires the appropriate password or private key for decryption prior to
+viewing any files, or any information about the files, in the archive.
+
+Older ZIP compatible programs not familiar with the Central Directory
+Encryption feature will no longer be able to recognize the Central
+Directory and may assume the ZIP file is corrupt. Programs that
+attempt streaming access using Local Headers will see invalid
+information for each file. Central Directory Encryption need not be
+used for every ZIP file. Its use is recommended for greater security.
+ZIP files not using Central Directory Encryption should operate as
+in the past.
+
+This strong encryption feature specification is intended to provide for
+scalable, cross-platform encryption needs ranging from simple password
+encryption to authenticated public/private key encryption.
+
+Encryption provides data confidentiality and privacy. It is
+recommended that you combine X.509 digital signing with encryption
+to add authentication and non-repudiation.
+
+
+Single Password Symmetric Encryption Method:
+-------------------------------------------
+
+The Single Password Symmetric Encryption Method using strong
+encryption algorithms operates similarly to the traditional
+PKWARE encryption defined in this format. Additional data
+structures are added to support the processing needs of the
+strong algorithms.
+
+The Strong Encryption data structures are:
+
+1. General Purpose Bits - Bits 0 and 6 of the General Purpose bit
+flag in both local and central header records. Both bits set
+indicates strong encryption. Bit 13, when set indicates the Central
+Directory is encrypted and that selected fields in the Local Header
+are masked to hide their actual value.
+
+
+2. Extra Field 0x0017 in central header only.
+
+ Fields to consider in this record are:
+
+ Format - the data format identifier for this record. The only
+ value allowed at this time is the integer value 2.
+
+ AlgId - integer identifier of the encryption algorithm from the
+ following range
+
+ 0x6601 - DES
+ 0x6602 - RC2 (version needed to extract < 5.2)
+ 0x6603 - 3DES 168
+ 0x6609 - 3DES 112
+ 0x660E - AES 128
+ 0x660F - AES 192
+ 0x6610 - AES 256
+ 0x6702 - RC2 (version needed to extract >= 5.2)
+ 0x6720 - Blowfish
+ 0x6721 - Twofish
+ 0x6801 - RC4
+ 0xFFFF - Unknown algorithm
+
+ Bitlen - Explicit bit length of key
+
+ 32 - 448 bits
+
+ Flags - Processing flags needed for decryption
+
+ 0x0001 - Password is required to decrypt
+ 0x0002 - Certificates only
+ 0x0003 - Password or certificate required to decrypt
+
+ Values > 0x0003 reserved for certificate processing
+
+
+3. Decryption header record preceding compressed file data.
+
+ -Decryption Header:
+
+ Value Size Description
+ ----- ---- -----------
+ IVSize 2 bytes Size of initialization vector (IV)
+ IVData IVSize Initialization vector for this file
+ Size 4 bytes Size of remaining decryption header data
+ Format 2 bytes Format definition for this record
+ AlgID 2 bytes Encryption algorithm identifier
+ Bitlen 2 bytes Bit length of encryption key
+ Flags 2 bytes Processing flags
+ ErdSize 2 bytes Size of Encrypted Random Data
+ ErdData ErdSize Encrypted Random Data
+ Reserved1 4 bytes Reserved certificate processing data
+ Reserved2 (var) Reserved for certificate processing data
+ VSize 2 bytes Size of password validation data
+ VData VSize-4 Password validation data
+ VCRC32 4 bytes Standard ZIP CRC32 of password validation data
+
+ IVData - The size of the IV should match the algorithm block size.
+ The IVData can be completely random data. If the size of
+ the randomly generated data does not match the block size
+ it should be complemented with zero's or truncated as
+ necessary. If IVSize is 0,then IV = CRC32 + Uncompressed
+ File Size (as a 64 bit little-endian, unsigned integer value).
+
+ Format - the data format identifier for this record. The only
+ value allowed at this time is the integer value 3.
+
+ AlgId - integer identifier of the encryption algorithm from the
+ following range
+
+ 0x6601 - DES
+ 0x6602 - RC2 (version needed to extract < 5.2)
+ 0x6603 - 3DES 168
+ 0x6609 - 3DES 112
+ 0x660E - AES 128
+ 0x660F - AES 192
+ 0x6610 - AES 256
+ 0x6702 - RC2 (version needed to extract >= 5.2)
+ 0x6720 - Blowfish
+ 0x6721 - Twofish
+ 0x6801 - RC4
+ 0xFFFF - Unknown algorithm
+
+ Bitlen - Explicit bit length of key
+
+ 32 - 448 bits
+
+ Flags - Processing flags needed for decryption
+
+ 0x0001 - Password is required to decrypt
+ 0x0002 - Certificates only
+ 0x0003 - Password or certificate required to decrypt
+
+ Values > 0x0003 reserved for certificate processing
+
+ ErdData - Encrypted random data is used to store random data that
+ is used to generate a file session key for encrypting
+ each file. SHA1 is used to calculate hash data used to
+ derive keys. File session keys are derived from a master
+ session key generated from the user-supplied password.
+ If the Flags field in the decryption header contains
+ the value 0x4000, then the ErdData field must be
+ decrypted using 3DES. If the value 0x4000 is not set,
+ then the ErdData field must be decrypted using AlgId.
+
+
+ Reserved1 - Reserved for certificate processing, if value is
+ zero, then Reserved2 data is absent. See the explanation
+ under the Certificate Processing Method for details on
+ this data structure.
+
+ Reserved2 - If present, the size of the Reserved2 data structure
+ is located by skipping the first 4 bytes of this field
+ and using the next 2 bytes as the remaining size. See
+ the explanation under the Certificate Processing Method
+ for details on this data structure.
+
+ VSize - This size value will always include the 4 bytes of the
+ VCRC32 data and will be greater than 4 bytes.
+
+ VData - Random data for password validation. This data is VSize
+ in length and VSize must be a multiple of the encryption
+ block size. VCRC32 is a checksum value of VData.
+ VData and VCRC32 are stored encrypted and start the
+ stream of encrypted data for a file.
+
+
+4. Useful Tips
+
+Strong Encryption is always applied to a file after compression. The
+block oriented algorithms all operate in Cypher Block Chaining (CBC)
+mode. The block size used for AES encryption is 16. All other block
+algorithms use a block size of 8. Two ID's are defined for RC2 to
+account for a discrepancy found in the implementation of the RC2
+algorithm in the cryptographic library on Windows XP SP1 and all
+earlier versions of Windows. It is recommended that zero length files
+not be encrypted, however programs should be prepared to extract them
+if they are found within a ZIP file.
+
+A pseudo-code representation of the encryption process is as follows:
+
+Password = GetUserPassword()
+MasterSessionKey = DeriveKey(SHA1(Password))
+RD = CryptographicStrengthRandomData()
+For Each File
+ IV = CryptographicStrengthRandomData()
+ VData = CryptographicStrengthRandomData()
+ VCRC32 = CRC32(VData)
+ FileSessionKey = DeriveKey(SHA1(IV + RD)
+ ErdData = Encrypt(RD,MasterSessionKey,IV)
+ Encrypt(VData + VCRC32 + FileData, FileSessionKey,IV)
+Done
+
+The function names and parameter requirements will depend on
+the choice of the cryptographic toolkit selected. Almost any
+toolkit supporting the reference implementations for each
+algorithm can be used. The RSA BSAFE(r), OpenSSL, and Microsoft
+CryptoAPI libraries are all known to work well.
+
+
+Single Password - Central Directory Encryption:
+-----------------------------------------------
+
+Central Directory Encryption is achieved within the .ZIP format by
+encrypting the Central Directory structure. This encapsulates the metadata
+most often used for processing .ZIP files. Additional metadata is stored for
+redundancy in the Local Header for each file. The process of concealing
+metadata by encrypting the Central Directory does not protect the data within
+the Local Header. To avoid information leakage from the exposed metadata
+in the Local Header, the fields containing information about a file are masked.
+
+Local Header:
+
+Masking replaces the true content of the fields for a file in the Local
+Header with false information. When masked, the Local Header is not
+suitable for streaming access and the options for data recovery of damaged
+archives is reduced. Extra Data fields that may contain confidential
+data should not be stored within the Local Header. The value set into
+the Version needed to extract field should be the correct value needed to
+extract the file without regard to Central Directory Encryption. The fields
+within the Local Header targeted for masking when the Central Directory is
+encrypted are:
+
+ Field Name Mask Value
+ ------------------ ---------------------------
+ compression method 0
+ last mod file time 0
+ last mod file date 0
+ crc-32 0
+ compressed size 0
+ uncompressed size 0
+ file name (variable size) Base 16 value from the
+ range 1 - 0xFFFFFFFFFFFFFFFF
+ represented as a string whose
+ size will be set into the
+ file name length field
+
+The Base 16 value assigned as a masked file name is simply a sequentially
+incremented value for each file starting with 1 for the first file.
+Modifications to a ZIP file may cause different values to be stored for
+each file. For compatibility, the file name field in the Local Header
+should never be left blank. As of Version 6.2 of this specification,
+the Compression Method and Compressed Size fields are not yet masked.
+Fields having a value of 0xFFFF or 0xFFFFFFFF for the ZIP64 format
+should not be masked.
+
+Encrypting the Central Directory:
+
+Encryption of the Central Directory does not include encryption of the
+Central Directory Signature data, the Zip64 End of Central Directory
+record, the Zip64 End of Central Directory Locator, or the End
+of Central Directory record. The ZIP file comment data is never
+encrypted.
+
+Before encrypting the Central Directory, it may optionally be compressed.
+Compression is not required, but for storage efficiency it is assumed
+this structure will be compressed before encrypting. Similarly, this
+specification supports compressing the Central Directory without
+requiring that it also be encrypted. Early implementations of this
+feature will assume the encryption method applied to files matches the
+encryption applied to the Central Directory.
+
+Encryption of the Central Directory is done in a manner similar to
+that of file encryption. The encrypted data is preceded by a
+decryption header. The decryption header is known as the Archive
+Decryption Header. The fields of this record are identical to
+the decryption header preceding each encrypted file. The location
+of the Archive Decryption Header is determined by the value in the
+Start of the Central Directory field in the Zip64 End of Central
+Directory record. When the Central Directory is encrypted, the
+Zip64 End of Central Directory record will always be present.
+
+The layout of the Zip64 End of Central Directory record for all
+versions starting with 6.2 of this specification will follow the
+Version 2 format. The Version 2 format is as follows:
+
+The leading fixed size fields within the Version 1 format for this
+record remain unchanged. The record signature for both Version 1
+and Version 2 will be 0x06064b50. Immediately following the last
+byte of the field known as the Offset of Start of Central
+Directory With Respect to the Starting Disk Number will begin the
+new fields defining Version 2 of this record.
+
+New fields for Version 2:
+
+Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ Compression Method 2 bytes Method used to compress the
+ Central Directory
+ Compressed Size 8 bytes Size of the compressed data
+ Original Size 8 bytes Original uncompressed size
+ AlgId 2 bytes Encryption algorithm ID
+ BitLen 2 bytes Encryption key length
+ Flags 2 bytes Encryption flags
+ HashID 2 bytes Hash algorithm identifier
+ Hash Length 2 bytes Length of hash data
+ Hash Data (variable) Hash data
+
+The Compression Method accepts the same range of values as the
+corresponding field in the Central Header.
+
+The Compressed Size and Original Size values will not include the
+data of the Central Directory Signature which is compressed or
+encrypted.
+
+The AlgId, BitLen, and Flags fields accept the same range of values
+the corresponding fields within the 0x0017 record.
+
+Hash ID identifies the algorithm used to hash the Central Directory
+data. This data does not have to be hashed, in which case the
+values for both the HashID and Hash Length will be 0. Possible
+values for HashID are:
+
+ Value Algorithm
+ ------ ---------
+ 0x0000 none
+ 0x0001 CRC32
+ 0x8003 MD5
+ 0x8004 SHA1
+ 0x8007 RIPEMD160
+ 0x800C SHA256
+ 0x800D SHA384
+ 0x800E SHA512
+
+When the Central Directory data is signed, the same hash algorithm
+used to hash the Central Directory for signing should be used.
+This is recommended for processing efficiency, however, it is
+permissible for any of the above algorithms to be used independent
+of the signing process.
+
+The Hash Data will contain the hash data for the Central Directory.
+The length of this data will vary depending on the algorithm used.
+
+The Version Needed to Extract should be set to 62.
+
+The value for the Total Number of Entries on the Current Disk will
+be 0. These records will no longer support random access when
+encrypting the Central Directory.
+
+When the Central Directory is compressed and/or encrypted, the
+End of Central Directory record will store the value 0xFFFFFFFF
+as the value for the Total Number of Entries in the Central
+Directory. The value stored in the Total Number of Entries in
+the Central Directory on this Disk field will be 0. The actual
+values will be stored in the equivalent fields of the Zip64
+End of Central Directory record.
+
+Decrypting and decompressing the Central Directory is accomplished
+in the same manner as decrypting and decompressing a file.
+
+Certificate Processing Method:
+-----------------------------
+
+The Certificate Processing Method of for ZIP file encryption
+defines the following additional data fields:
+
+1. Certificate Flag Values
+
+Additional processing flags that can be present in the Flags field of both
+the 0x0017 field of the central directory Extra Field and the Decryption
+header record preceding compressed file data are:
+
+ 0x0007 - reserved for future use
+ 0x000F - reserved for future use
+ 0x0100 - Indicates non-OAEP key wrapping was used. If this
+ this field is set, the version needed to extract must
+ be at least 61. This means OAEP key wrapping is not
+ used when generating a Master Session Key using
+ ErdData.
+ 0x4000 - ErdData must be decrypted using 3DES-168, otherwise use the
+ same algorithm used for encrypting the file contents.
+ 0x8000 - reserved for future use
+
+
+2. CertData - Extra Field 0x0017 record certificate data structure
+
+The data structure used to store certificate data within the section
+of the Extra Field defined by the CertData field of the 0x0017
+record are as shown:
+
+ Value Size Description
+ ----- ---- -----------
+ RCount 4 bytes Number of recipients.
+ HashAlg 2 bytes Hash algorithm identifier
+ HSize 2 bytes Hash size
+ SRList (var) Simple list of recipients hashed public keys
+
+
+ RCount This defines the number intended recipients whose
+ public keys were used for encryption. This identifies
+ the number of elements in the SRList.
+
+ HashAlg This defines the hash algorithm used to calculate
+ the public key hash of each public key used
+ for encryption. This field currently supports
+ only the following value for SHA-1
+
+ 0x8004 - SHA1
+
+ HSize This defines the size of a hashed public key.
+
+ SRList This is a variable length list of the hashed
+ public keys for each intended recipient. Each
+ element in this list is HSize. The total size of
+ SRList is determined using RCount * HSize.
+
+
+3. Reserved1 - Certificate Decryption Header Reserved1 Data:
+
+ Value Size Description
+ ----- ---- -----------
+ RCount 4 bytes Number of recipients.
+
+ RCount This defines the number intended recipients whose
+ public keys were used for encryption. This defines
+ the number of elements in the REList field defined below.
+
+
+4. Reserved2 - Certificate Decryption Header Reserved2 Data Structures:
+
+
+ Value Size Description
+ ----- ---- -----------
+ HashAlg 2 bytes Hash algorithm identifier
+ HSize 2 bytes Hash size
+ REList (var) List of recipient data elements
+
+
+ HashAlg This defines the hash algorithm used to calculate
+ the public key hash of each public key used
+ for encryption. This field currently supports
+ only the following value for SHA-1
+
+ 0x8004 - SHA1
+
+ HSize This defines the size of a hashed public key
+ defined in REHData.
+
+ REList This is a variable length of list of recipient data.
+ Each element in this list consists of a Recipient
+ Element data structure as follows:
+
+
+ Recipient Element (REList) Data Structure:
+
+ Value Size Description
+ ----- ---- -----------
+ RESize 2 bytes Size of REHData + REKData
+ REHData HSize Hash of recipients public key
+ REKData (var) Simple key blob
+
+
+ RESize This defines the size of an individual REList
+ element. This value is the combined size of the
+ REHData field + REKData field. REHData is defined by
+ HSize. REKData is variable and can be calculated
+ for each REList element using RESize and HSize.
+
+ REHData Hashed public key for this recipient.
+
+ REKData Simple Key Blob. The format of this data structure
+ is identical to that defined in the Microsoft
+ CryptoAPI and generated using the CryptExportKey()
+ function. The version of the Simple Key Blob
+ supported at this time is 0x02 as defined by
+ Microsoft.
+
+Certificate Processing - Central Directory Encryption:
+------------------------------------------------------
+
+Central Directory Encryption using Digital Certificates will
+operate in a manner similar to that of Single Password Central
+Directory Encryption. This record will only be present when there
+is data to place into it. Currently, data is placed into this
+record when digital certificates are used for either encrypting
+or signing the files within a ZIP file. When only password
+encryption is used with no certificate encryption or digital
+signing, this record is not currently needed. When present, this
+record will appear before the start of the actual Central Directory
+data structure and will be located immediately after the Archive
+Decryption Header if the Central Directory is encrypted.
+
+The Archive Extra Data record will be used to store the following
+information. Additional data may be added in future versions.
+
+Extra Data Fields:
+
+0x0014 - PKCS#7 Store for X.509 Certificates
+0x0016 - X.509 Certificate ID and Signature for central directory
+0x0019 - PKCS#7 Encryption Recipient Certificate List
+
+The 0x0014 and 0x0016 Extra Data records that otherwise would be
+located in the first record of the Central Directory for digital
+certificate processing. When encrypting or compressing the Central
+Directory, the 0x0014 and 0x0016 records must be located in the
+Archive Extra Data record and they should not remain in the first
+Central Directory record. The Archive Extra Data record will also
+be used to store the 0x0019 data.
+
+When present, the size of the Archive Extra Data record will be
+included in the size of the Central Directory. The data of the
+Archive Extra Data record will also be compressed and encrypted
+along with the Central Directory data structure.
+
+Certificate Processing Differences:
+
+The Certificate Processing Method of encryption differs from the
+Single Password Symmetric Encryption Method as follows. Instead
+of using a user-defined password to generate a master session key,
+cryptographically random data is used. The key material is then
+wrapped using standard key-wrapping techniques. This key material
+is wrapped using the public key of each recipient that will need
+to decrypt the file using their corresponding private key.
+
+This specification currently assumes digital certificates will follow
+the X.509 V3 format for 1024 bit and higher RSA format digital
+certificates. Implementation of this Certificate Processing Method
+requires supporting logic for key access and management. This logic
+is outside the scope of this specification.
+
+OAEP Processing with Certificate-based Encryption:
+
+OAEP stands for Optimal Asymmetric Encryption Padding. It is a
+strengthening technique used for small encoded items such as decryption
+keys. This is commonly applied in cryptographic key-wrapping techniques
+and is supported by PKCS #1. Versions 5.0 and 6.0 of this specification
+were designed to support OAEP key-wrapping for certificate-based
+decryption keys for additional security.
+
+Support for private keys stored on Smartcards or Tokens introduced
+a conflict with this OAEP logic. Most card and token products do
+not support the additional strengthening applied to OAEP key-wrapped
+data. In order to resolve this conflict, versions 6.1 and above of this
+specification will no longer support OAEP when encrypting using
+digital certificates.
+
+Versions of PKZIP available during initial development of the
+certificate processing method set a value of 61 into the
+version needed to extract field for a file. This indicates that
+non-OAEP key wrapping is used. This affects certificate encryption
+only, and password encryption functions should not be affected by
+this value. This means values of 61 may be found on files encrypted
+with certificates only, or on files encrypted with both password
+encryption and certificate encryption. Files encrypted with both
+methods can safely be decrypted using the password methods documented.
+
+IX. Change Process
+------------------
+
+In order for the .ZIP file format to remain a viable definition, this
+specification should be considered as open for periodic review and
+revision. Although this format was originally designed with a
+certain level of extensibility, not all changes in technology
+(present or future) were or will be necessarily considered in its
+design. If your application requires new definitions to the
+extensible sections in this format, or if you would like to
+submit new data structures, please forward your request to
+zipformat@pkware.com. All submissions will be reviewed by the
+ZIP File Specification Committee for possible inclusion into
+future versions of this specification. Periodic revisions
+to this specification will be published to ensure interoperability.
+We encourage comments and feedback that may help improve clarity
+or content.
+
+X. Incorporating PKWARE Proprietary Technology into Your Product
+----------------------------------------------------------------
+
+PKWARE is committed to the interoperability and advancement of the
+.ZIP format. PKWARE offers a free license for certain technological
+aspects described above under certain restrictions and conditions.
+However, the use or implementation in a product of certain technological
+aspects set forth in the current APPNOTE, including those with regard to
+strong encryption, patching, or extended tape operations requires a
+license from PKWARE. Please contact PKWARE with regard to acquiring
+a license.
+
+XI. Acknowledgements
+---------------------
+
+In addition to the above mentioned contributors to PKZIP and PKUNZIP,
+I would like to extend special thanks to Robert Mahoney for suggesting
+the extension .ZIP for this software.
+
+XII. References
+---------------
+
+ Fiala, Edward R., and Greene, Daniel H., "Data compression with
+ finite windows", Communications of the ACM, Volume 32, Number 4,
+ April 1989, pages 490-505.
+
+ Held, Gilbert, "Data Compression, Techniques and Applications,
+ Hardware and Software Considerations", John Wiley & Sons, 1987.
+
+ Huffman, D.A., "A method for the construction of minimum-redundancy
+ codes", Proceedings of the IRE, Volume 40, Number 9, September 1952,
+ pages 1098-1101.
+
+ Nelson, Mark, "LZW Data Compression", Dr. Dobbs Journal, Volume 14,
+ Number 10, October 1989, pages 29-37.
+
+ Nelson, Mark, "The Data Compression Book", M&T Books, 1991.
+
+ Storer, James A., "Data Compression, Methods and Theory",
+ Computer Science Press, 1988
+
+ Welch, Terry, "A Technique for High-Performance Data Compression",
+ IEEE Computer, Volume 17, Number 6, June 1984, pages 8-19.
+
+ Ziv, J. and Lempel, A., "A universal algorithm for sequential data
+ compression", Communications of the ACM, Volume 30, Number 6,
+ June 1987, pages 520-540.
+
+ Ziv, J. and Lempel, A., "Compression of individual sequences via
+ variable-rate coding", IEEE Transactions on Information Theory,
+ Volume 24, Number 5, September 1978, pages 530-536.
+
+
+APPENDIX A - AS/400 Extra Field (0x0065) Attribute Definitions
+--------------------------------------------------------------
+
+Field Definition Structure:
+
+ a. field length including length 2 bytes
+ b. field code 2 bytes
+ c. data x bytes
+
+Field Code Description
+ 4001 Source type i.e. CLP etc
+ 4002 The text description of the library
+ 4003 The text description of the file
+ 4004 The text description of the member
+ 4005 x'F0' or 0 is PF-DTA, x'F1' or 1 is PF_SRC
+ 4007 Database Type Code 1 byte
+ 4008 Database file and fields definition
+ 4009 GZIP file type 2 bytes
+ 400B IFS code page 2 bytes
+ 400C IFS Creation Time 4 bytes
+ 400D IFS Access Time 4 bytes
+ 400E IFS Modification time 4 bytes
+ 005C Length of the records in the file 2 bytes
+ 0068 GZIP two words 8 bytes
+
+APPENDIX B - z/OS Extra Field (0x0065) Attribute Definitions
+------------------------------------------------------------
+
+Field Definition Structure:
+
+ a. field length including length 2 bytes
+ b. field code 2 bytes
+ c. data x bytes
+
+Field Code Description
+ 0001 File Type 2 bytes
+ 0002 NonVSAM Record Format 1 byte
+ 0003 Reserved
+ 0004 NonVSAM Block Size 2 bytes Big Endian
+ 0005 Primary Space Allocation 3 bytes Big Endian
+ 0006 Secondary Space Allocation 3 bytes Big Endian
+ 0007 Space Allocation Type1 byte flag
+ 0008 Modification Date Retired with PKZIP 5.0 +
+ 0009 Expiration Date Retired with PKZIP 5.0 +
+ 000A PDS Directory Block Allocation 3 bytes Big Endian binary value
+ 000B NonVSAM Volume List variable
+ 000C UNIT Reference Retired with PKZIP 5.0 +
+ 000D DF/SMS Management Class 8 bytes EBCDIC Text Value
+ 000E DF/SMS Storage Class 8 bytes EBCDIC Text Value
+ 000F DF/SMS Data Class 8 bytes EBCDIC Text Value
+ 0010 PDS/PDSE Member Info. 30 bytes
+ 0011 VSAM sub-filetype 2 bytes
+ 0012 VSAM LRECL 13 bytes EBCDIC "(num_avg num_max)"
+ 0013 VSAM Cluster Name Retired with PKZIP 5.0 +
+ 0014 VSAM KSDS Key Information 13 bytes EBCDIC "(num_length num_position)"
+ 0015 VSAM Average LRECL 5 bytes EBCDIC num_value padded with blanks
+ 0016 VSAM Maximum LRECL 5 bytes EBCDIC num_value padded with blanks
+ 0017 VSAM KSDS Key Length 5 bytes EBCDIC num_value padded with blanks
+ 0018 VSAM KSDS Key Position 5 bytes EBCDIC num_value padded with blanks
+ 0019 VSAM Data Name 1-44 bytes EBCDIC text string
+ 001A VSAM KSDS Index Name 1-44 bytes EBCDIC text string
+ 001B VSAM Catalog Name 1-44 bytes EBCDIC text string
+ 001C VSAM Data Space Type 9 bytes EBCDIC text string
+ 001D VSAM Data Space Primary 9 bytes EBCDIC num_value left-justified
+ 001E VSAM Data Space Secondary 9 bytes EBCDIC num_value left-justified
+ 001F VSAM Data Volume List variable EBCDIC text list of 6-character Volume IDs
+ 0020 VSAM Data Buffer Space 8 bytes EBCDIC num_value left-justified
+ 0021 VSAM Data CISIZE 5 bytes EBCDIC num_value left-justified
+ 0022 VSAM Erase Flag 1 byte flag
+ 0023 VSAM Free CI % 3 bytes EBCDIC num_value left-justified
+ 0024 VSAM Free CA % 3 bytes EBCDIC num_value left-justified
+ 0025 VSAM Index Volume List variable EBCDIC text list of 6-character Volume IDs
+ 0026 VSAM Ordered Flag 1 byte flag
+ 0027 VSAM REUSE Flag 1 byte flag
+ 0028 VSAM SPANNED Flag 1 byte flag
+ 0029 VSAM Recovery Flag 1 byte flag
+ 002A VSAM WRITECHK Flag 1 byte flag
+ 002B VSAM Cluster/Data SHROPTS 3 bytes EBCDIC "n,y"
+ 002C VSAM Index SHROPTS 3 bytes EBCDIC "n,y"
+ 002D VSAM Index Space Type 9 bytes EBCDIC text string
+ 002E VSAM Index Space Primary 9 bytes EBCDIC num_value left-justified
+ 002F VSAM Index Space Secondary 9 bytes EBCDIC num_value left-justified
+ 0030 VSAM Index CISIZE 5 bytes EBCDIC num_value left-justified
+ 0031 VSAM Index IMBED 1 byte flag
+ 0032 VSAM Index Ordered Flag 1 byte flag
+ 0033 VSAM REPLICATE Flag 1 byte flag
+ 0034 VSAM Index REUSE Flag 1 byte flag
+ 0035 VSAM Index WRITECHK Flag 1 byte flag Retired with PKZIP 5.0 +
+ 0036 VSAM Owner 8 bytes EBCDIC text string
+ 0037 VSAM Index Owner 8 bytes EBCDIC text string
+ 0038 Reserved
+ 0039 Reserved
+ 003A Reserved
+ 003B Reserved
+ 003C Reserved
+ 003D Reserved
+ 003E Reserved
+ 003F Reserved
+ 0040 Reserved
+ 0041 Reserved
+ 0042 Reserved
+ 0043 Reserved
+ 0044 Reserved
+ 0045 Reserved
+ 0046 Reserved
+ 0047 Reserved
+ 0048 Reserved
+ 0049 Reserved
+ 004A Reserved
+ 004B Reserved
+ 004C Reserved
+ 004D Reserved
+ 004E Reserved
+ 004F Reserved
+ 0050 Reserved
+ 0051 Reserved
+ 0052 Reserved
+ 0053 Reserved
+ 0054 Reserved
+ 0055 Reserved
+ 0056 Reserved
+ 0057 Reserved
+ 0058 PDS/PDSE Member TTR Info. 6 bytes Big Endian
+ 0059 PDS 1st LMOD Text TTR 3 bytes Big Endian
+ 005A PDS LMOD EP Rec # 4 bytes Big Endian
+ 005B Reserved
+ 005C Max Length of records 2 bytes Big Endian
+ 005D PDSE Flag 1 byte flag
+ 005E Reserved
+ 005F Reserved
+ 0060 Reserved
+ 0061 Reserved
+ 0062 Reserved
+ 0063 Reserved
+ 0064 Reserved
+ 0065 Last Date Referenced 4 bytes Packed Hex "yyyymmdd"
+ 0066 Date Created 4 bytes Packed Hex "yyyymmdd"
+ 0068 GZIP two words 8 bytes
+ 0071 Extended NOTE Location 12 bytes Big Endian
+ 0072 Archive device UNIT 6 bytes EBCDIC
+ 0073 Archive 1st Volume 6 bytes EBCDIC
+ 0074 Archive 1st VOL File Seq# 2 bytes Binary
+
+APPENDIX C - Zip64 Extensible Data Sector Mappings (EFS)
+--------------------------------------------------------
+
+ -Z390 Extra Field:
+
+ The following is the general layout of the attributes for the
+ ZIP 64 "extra" block for extended tape operations. Portions of
+ this extended tape processing technology is covered under a
+ pending patent application. The use or implementation in a
+ product of certain technological aspects set forth in the
+ current APPNOTE, including those with regard to strong encryption,
+ patching or extended tape operations, requires a license from
+ PKWARE. Please contact PKWARE with regard to acquiring a license.
+
+
+ Note: some fields stored in Big Endian format. All text is
+ in EBCDIC format unless otherwise specified.
+
+ Value Size Description
+ ----- ---- -----------
+ (Z390) 0x0065 2 bytes Tag for this "extra" block type
+ Size 4 bytes Size for the following data block
+ Tag 4 bytes EBCDIC "Z390"
+ Length71 2 bytes Big Endian
+ Subcode71 2 bytes Enote type code
+ FMEPos 1 byte
+ Length72 2 bytes Big Endian
+ Subcode72 2 bytes Unit type code
+ Unit 1 byte Unit
+ Length73 2 bytes Big Endian
+ Subcode73 2 bytes Volume1 type code
+ FirstVol 1 byte Volume
+ Length74 2 bytes Big Endian
+ Subcode74 2 bytes FirstVol file sequence
+ FileSeq 2 bytes Sequence
+
+APPENDIX D - Language Encoding (EFS)
+------------------------------------
+
+The ZIP format has historically supported only the original IBM PC character
+encoding set, commonly referred to as IBM Code Page 437. This limits storing
+file name characters to only those within the original MS-DOS range of values
+and does not properly support file names in other character encodings, or
+languages. To address this limitation, this specification will support the
+following change.
+
+If general purpose bit 11 is unset, the file name and comment should conform
+to the original ZIP character encoding. If general purpose bit 11 is set, the
+filename and comment must support The Unicode Standard, Version 4.1.0 or
+greater using the character encoding form defined by the UTF-8 storage
+specification. The Unicode Standard is published by the The Unicode
+Consortium (www.unicode.org). UTF-8 encoded data stored within ZIP files
+is expected to not include a byte order mark (BOM).
+
+Applications may choose to supplement this file name storage through the use
+of the 0x0008 Extra Field. Storage for this optional field is currently
+undefined, however it will be used to allow storing extended information
+on source or target encoding that may further assist applications with file
+name, or file content encoding tasks. Please contact PKWARE with any
+requirements on how this field should be used.
+
+The 0x0008 Extra Field storage may be used with either setting for general
+purpose bit 11. Examples of the intended usage for this field is to store
+whether "modified-UTF-8" (JAVA) is used, or UTF-8-MAC. Similarly, other
+commonly used character encoding (code page) designations can be indicated
+through this field. Formalized values for use of the 0x0008 record remain
+undefined at this time. The definition for the layout of the 0x0008 field
+will be published when available. Use of the 0x0008 Extra Field provides
+for storing data within a ZIP file in an encoding other than IBM Code
+Page 437 or UTF-8.
+
+General purpose bit 11 will not imply any encoding of file content or
+password. Values defining character encoding for file content or
+password must be stored within the 0x0008 Extended Language Encoding
+Extra Field.
+
+Ed Gordon of the Info-ZIP group has defined a pair of "extra field" records
+that can be used to store UTF-8 file name and file comment fields. These
+records can be used for cases when the general purpose bit 11 method
+for storing UTF-8 data in the standard file name and comment fields is
+not desirable. A common case for this alternate method is if backward
+compatibility with older programs is required.
+
+Definitions for the record structure of these fields are included above
+in the section on 3rd party mappings for "extra field" records. These
+records are identified by Header ID's 0x6375 (Info-ZIP Unicode Comment
+Extra Field) and 0x7075 (Info-ZIP Unicode Path Extra Field).
+
+The choice of which storage method to use when writing a ZIP file is left
+to the implementation. Developers should expect that a ZIP file may
+contain either method and should provide support for reading data in
+either format. Use of general purpose bit 11 reduces storage requirements
+for file name data by not requiring additional "extra field" data for
+each file, but can result in older ZIP programs not being able to extract
+files. Use of the 0x6375 and 0x7075 records will result in a ZIP file
+that should always be readable by older ZIP programs, but requires more
+storage per file to write file name and/or file comment fields.
+
+
+
+
diff --git a/format_docs/pdb/ereader.txt b/format_docs/pdb/ereader.txt
new file mode 100644
index 0000000000..5770c37e65
--- /dev/null
+++ b/format_docs/pdb/ereader.txt
@@ -0,0 +1,309 @@
+About
+-----
+
+The eReader format has evolved and changed over time. Subsequently, there are
+multiple versions of the eReader format. There are also two different tools
+that can create eReader files. The official tools are Makebook and Dropbook.
+Dropbook is the newer official tool that has replaced Makebook. However,
+Makebook is still in wide use because it supports a wider range of platforms
+than Dropbook. Dropbook is a GUI application that only runs on Windows and
+Apple’s OS X.
+
+
+PDB Identiy
+-------
+
+PNRdPPrs
+
+
+202 and 132 headers
+-----------------------------------------
+
+Older files have a record 0 size of 202 and occasionally 116. Newer files have
+a record 0 size of 132. As of this writing the 202 files only support text and
+images. The image format in the 202 files is the same as the 132 files. The 132
+files support a number of additional features.
+
+
+Record 0, eReader header (202)
+------------------
+
+Note all values are in 2 byte increments. Like values are condensed into a
+range. The range can be borken into 2 byte sections which represent the actual
+stored values.
+
+bytes content comments
+
+0-2 Version Non-DRM books 2 and 4.
+2-8 Garbage
+8-10 Non-Text Offset Start of Non text area (images) will run to the
+ end of the section list.
+10-14 Unknown
+14-24 Garbage
+24-28 Unknown
+28-98 Garbage
+98-100 Unknown
+100-110 Garbage
+110-114 Unknown
+114-116 Garbage
+116-202 Unknown
+
+* Garbage: Intentially random values.
+
+
+Text Records (202)
+------------------
+
+Text starts with section 1 and continues until the section indicated by the
+Non-Text Offset. All text records are PalmDoc compressed.
+
+Each character in the compressed data is xored with 0xA5.
+
+A decompression example in sudo Python:
+
+for num in range(1, Non-Text Offset):
+ text += decompress_pamldoc(''.join([chr(ord(x) ^ 0xA5) for x in section_data(num)])).decode('cp1252', 'replace')
+
+
+Dropbook 132 files
+------------------
+
+The following sections apply to the newer Dropbook created files.
+
+
+Record 0, eReader header (132)
+----------------------------
+
+This is only for 132 byte header files created by Dropbook.
+
+bytes content comments
+
+0-2 compression Specifies compression and drm. 2 = palmdoc,
+ 10 = zlib. 260 and 272 = DRM
+2-6 unknown Value of 0 is used
+6-8 encoding Always 25152 (0x6240). All text must be
+ encoded as Latin-1 cp1252
+8-10 Number of small pages The number of small font pages. If page
+ index is not build in then 0.
+10-12 Number of large pages The number of large font pages. If page
+ index is not build in then 0.
+12-14 Non-Text record start The location of the first non text records.
+ record 1 to this value minus 1 are all text
+ records
+14-16 Number of chapters The number of chapter index records
+ contained in the file
+16-18 Number of small index The number of small font page index records
+ contained in the file
+18-20 Number of large index The number of large font page index records
+ contained in the file
+20-22 Number of images The number of images contained in the file
+22-24 Number of links The number of links contained in the file
+24-26 Metadata avaliable Is there a metadata record in the file?
+ 0 = None, 1 = There is a metadata record
+26-28 Unknown Value of 0 is used
+28-30 Number of Footnotes The number of footnote records in the file
+30-32 Number of Sidebars The number of sidebar records in the file
+32-34 Chapter index record start The location of chapter index records. If
+ there are no chapters use the value for the
+ Last data record.
+34-36 2560 Magic value that must be set to 2560
+36-38 Small page index start The location of small font page index
+ records. If page table is not built in use
+ the value for the Last data record.
+38-40 Large page index start The location of large font page index
+ records. If page table is not built in use
+ the value for the Last data record.
+40-42 Image data record start The location of the first image record. If
+ there are no images use the value for the
+ Last data record.
+42-44 Links record start The location of the first link index
+ record. If there are no links use the value
+ for the Last data record.
+44-46 Metadata record start The location of the metadata record. If
+ there is no metadata use the value for the
+ Last data record.
+46-48 Unknown Value of 0 is used
+48-50 Footnote record start The location of the first footnote record.
+ If there are no footnotes use the value for
+ the Last data record.
+50-52 Sidebar record start The location of the first sidebar record.
+ If there are no sidebars use the value for
+ the Last data record.
+52-54 Last data record The location of the last data record
+54-132 Unknown Value of 0 is used
+
+Note: All values are in 2 byte increments. All bytes in the table that have a
+range larger than 2 can be broken into 2 byte segments and have different
+values set for each grouping.
+
+
+Records Order
+-------------
+
+Though the order of this sections is described in eReader header,
+DropBook makes the following order:
+
+ 1. eReader Header
+ 2. Compressed text
+ 3. Small font page index
+ 4. Large font page index
+ 5. Chapter index
+ 6. Links index
+ 7. Images
+ 8. (Extrapolation: there should be one more record type here though it has
+ not yet been uncovered what it might be).
+ 9. Metadata
+ 10. Sidebar records
+ 11. Footnote records
+ 12. Text block size record
+ 13. "MeTaInFo\x00" word record
+
+
+Text Records
+------------
+
+All text records use cp1252 encoding (although eReader documents talk about
+UTF-8 as well). Their total compressed size is unknown however, anything below
+3560 Bytes is known to work. The text will be either zlib or palmdoc
+compressed. Use the compression value from the eReader header to determine
+which. All text utalizes the Palm Markup Language (PML) for formatting.
+
+Starting with DropBook 1.6.0 text is divided into 8KB (8192 bytes) blocks
+trimming the end to the closest space character and then being compressed.
+Earlier version of DropBook 1.5.2 tries to behave the same way, though
+sometimes it trims the block in unexpected place.
+
+
+Chapter Index Records
+---------------------
+
+Each chapter record corresponds to 1 chapter and points at the place in the
+book. Chapter record takes a form of 'offset name\x00' First 4 bytes are offset
+of the original pml file where the chapter index points to (offset of
+the \x|\X?|\C? tags). Then without a space goes a name of a chapter in chapter
+index. It should contain only text, all formatting tags should be removed.
+\U and \a tags are not permitted in chapter name. To maintain sub-chapters
+4*n spaces (\x20) are added to the beginning of the name, where "n" is level of
+chapter: 0 for \x tag and N for \CN="" and \XN tags. And then an ending
+\x00 symbol.
+
+
+Image Records
+-------------
+
+Image records must be smaller than 65505 Bytes. They must also be 8bit PNG
+images.
+
+An image record takes the form 'PNG name\x00... image_data'
+
+bytes content comments
+
+0-4 PNG There must be a space after PNG.
+4-36 image name. The image name must be 32 exactly 32 Bytes long. Pad
+ the right side of the name with \x00 characters for
+ names shorter than 32 characters.
+36-58 Unknown
+58-60 width Width of an image
+60-62 height Height of an image
+62-? The image data raw image data in 8 bit PNG format
+
+Note: DropBooks seems to change something in png raw data. Like reencoding or
+something, but plain insertion of png image there still works.
+
+
+Links Records
+-------------
+
+Links records are constructed the same way as chapter ones. Each link anchor
+record corresponds to 1 link anchor and points at the place in the book. Link
+record takes a form of 'offset name\x00' First 4 bytes are offset of the
+original pml file where the link anchor points to (offset of the \Q tag). Then
+without a space goes a name of a link anchor. It should contain only text, all
+formatting tags should be removed. \U and \a tags are not permitted in link
+anchor name. And then an ending \x00 symbol.
+
+
+Footnote Records
+----------------
+
+The first footnote record is a \x00 separated list of footnote ids. All
+subsequent footnote records are the footnote text corresponding to the id's
+position in the list. Footnote text is compressed in the same manner as normal
+text records
+
+E.G.
+
+footnote section 1 = 'notice1\x00notice2\x00notice3\x00'
+footnote section 2 = 'Text for notice 1'
+footnote section 3 = 'Text for notice 2'
+footnote section 4 = 'Text for notice 3'
+
+Starting with Dropbook 1.5.2 first record looks a bit different. It is sequence
+of \x00\x01 then 1 byte of footnote id length, then footnote id then \x00.
+
+E.G.
+
+footnote section 1 = '\x00\x01\x07notice1\x00\x00\x01\x0Afootnote10\x00'
+
+
+Sidebar Records
+---------------
+
+The first sidebar record is a \x00 separated list of sidebar ids. All
+subsequent sidebar records are the sidebar text corresponding to the id's
+position in the list. Sidebar text is compressed in the same manner as normal
+text records
+
+E.G.
+
+sidebar section 1 = 'notice1\x00notice2\x00notice3\x00'
+sidebar section 2 = 'Text for notice 1'
+sidebar section 3 = 'Text for notice 2'
+sidebar section 4 = 'Text for notice 3'
+
+Starting with Dropbook 1.5.2 first record looks a bit different. It is sequence
+of \x00\x01 then 1 byte of sidebar's id length, then sidebar's id then \x00.
+
+E.G.
+
+sidebar section 1 = '\x00\x01\x07notice1\x00\x00\x01\x09sidebar10\x00'
+
+
+Metadata Record
+---------------
+
+\x00 separated list of string.
+
+Metadata takes the form:
+
+ title\x00
+ author\x00
+ copyright\x00
+ publisher\x00
+ isbn\x00
+
+E.G.
+
+Gibraltar Earth\x00Michael McCollum\x001999\x00Sci Fi Arizona\x001929381255\x00
+
+The metdata record is always followed by a record which contains 'MeTaInFo\x00'
+
+Note: Starting with DropBook 1.5.2 'MeTaInFo\x00' is not following Metadata
+Record. It is a separate record that ends the file and there are some more
+records between Metadata record and 'MeTaInFo\x00' record.
+
+
+Text Sizes Record
+-----------------
+
+There is a special record that contains the initial size of all text blocks
+before compression. It is just a sequence of 2-byte blocks which are containing
+the sizes.
+
+E.G.
+
+\x1F\xFB\x20\x00\x20\x00\x1F\xFE\x1F\xFD\x09\x46
+
+Note: By this we can judge that theoretical maximum of initial block size is
+65535 bytes.
+
diff --git a/format_docs/pdb/mbp.txt b/format_docs/pdb/mbp.txt
new file mode 100644
index 0000000000..61e1d2d9ee
--- /dev/null
+++ b/format_docs/pdb/mbp.txt
@@ -0,0 +1,414 @@
+// BEGINING OF FILE
+// NOTES:
+// 1* Numeric data stored as big endian, 32 bits.
+// 2* Data padded to 16 bits limits. (Sometimes to 32 bits limits?)
+// 3* Text stored seems to be an 8 bit encoding padded to 16 bits
+// (may be "ISO-8859-1"?, or may be just a local machine character set?)
+// 4* I initially used the term "MARK" where I should have used "HIGHLIGTH",
+// bear that in mind (it was a bad name election when I started reversing)
+
+<0x 31 bytes = book_title_PAR + 0x00 PAD if (book_title_PAR < 31) >
+<0x 00>
+<0x 00 00 00 00>
+...4
+...4
+<0x 00 00 00 00>
+<0x 00 00 00 00>
+<0x 00 00 00 00>
+<0x 00 00 00 00>
+BPAR
+MOBI
+<0x 4 bytes = Next free pointer identifier>
+ // Note: pointer identifiers aren't always consecutive,
+ // so this number is usually bigger than de # of index entries
+<0x 00 00>
+<0x 4 bytes = Number of index entries>
+<0x 4 bytes = Position of BPAR>
+<0x 00 00 00 00> // BPAR pointer identifier = 0x0
+
+
+// INDEXES:
+// Order of Indexes: from the beginning of this MBP file,
+// forward to the end of the file.
+// Nevertheless, see these comments for order relative to:
+// "BEGINING OF USER DATA": order of Data marks.
+// "FINAL GROUP OF MARKS": order of final marks.
+[for each {NOTE,MARK,CORRECTION,DRAWING,BOOKMARK,
+ AUTHOR,TITLE,CATEGORY,GENRE,ABSTRACT,COVER,PUBLISHER,
+ ...}
+ || "last DATA"]
+// Note: Pointer identifiers to DATA's assigned so the number
+// shrinks as the table grows down.
+[if NOTE || CORRECTION]
+ <0x 4 bytes = Position of DATA....EBVS>
+ <0x 4 bytes = Pointer identifier, used by BKMK blocks>
+[fi NOTE || CORRECTION]
+<0x 4 bytes = Position of DATA>
+<0x 4 bytes = Pointer identifier, used by BKMK blocks>
+[if NOTE || CORRECTION]
+ <0x 4 bytes = Position of DATA>
+ <0x 4 bytes = Pointer identifier, used by BKMK blocks>
+[fi NOTE || CORRECTION]
+[if MARK || DRAWING || BOOKMARK]
+ <0x 4 bytes = Position of DATA....EBVS>
+ <0x 4 bytes = Pointer identifier, used by BKMK blocks>
+[fi MARK || DRAWING || BOOKMARK]
+[if AUTHOR || TITLE || CATEGORY || GENRE || ABSTRACT || COVER || PUBLISHER]
+ <0x 4 bytes = Position of [AUTH || TITL || CATE || GENR || ABST || COVE || PUBL] >
+ <0x 4 bytes = Pointer identifier>
+[fi AUTHOR || TITLE || CATEGORY || GENRE || ABSTRACT || COVER || PUBLISHER]
+[if last DATA] // there's always a last piece of DATA (not user data?)
+ <0x 4 bytes = Position of last DATA>
+ <0x 4 bytes = Pointer identifier> // usually <0x 00 00 00 01>
+[fi last DATA]
+[next {NOTE,MARK,CORRECTION,DRAWING,BOOKMARK,
+ AUTHOR,TITLE,CATEGORY,GENRE,ABSTRACT,COVER,PUBLISHER,
+ ...}
+ || "last DATA"]
+
+
+[for each {NOTE,MARK,CORRECTION,DRAWING}]
+<0x 4 bytes = Position of BKMK>
+<0x 4 bytes = Pointer identifier>
+ // Note: pointer identifiers for BKMK's are usually the minor
+ // of all the identifiers associated to an annotation. All
+ // other DATA references in INDEXES table associated to this
+ // BKMK, have bigger pointer identifiers.
+ // Note: Pointer identifiers to BKMK's assigned so the number
+ // grows as the table grows down.
+[next {NOTE,MARK,CORRECTION,DRAWING}]
+
+
+<0x 2 bytes random PAD>
+BPAR
+<0x 4 bytes = size of BPAR block>
+<0x FF FF FF FF>
+...4 <-- 'position of last read' related
+...4 <-- 'position of last read' related
+...4
+<0x FF FF FF FF>
+...4
+...4
+...4 <-- 'position of last read' related
+...(rest of size of BPAR block, if bigger than 0x20)
+[if (size of BPAR block) mod 32 != 0]
+<0x FF FF FF FF>
+[fi]
+
+// BEGINING OF USER DATA:
+// Order of {NOTE,MARK,CORRECTION,DRAWING} :
+// starts with user data at the end of the file,
+// going backwards to the begining of the file:
+//--------------------------------------------------------------------
+[for each {NOTE,MARK,CORRECTION,DRAWING}]
+//-------------------------------
+[if NOTE]
+DATA
+<0x 4 bytes = size of DATA block>
+[if EBAR] // this block can appear, or not... ???
+ EBAR
+ ...various {4 x byte} ???
+[fi EBAR]
+EBVS
+<0x 00 00 00 03> ???
+<0x 4 bytes = IDENTIFIER> ???
+[<0x 00 00 00 01>, or nothing at all] ???
+<0x 00 00 00 08>
+<0x FF FF FF FF>
+<0x 00 00 00 00>
+<0x 00 00 00 10>
+...(rest of size of DATA block)
+<0x FD EA = PAD? ()>
+DATA
+<0x 4 bytes = size of >
+
+[if (size of ) mod 4 !=0]
+<0x random PAD until (size of ) mod 4 ==0>
+[fi]
+DATA
+<0x 4 bytes = size of >
+
+[if (size of ) mod 4 !=0]
+<0x random PAD until (size of ) mod 4 ==0>
+[fi]
+[fi NOTE]
+//-------------------------------
+[if MARK || BOOKMARK]
+DATA
+<0x 4 bytes = size of >
+
+[if (size of ) mod 4 !=0]
+<0x random PAD until (size of ) mod 4 ==0>
+[fi]
+DATA
+<0x 4 bytes = size of DATA block>
+[if EBAR] // this block can appear, or not... ???
+ EBAR
+ ...various {4 x byte} ???
+[fi EBAR]
+EBVS
+<0x 00 00 00 03> ???
+<0x 4 bytes = IDENTIFIER> ???
+[<0x 00 00 00 01>, or nothing at all] ???
+<0x 00 00 00 08>
+<0x FF FF FF FF>
+<0x 00 00 00 00>
+<0x 00 00 00 10>
+...(rest of size of DATA block)
+<0x FD EA = PAD? ()>
+[fi MARK || BOOKMARK]
+//-------------------------------
+[if CORRECTION]
+DATA
+<0x 4 bytes = size of DATA block>
+[if EBAR] // this block can appear, or not... ???
+ EBAR
+ ...various {4 x byte} ???
+[fi EBAR]
+EBVS
+<0x 00 00 00 03> ???
+<0x 4 bytes = IDENTIFIER> ???
+[<0x 00 00 00 01>, or nothing at all] ???
+<0x 00 00 00 08>
+<0x FF FF FF FF>
+<0x 00 00 00 00>
+<0x 00 00 00 10>
+...(rest of size of DATA block)
+<0x FD EA = PAD? ()>
+DATA
+<0x 4 bytes = size of >
+
+[if (size of ) mod 4 !=0]
+<0x random PAD until (size of ) mod 4 ==0>
+[fi]
+DATA
+<0x 4 bytes = size of >
+
+[if (size of ) mod 4 !=0]
+<0x random PAD until (size of ) mod 4 ==0>
+[fi]
+[fi CORRECTION]
+//-------------------------------
+[if DRAWING]
+DATA
+<0x 4 bytes = size of raw data>
+ADQM
+ // NOTE: bakground color is stored in corresponding BKMK.
+ [begin DRAWING format]
+ ...4 = <0x 00 00 00 01> ???
+ <0x 4 bytes = X POSITION OF UPPER LEFT CORNER??? >
+ <0x 4 bytes = Y POSITION OF UPPER LEFT CORNER??? >
+ <0x 4 bytes = X SIZE in pixels >
+ <0x 4 bytes = Y SIZE in pixels >
+ ...4 = <0x 00 00 00 00> ???
+ <0x 4 bytes = number of STROKES>
+ [if "number of STROKES" == 0]
+ <0x 00 00 00 00>
+ [end DRAWING format]
+ [fi]
+ [for each STROKE]
+ <0x 00 00 00 01> ???
+ <0x 4 bytes> =
+ Stroke's beginning position in list of coordinates.
+ <0x 4 bytes> =
+ Stroke's ending position in list of coordinates.
+ <0x 00 RR GG BB> = RRGGBB color of stroke.
+ [next STROKE]
+ <0x 4 bytes> = number of coordinate pairs in array of coordinates.
+ // NOTE: each stroke is formed out of at least three
+ // coordinate pairs: begin, {next point}(1-n), end point.
+ [for each COORDINATE]
+ <0x 4 bytes> = X coordinate
+ <0x 4 bytes> = Y coordinate
+ [next COORDINATE]
+ [end DRAWING format]
+[if (size of ) mod 4 !=0]
+<0x random PAD until (size of ) mod 4 ==0>
+[fi]
+DATA
+<0x 4 bytes = size of >
+
+[if (size of ) mod 4 !=0]
+<0x random PAD until (size of ) mod 4 ==0>
+[fi]
+DATA
+<0x 4 bytes = size of DATA block>
+[if EBAR] // this block can appear, or not... ???
+ EBAR
+ ...various {4 x byte} ???
+[fi EBAR]
+EBVS
+<0x 00 00 00 03>
+<0x 4 bytes = IDENTIFIER>
+[<0x 00 00 00 01>, or nothing at all] ???
+<0x 00 00 00 08>
+<0x FF FF FF FF>
+<0x 00 00 00 00>
+<0x 00 00 00 10>
+...(size of DATA block - 30)
+<0x FD EA = PAD? ()>
+[fi DRAWING]
+//-------------------------------
+[next {NOTE,MARK,CORRECTION,DRAWING}]
+
+// AUTHOR (if any)
+//--------------------------------------------------------------------
+[if AUTHOR]
+AUTH
+<0x 4 bytes = size of AUTHOR block>
+
+[fi AUTHOR]
+//--------------------------------------------------------------------
+// TITLE (if any)
+//--------------------------------------------------------------------
+[if TITLE]
+TITL
+<0x 4 bytes = size of TITLE block>
+
+[fi TITLE]
+//--------------------------------------------------------------------
+// GENRE (if any)
+//--------------------------------------------------------------------
+[if GENRE]
+GENR
+<0x 4 bytes = size of GENRE block>
+
+[fi GENRE]
+//--------------------------------------------------------------------
+// ABSTRACT (if any)
+//--------------------------------------------------------------------
+[if ABSTRACT]
+ABST
+<0x 4 bytes = size of ABSTRACT block>
+
+[fi ABSTRACT]
+//--------------------------------------------------------------------
+
+// FINAL DATA
+// Note: 'FINAL DATA' can occur anytime between these marks:
+// AUTHOR,TITLE,CATEGORY,GENRE,ABSTRACT,COVER,PUBLISHER,...
+//--------------------------------------------------------------------
+DATA
+<0x 4 bytes = size of EBVS block>
+[if EBAR] // this block can appear, or not... ???
+ EBAR
+ ...various {4 x byte} ???
+[fi EBAR]
+EBVS
+<0x 00 00 00 03> || <0x 00 00 00 04>
+<0x 4 bytes || 8 bytes = IDENTIFIER>
+<0x 00 00 00 08>
+<0x FF FF FF FF>
+<0x 00 00 00 00>
+<0x 00 00 00 10>
+...(size of EBVS block - 30) :
+ ...4 <-- 'position of last read' related
+ ...various {4 x byte} ???
+ ...4 <-- 'position of last read' related
+ ...4
+ ...4
+ ...4
+<0x FD EA = PAD? ()>
+//--------------------------------------------------------------------
+
+// CATEGORY (if any)
+//--------------------------------------------------------------------
+[if CATEGORY]
+CATE
+<0x 4 bytes = size of CATEGORY block>
+
+[fi CATEGORY]
+//--------------------------------------------------------------------
+// COVER (if any)
+//--------------------------------------------------------------------
+[if COVER]
+COVE
+<0x 4 bytes = size of COVER block>
+
+[fi COVER]
+//--------------------------------------------------------------------
+// PUBLISHER (if any)
+//--------------------------------------------------------------------
+[if PUBLISHER]
+PUBL
+<0x 4 bytes = size of PUBLISHER block>
+
+[fi PUBLISHER]
+//--------------------------------------------------------------------
+
+
+// FINAL GROUP OF MARKS
+// Order of {NOTE,MARK,CORRECTION} :
+// starts with user data at the begining of the file,
+// going forwards to the end:
+//--------------------------------------------------------------------
+[for each {NOTE,MARK,CORRECTION,DRAWING,BOOKMARK}]
+BKMK
+<0x 4 bytes = size of BKMK>
+<0x 4 bytes = TEXT position of the beginning of {NOTE,MARK,CORRECTION,DRAWING,BOOKMARK}>
+//-------------------------------
+[if DRAWING]
+<0x FF FF FF FF>
+[else]
+<0x 4 bytes = TEXT position of the end of {NOTE,MARK,CORRECTION,BOOKMARK}>
+[fi DRAWING]
+...4
+...4
+//-------------------------------
+[if NOTE]
+ <0x xx xx xx (20)?>, xxxxxx=>RRGGBB color ???
+ <0x 00 00 00 02>
+[fi NOTE]
+[if MARK]
+ <0x xx xx xx (0F/00)??>, xxxxxx=>RRGGBB color ???
+ <0x 00 00 00 04>
+[fi MARK]
+[if CORRECTION]
+ <0x xx xx xx (6F)?>, xxxxxx=>RRGGBB color ???
+ <0x 00 00 00 02>
+[fi CORRECTION]
+[if DRAWING]
+ <0x xx xx xx (0F)?>, xxxxxx=>RRGGBB DRAWING's background color.
+ <0x 00 00 00 08>
+[fi DRAWING]
+[if BOOKMARK]
+ <0x xx xx xx 00>
+ <0x 00 00 00 01>
+[fi BOOKMARK]
+ // this one is a strange type of mark, of yet not identified use:
+ [if UNKNOWN_TYPE_YET_1]
+ <0x xx xx xx 00>
+ <0x 00 00 40 00>
+ [fi UNKNOWN_TYPE_YET_1]
+
+//-------------------------------
+[if BOOKMARK || (NOTE "without stored marked text")]
+ <0x FF FF FF FF>
+[else]
+ <0x 4 bytes = DATA pointer in INDEXES>
+[fi BOOKMARK]
+[if DRAWING || MARK]
+ <0x FF FF FF FF>
+[else]
+ <0x 4 bytes = DATA pointer in INDEXES>
+[fi]
+<0x 4 bytes = DATA pointer in INDEXES>
+[if DRAWING]
+ <0x 4 bytes = DATA pointer in INDEXES>
+[else]
+ <0x FF FF FF FF>
+[fi]
+//-------------------------------
+<0x FF FF FF FF>
+<0x FF FF FF FF>
+[next {NOTE,MARK,CORRECTION,DRAWING,BOOKMARK}]
+//--------------------------------------------------------------------
+
+[if length % 32 bit != 0] ???
+ <0x FF FF FF FF>
+[fi]
+
+// END OF FILE
+
+// by idleloop@yahoo.com, v0.2.e, 12/2009
+// http://www.angelfire.com/ego2/idleloop
\ No newline at end of file
diff --git a/format_docs/pdb/mobi.txt b/format_docs/pdb/mobi.txt
new file mode 100644
index 0000000000..e378e1622b
--- /dev/null
+++ b/format_docs/pdb/mobi.txt
@@ -0,0 +1,341 @@
+from (http://wiki.mobileread.com/wiki/MOBI)
+
+About
+-----
+
+MOBI is the format used by the the MobiPocket Reader. It may have a .mobi
+extension or it may have a .prc extension. The extension can be changed by the
+user to either of the accepted forms. In either case it may be DRM protected or
+non-DRM. The .prc extension is used because the PalmOS doesn't support any file
+extensions except .prc or .pdb. Note that Mobipocket prohibits their DRM format
+to be used on dedicated eBook readers that support other DRM formats.
+
+
+Description
+-----------
+
+MOBI format was originally an extension of the PalmDOC format by adding
+certain HTML like tags to the data. Many MOBI formatted documents still use
+this form. However there is also a high compression version of this file format
+that compresses data to a larger degree in a proprietary manner. There are some
+third party programs that can read the eBooks in the original MOBI format but
+there are only a few third party program that can read the eBooks in the new
+compressed form. The higher compression mode is using a huffman coding scheme
+that has been called the Huff/cdic algorithm.
+
+From time to time features have been added to the format so new files may have
+problems if you try and read them with a down level reader. Currently the
+source files follow the guidelines in the Open eBook format.
+
+Note that AZW for the Amazon Kindle is the same format as MOBI except that it
+uses a slightly different DRM scheme.
+
+
+Format
+------
+
+Like PalmDOC, the Mobipocket file format is that of a standard Palm Database
+Format file. The header of that format includes the name of the database
+(usually the book title and sometimes a portion of the authors name) which is
+up to 31 bytes of data. The files are identified as Creator ID of MOBI and a
+Type of BOOK.
+
+
+PalmDOC Header
+--------------
+
+The first record in the Palm Database Format gives more information about the
+Mobipocket file. The first 16 bytes are almost identical to the first sixteen
+bytes of a PalmDOC format file.
+
+bytes content comments
+2 Compression 1 == no compression, 2 = PalmDOC compression,
+ 17480 = HUFF/CDIC compression.
+2 Unused Always zero
+4 text length Uncompressed length of the entire text of the book
+2 record count Number of PDB records used for the text of the book.
+2 record size Maximum size of each record containing text, always
+ 4096.
+4 Current Position Current reading position, as an offset into the
+ uncompressed text
+
+There are two differences from a Palm DOC file. There's an additional
+compression type (17480), and the Current Position bytes are used for a
+different purpose:
+
+bytes content comments
+2 Encryption Type 0 == no encryption, 1 = Old Mobipocket Encryption,
+ 2 = Mobipocket Encryption.
+2 Unknown Usually zero
+
+The old Mobipocket Encryption scheme only allows the file to be registered
+with one PID, unlike the current encryption scheme that allows multiple PIDs to
+be used in a single file. Unless specifically mentioned, all the encryption
+information on this page refers to the current scheme.
+
+
+MOBI Header
+-----------
+
+Most Mobipocket file also have a MOBI header in record 0 that follows these
+16 bytes, and newer formats also have an EXTH header following the MOBI header,
+again all in record 0 of the PDB file format.
+
+The MOBI header is of variable length and is not documented. Some fields have
+been tentatively identified as follows:
+
+offset bytes content comments
+16 4 identifier The characters M O B I
+20 4 header length The length of the MOBI header, including
+ the previous 4 bytes
+24 4 Mobi type The kind of Mobipocket file this is
+ 2 Mobipocket Book
+ 3 PalmDoc Book
+ 4 Audio
+ 257 News
+ 258 News_Feed
+ 259 News_Magazine
+ 513 PICS
+ 514 WORD
+ 515 XLS
+ 516 PPT
+ 517 TEXT
+ 518 HTML
+28 4 text Encoding 1252 = CP1252 (WinLatin1); 65001 = UTF-8
+32 4 Unique-ID Some kind of unique ID number (random?)
+36 4 Generator version Potentially the version of the
+ Mobipocket-generation tool. Always >=
+ the value of the "format version" field
+ and <= the version of mobigen used to
+ produce the file.
+40 40 Reserved All 0xFF. In case of a dictionary, or
+ some newer file formats, a few bytes are
+ used from this range of 40 0xFFs
+80 4 First Non-book index? First record number (starting with 0)
+ that's not the book's text
+84 4 Full Name Offset Offset in record 0 (not from start of
+ file) of the full name of the book
+88 4 Full Name Length Length in bytes of the full name of the
+ book
+92 4 Language Book language code. Low byte is main
+ language 09= English, next byte is
+ dialect, 08 = British, 04 = US
+96 4 Input Language Input language for a dictionary
+100 4 Output Language Output language for a dictionary
+104 4 Format version Potentially the version of the
+ Mobipocket format used in this file.
+ Always >= 1 and <= the value of the
+ "generator version" field.
+108 4 First Image record First record number (starting with 0)
+ that contains an image. Image records
+ should be sequential. If there are
+ no images this will be 0xffffffff.
+112 4 HUFF record Record containing Huff information
+ used in HUFF/CDIC decompression.
+116 4 HUFF count Number of Huff records.
+122 4 DATP record Unknown: Records starts with DATP.
+124 4 DATP count Number of DATP records.
+128 4 EXTH flags Bitfield. if bit 6, 0x40 is set, then
+ there's an EXTH record
+The following records are only present if the mobi header is long enough.
+132 36 ? 32 unknown bytes, if MOBI is long enough
+168 4 DRM Offset Offset to DRM key info in DRMed files.
+ 0xFFFFFFFF if no DRM
+172 4 DRM Count Number of entries in DRM info.
+174 4 DRM Size Number of bytes in DRM info.
+176 4 DRM Flags Some flags concerning the DRM info.
+180 6 ?
+186 2 Last Image record Possible vaule with the last image
+ record. If there are no images in the
+ book this will be 0xffff.
+188 4 ?
+192 4 FCIS record Unknown. Record starts with FCIS.
+196 4 ?
+200 4 FLIS record Unknown. Records starts with FLIS.
+204 ? ? Bytes to the end of the MOBI header,
+ including the following if the header
+ length >= 228. ( 244 from start of
+ record)
+242 2 Extra Data Flags A set of binary flags, some of which
+ indicate extra data at the end of each
+ text block. This only seems to be valid
+ for Mobipocket format version 5 and 6
+ (and higher?), when the header length
+ is 228 (0xE4) or 232 (0xE8).
+
+
+EXTH Header
+-----------
+
+If the MOBI header indicates that there's an EXTH header, it follows immediately
+after the MOBI header. since the MOBI header is of variable length, this isn't
+at any fixed offset in record 0. Note that some readers will ignore any EXTH
+header info if the mobipocket version number specified in the MOBI header is 2
+or less (perhaps 3 or less).
+
+The EXTH header is also undocumented, so some of this is guesswork.
+
+bytes content comments
+4 identifier the characters E X T H
+4 header length the length of the EXTH header, including the previous 4 bytes
+4 record Count The number of records in the EXTH header. the rest of the EXTH header consists of repeated EXTH records to the end of the EXTH length.
+ EXTH record start Repeat until done.
+4 record type Exth Record type. Just a number identifying what's stored in the record
+4 record length length of EXTH record = L , including the 8 bytes in the type and length fields
+L-8 record data Data.
+ EXTH record end Repeat until done.
+
+There are lots of different EXTH Records types. Ones found so far in Mobipocket
+files are listed here, with possible meanings. Hopefully the table will be
+filled in as more information comes to light.
+
+record type usual length name comments
+1 drm_server_id
+2 drm_commerce_id
+3 drm_ebookbase_book_id
+100 author
+101 publisher
+102 imprint
+103 description
+104 isbn
+105 subject
+106 publishingdate
+107 review
+108 contributor
+109 rights
+110 subjectcode
+111 type
+112 source
+113 asin
+114 versionnumber
+115 sample
+116 startreading
+118 retail price (as text)
+119 retail price currency (as text)
+201 coveroffset
+202 thumboffset
+203 hasfakecover
+204 204 Unknown
+205 205 Unknown
+206 206 Unknown
+207 207 Unknown
+208 208 Unknown
+300 300 Unknown
+401 clippinglimit
+402 publisherlimit
+403 403 Unknown
+404 404 ttsflag
+501 4 cdetype PDOC - Personal Doc;
+ EBOK - ebook;
+502 lastupdatetime
+503 updatedtitle
+
+And now, at the end of Record 0 of the PDB file format, we usually get the full
+file name, the offset of which is given in the MOBI header.
+
+
+Variable-width integers
+-----------------------
+
+Some parts of the Mobipocket format encode data as variable-width integers.
+These integers are represented big-endian with 7 bits per byte in bits 1-7. They
+may be either forward-encoded, in which case only the LSB has bit 8 set, or
+backward-encoded, in which case only the MSB has bit 8 set. For example, the
+number 0x11111 would be represented forward-encoded as:
+
+ 0x04 0x22 0x91
+
+And backward-encoded as:
+
+ 0x84 0x22 0x11
+
+
+Trailing entries
+----------------
+
+The Extra Data Flags field of the MOBI header indicates which, if any, trailing
+entries are appended to the end of each text record. Each set bit in the field
+indicates a trailing entry. The entries appear to occur in bit-order; e.g.,
+trailing entry 1 immediately follows the text content and entry 16 occurs at
+the very end of the record. The effect and exact details of most of these
+entries is unknown. The trailing entries indicated by bits 2-16 appear to
+follow a common format. That format is:
+
+
+
+Where is the size of the entire trailing entry (including the size of
+) as a backward-encoded Mobipocket variable-width integer.
+
+Only a few bits have been identified
+
+bit Data at end of records
+0x0001 Multi-byte character overlaps
+0x0002 Some data to help with indexing
+0x0004 Some data about uncrossable breaks
+
+
+Multibyte character overlap
+---------------------------
+
+When bit 1 of the Extra Data Flags field is set, each record is followed by a
+trailing entry containing any extra bytes necessary to complete a multibyte
+character which crosses the record boundary. The bytes do not participate in
+compression regardless which compression scheme is used for the file. However,
+unlike the trailing data bytes, the multibytes (including the count byte) do
+get included in any encryption. The overlapping bytes then re-appear as normal
+content at the beginning of the following record. The trailing entry ends with
+a byte containing a count of the overlapping bytes plus additional flags.
+
+offset bytes content comments
+0 0-3 N terminal bytes
+ of a multibyte
+ character
+N 1 Size & flags bits 1-2 encode N, use of bits 3-8 is unknown
+
+
+PalmDOC Compression
+-------------------
+
+PalmDOC uses LZ77 compression techniques. DOC files can contain only compressed
+text. The format does not allow for any text formatting. This keeps files small,
+in keeping with the Palm philosophy. However, extensions to the format can use
+tags, such as HTML or PML, to include formatting within text. These extensions
+to PalmDoc are not interchangeable and are the basis for most eBook Reader
+formats on Palm devices.
+
+LZ77 algorithms achieve compression by replacing portions of the data with
+references to matching data that has already passed through both encoder and
+decoder. A match is encoded by a pair of numbers called a length-distance pair,
+which is equivalent to the statement "each of the next length characters is
+equal to the character exactly distance characters behind it in the uncompressed
+stream." (The "distance" is sometimes called the "offset" instead.)
+
+In the PalmDoc format, a length-distance pair is always encoded by a two-byte
+sequence. Of the 16 bits that make up these two bytes, 11 bits go to encoding
+the distance, 3 go to encoding the length, and the remaining two are used to
+make sure the decoder can identify the first byte as the beginning of such a
+two-byte sequence. The exact alforithm needed to decode the compressed text can
+be found on the PalmDOC page.
+
+PalmDOC data is always divided into 4096 byte blocks and the blocks are acted
+upon independently.
+
+PalmDOC does have support for bookmarks. These pointers are named and refer to
+an offset location in a file. If the file is edited these locations may no
+longer refer to the correct locations. Some reading programs allow the user to
+enter or edit these bookmarks while others treat them as a TOC. Some reading
+programs may ignore them entirely. They are stored at the end of the file itself
+so the full file needs to be scanned when loaded to find them.
+
+
+MBP
+---
+
+This is the extension used on a side file (auxiliary) for MOBI formatted eBooks.
+It is used to store metadata used by the library software and also to store
+user entered data like bookmarks, annotations, last read position. This file is
+created automatically by the reader program when the eBook is first opened and
+has a .mbp extension. The Library management software in MobiPocket uses this
+file to get information displayed in the library window such as title and author
+so that it won't have to open the larger eBook file.
+
diff --git a/format_docs/pdb/palmdoc.txt b/format_docs/pdb/palmdoc.txt
new file mode 100644
index 0000000000..0df62ae2e2
--- /dev/null
+++ b/format_docs/pdb/palmdoc.txt
@@ -0,0 +1,25 @@
+PalmDoc Format
+--------------
+
+The format is that of a standard Palm Database Format file. The header of that
+format includes the name of the database (usually the book title and sometimes
+a portion of the authors name) which is up to 31 bytes of data. This string of
+characters is terminated with a 0 in the C style. The files are identified as
+Creator ID of REAd and a Type of TEXt.
+
+
+Record 0
+--------
+
+The first record in the Palm Database Format gives more information about the
+PalmDOC file, and contains 16 bytes.
+
+bytes content comments
+
+2 Compression 1 == no compression, 2 = PalmDOC compression (see below)
+2 Unused Always zero
+4 text length Uncompressed length of the entire text of the book
+2 record count Number of PDB records used for the text of the book.
+2 record size Maximum size of each record containing text, always 4096
+4 Current Position Current reading position, as an offset into the uncompressed text
+
diff --git a/format_docs/pdb/pdb_format.txt b/format_docs/pdb/pdb_format.txt
new file mode 100644
index 0000000000..e6837ac2ad
--- /dev/null
+++ b/format_docs/pdb/pdb_format.txt
@@ -0,0 +1,104 @@
+Format
+------
+
+A PDB file can be borken into multiple parts. The header, record 0 and data.
+values stored within the various parts are big-endian byte order. The data
+part is is broken down into multiple sections. The section count and offsets
+are referened in the PDB header. Sections can be no more than 65505 bytes in
+length.
+
+
+Layout
+------
+
+PDB files take the format: DB header followed by the record 0 which has
+contained format specific iformation followed by data.
+
+ DB Header
+0 Record 0
+.
+. Data (borken down into sections)
+.
+
+
+Palm Database Header Format
+
+bytes content comments
+
+32 name database name. This name is 0 terminated in the
+ field and will be used as the file name on a
+ computer. For eBooks this usually contains the
+ title and may have the author depending on the
+ length available.
+
+2 attributes bit field.
+ 0x0002 Read-Only
+ 0x0004 Dirty AppInfoArea
+ 0x0008 Backup this database (i.e. no conduit exists)
+ 0x0010 (16 decimal) Okay to install newer over
+ existing copy, if present on PalmPilot
+ 0x0020 (32 decimal) Force the PalmPilot to reset
+ after this database is installed
+ 0x0040 (64 decimal) Don't allow copy of file to be
+ beamed to other Pilot.
+
+2 version file version
+
+4 creation date No. of seconds since start of January 1, 1904.
+
+4 modification date No. of seconds since start of January 1, 1904.
+
+4 last backup date No. of seconds since start of January 1, 1904.
+
+4 modificationNumber
+
+4 appInfoID offset to start of Application Info (if present)
+ or null
+
+4 sortInfoID offset to start of Sort Info (if present) or null
+
+4 type See above table. (For Applications this data will
+ be 'appl')
+
+4 creator See above table. This program will be launched if
+ the file is tapped
+
+4 uniqueIDseed used internally to identify record
+
+4 nextRecordListID Only used when in-memory on Palm OS. Always set to
+ zero in stored files.
+
+2 number of Records number of records in the file - N
+
+8N record Info List
+
+ start of record
+ info entry Repeat N times to end of record info entry
+
+4 record Data Offset the offset from the start of the PDB of this record
+
+1 record Attributes bit field. The least significant four bits are used
+ to represent the category values. These are the
+ categories used to split the databases for viewing
+ on the screen. A few of the 16 categories are
+ pre-defined but the user can add their own. There
+ is an undefined category for use if the user or
+ programmer hasn't set this.
+ 0x10 (16 decimal) Secret record bit.
+ 0x20 (32 decimal) Record in use (busy bit).
+ 0x40 (64 decimal) Dirty record bit.
+ 0x80 (128, unsigned decimal) Delete record on
+ next HotSync.
+
+3 UniqueID The unique ID for this record. Often just a
+ sequential count from 0
+
+ end of record
+ info entry
+
+2? Gap to data traditionally 2 zero bytes to Info or raw data
+
+? Records The actual data in the file. AppInfoArea (if
+ present), SortInfoArea (if present) and then
+ records sequentially
+
diff --git a/format_docs/pdb/pdb_types.txt b/format_docs/pdb/pdb_types.txt
new file mode 100644
index 0000000000..5d6d39c897
--- /dev/null
+++ b/format_docs/pdb/pdb_types.txt
@@ -0,0 +1,34 @@
+Palm Database File Code
+-----------------------
+
+Reader Type Code
+
+Adobe Reader .pdfADBE
+PalmDOC TEXtREAd
+BDicty BVokBDIC
+DB (Database program) DB99DBOS
+eReader PNRdPPrs
+eReader DataPPrs
+FireViewer (ImageViewer) vIMGView
+HanDBase PmDBPmDB
+InfoView InfoINDB
+iSilo ToGoToGo
+iSilo 3 SDocSilX
+JFile JbDbJBas
+JFile Pro JfDbJFil
+LIST DATALSdb
+MobileDB Mdb1Mdb1
+MobiPocket BOOKMOBI
+Plucker DataPlkr
+QuickSheet DataSprd
+SuperMemo SM01SMem
+TealDoc TEXtTlDc
+TealInfo InfoTlIf
+TealMeal DataTlMl
+TealPaint DataTlPt
+ThinkDB dataTDBP
+Tides TdatTide
+TomeRaider ToRaTRPW
+Weasel zTXTGPlm
+WordSmith BDOCWrdS
+
diff --git a/format_docs/pdb/plucker.html b/format_docs/pdb/plucker.html
new file mode 100644
index 0000000000..07f7b926ca
--- /dev/null
+++ b/format_docs/pdb/plucker.html
@@ -0,0 +1,2122 @@
+
+
+
+
+The Plucker Document Format
+
+
+The Plucker Document Format
+
+Introduction
+
+
+This document is the official description of the
+Plucker format.
+
+
Overview
+
+The Plucker document format supports a multi-page (in the Web sense of 'page') hyperlinked information structure containing both 'rich' text and images. Links may be internal to the document or link to other documents. External links, in standard URL form, may be included and displayed, but not followed. Images may either be embedded in a text page, as with HTML, or be included as separate stand-alone pages.
+
+Plucker documents are structured so that they can be used both with a file-system-oriented operating system such as Unix or Windows, and with the PalmOS, a non-file-system-oriented OS. To this end, they always begin with a standard PalmOS record database prefix, which consists of four parts: the database header, a record-id list, an AppInfo block, and a SortInfo block. The Plucker format does not use the SortInfo block, which is therefore null, and consequently occupies no space in the document prefix.
+
+The record database prefix is then followed by a sequence of application-specific records. In a Plucker document, this sequence consists of one index record, followed by a series of data records. The index record contains information about the data records, along with some global information, such as the type of compression used. Each data record contains either a page, an image, or data about the document, such as bookmarks or URL data.
+
+The format is big-endian; any multi-byte numeric values specified in this document are big-endian. Images are stored in the Palm image format; for more information on this format please consult http://www.palmos.com/dev/tech/docs/.
+
The Database Prefix
+
+
+
+The database header is a fixed-size structure of 72 bytes. It contains the name of the database, the Plucker version number, various timestamps (creation, modification, last backup), and several flags. All timestamps are given using the PalmOS standard, seconds since 12:00 AM on January 1, 1904.
+
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+docName |
+32 |
+String |
+Must contain a NUL-terminated 7-bit ASCII string (only character codes 0x20-0x7E are valid) giving the name of the document. Because of the terminating NUL character at end, only 31 bytes can actually be used for the name of the document. The first 26 bytes of this string are used by Plucker as a unique ID for the document; names should be unique in the first 26 characters. |
+
+
+
+flags |
+2 |
+Bitfield |
+Most bits in this field are unused. Unused bits should be set to zero on document creation, but reader software should not expect them to stay at this value.
+
+ Valid bits are as follows. All numeric values given are big-endian.
+
+
+Name |
+Value |
+Meaning |
+
+
+
+CopyPrevention |
+0x0040 |
+Indicates that system should not allow copying of this document. |
+
+
+
+Launchable |
+0x0200 |
+Indicates that this document should be presented as a first-class object on desktop renderings. If this bit is set, an AppInfo block must be included. |
+
+
+
+Backup |
+0x0008 |
+Indicates that this document should be backed up, if the system includes such a capability. |
+
+
+
+ |
+
+
+
+version |
+2 |
+Numeric |
+Version of the Plucker format used in this document. Must have the value 1. |
+
+
+
+creationDate |
+4 |
+Timestamp |
+Time of document creation |
+
+
+
+modificationDate |
+4 |
+Timestamp |
+Time document last modified |
+
+
+
+unused1 |
+8 |
+Numeric |
+Must be zero at document creation, but any specific value should not be relied upon. |
+
+
+
+appInfoOffset |
+4 |
+Numeric |
+Either zero, if no appInfo is present, or the offset from the beginning of the document to the start of the appInfo block.
+ |
+
+
+sortInfoId |
+4 |
+Numeric |
+Must be zero. |
+
+
+
+magic |
+8 |
+String |
+Must be the 8 ISO Latin-1 characters "DataPlkr". No terminating NUL character. |
+
+
+
+unused2 |
+4 |
+Numeric |
+Must be zero at document creation, but any specific value should not be relied upon. |
+
+
+
+
+
+
+This list consists of a six-byte list header, followed by one ID entry for each data record in the document. The list header has the structure:
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+nextRecordListID |
+4 |
+Numeric |
+Must be zero. |
+
+
+
+numRecords |
+2 |
+Numeric |
+Number of records in the document, including the index record. |
+
+
+
+This is then followed by numRecords entries of the following structure:
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+recordOffset |
+4 |
+Numeric |
+Number of bytes from the start of the document to the beginning of the record |
+
+
+
+attributes |
+1 |
+Bitfield |
+Record attributes -- should be zero. |
+
+
+
+uniqueID |
+3 |
+Numeric |
+A local (document-specific) unique ID for the record. This is not used by Plucker (because it is not preserved by PalmOS through beaming of a document), but must still be different for each record. |
+
+
+
+
+Finally, there are two bytes of zero-padding to bring the structure alignment back to 4 bytes.
+
+
+
+Typically, this is only present when the launchable flag is set in the flags field of the database header. No Plucker data aside from icon display information and a versioning string is stored in this block. This block has the following structure:
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+signature |
+4 |
+Numeric |
+Must contain the value 0x6C6E6368. |
+
+
+
+hdrVersion |
+2 |
+Numeric |
+Must have the value 3. |
+
+
+
+hdrEncoding |
+2 |
+Numeric |
+Must have the value 0. |
+
+
+
+verStrWords |
+2 |
+Numeric |
+The number of two-byte words following, containing the version string. |
+
+
+
+verStr |
+2 * verStrWords |
+String |
+NUL-terminated ISO Latin-1 string, padded at end if necessary with a zero byte to an even-byte boundary, containing a version string to display to the user containing version information for the document. |
+
+
+
+pqaTitleWords |
+2 |
+Numeric |
+The number of two-byte words in the following pqaTitleStr. |
+
+
+
+pqaTitleStr |
+2 * pqaTitleWords |
+String |
+NUL-terminated ISO Latin-1 string, padded at end if necessary with a zero byte to an even-byte boundary, containing a title string for iconic display of the document. |
+
+
+
+iconWords |
+2 |
+Numeric |
+Number of two-byte words in the following icon image. |
+
+
+
+icon |
+2 * iconWords |
+Image |
+Image (32x32) in Palm image format to be used as an icon to represent the document on a desktop-style display. The image may not use a custom color map. |
+
+
+
+smIconWords |
+2 |
+Numeric |
+Number of two-byte words in the following icon image. |
+
+
+
+smIcon |
+2 * smIconWords |
+Image |
+Small image (15x9) in Palm image format to be used as an icon to represent the document on a desktop-style display. The image may not use a custom color map. |
+
+
+
+
+
+This record includes info about the compression type used
+for the Plucker document and also what IDs the reserved records use.
+The viewer will use this record to know where to look for the
+reserved records and whether it must have support for ZLib
+compression. This record should always be the first record in
+the Plucker document (i.e. at index 0).
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+uid |
+
+2 |
+
+Numeric |
+
+unique ID for record, always 0x0001 |
+
+
+
+version |
+
+2 |
+
+Numeric |
+
+0x0002 if data is ZLib compressed, 0x0001 if DOC compressed |
+
+
+
+records |
+
+2 |
+
+Numeric |
+
+number of reserved records |
+
+
+
+reserved |
+
+4*records |
+
+Numeric |
+
+reserved ID array |
+
+
+
+
+The reserved ID array consists of a series of name/ID pairs,
+where the ID is the unique ID (2 bytes) for
+the record and the name is a value (2 bytes)
+from the following list.
+
+
+- home.html = 0
+- external bookmarks = 1
+- URL handling = 2
+- default category = 3
+- additional metadata = 4
+- page list metadata = 5
+- sorted URL name data = 6
+- external anchor name data = 7
+
+
+
+
+
+There are several different types of data records.
+
+
+
+
+
+Each data record starts with a header, having the following structure:
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+uid |
+2 |
+Numeric |
+Unique ID for record. IDs must be sorted in increasing order.
+Currently the ID is not
+allowed to be 0xFFFF. Moreover, some earlier versions of the viewer had a bug
+that crashed on records numbered 0x8000-0xFFFE. |
+
+
+
+paragraphs |
+2 |
+Numeric |
+number of paragraphs |
+
+
+
+size |
+2 |
+Numeric |
+total length of data before compression |
+
+
+
+type |
+1 |
+Numeric |
+Data type. Must be one of the following:
+
+
+Data type |
+Value |
+
+
+
+DATATYPE_PHTML |
+
+0 |
+
+
+
+DATATYPE_PHTML_COMPRESSED |
+
+1 |
+
+
+
+DATATYPE_TBMP |
+
+2 |
+
+
+
+DATATYPE_TBMP_COMPRESSED |
+
+3 |
+
+
+
+DATATYPE_MAILTO |
+
+4 |
+
+
+
+DATATYPE_LINK_INDEX |
+
+5 |
+
+
+
+DATATYPE_LINKS |
+
+6 |
+
+
+
+DATATYPE_LINKS_COMPRESSED |
+
+7 |
+
+
+
+DATATYPE_BOOKMARKS |
+
+8 |
+
+
+
+DATATYPE_CATEGORY |
+
+9 |
+
+
+
+DATATYPE_METADATA |
+
+10 |
+
+
+
+DATATYPE_STYLE_SHEET |
+
+11 |
+
+
+
+DATATYPE_FONT_PAGE |
+
+12 |
+
+
+DATATYPE_TABLE |
+
+13 |
+
+
+
+DATATYPE_TABLE_COMPRESSED |
+
+14 |
+
+
+
+DATATYPE_COMPOSITE_IMAGE |
+
+15 |
+
+
+
+DATATYPE_PAGELIST_METADATA |
+
+16 |
+
+
+
+DATATYPE_SORTED_URL_INDEX |
+
+17 |
+
+
+
+DATATYPE_SORTED_URL |
+
+18 |
+
+
+
+DATATYPE_SORTED_URL_COMPRESSED |
+
+19 |
+
+
+
+DATATYPE_EXT_ANCHOR_INDEX |
+
+20 |
+
+
+
+DATATYPE_EXT_ANCHOR |
+
+21 |
+
+
+
+DATATYPE_EXT_ANCHOR_COMPRESSED |
+
+22 |
+
+
+
+ |
+
+
+
+flags |
+1 |
+Bitfield |
+Bit-mapped record flags. Valid bits are as follows (all numeric values given are big-endian); unused bits should be set to zero.
+
+
+Name |
+Value |
+Meaning |
+
+
+
+Continued Record |
+ 0x01 |
+A value of one indicates that the record is
+continued by the fragment in the next sequential record of the same type.
+This value is applicable to the following data types:
+
+ - DATATYPE_PHTML
+ - DATATYPE_PHTML_COMPRESSED
+
+A value of zero indicates that the record is not to be continued (i.e. there are no fragments beyond this one, or this is the last one).
+ |
+
+
+
+Navigation Metadata |
+0x02 |
+A value of one indicates that the text or image data in this record is followed by additional navigation metadata. |
+
+
+
+ |
+
+
+
+
+This data format supports two forms of compression, DOC and ZLIB. That part of a data record that occurs after the header is compressed as a single chunk. All compressed records in a single document must use the same compression format. Compressed records may be mixed with uncompressed records. In a compressed record, the length of the compressed data must be less than its uncompressed length.
+DOC compression is the the format invented for early Palm usage.
+ZLIB compression uses the ZLib format documented in Internet RFCs 1950 and 1951. See also http://www.gzip.org/zlib/manual.html for a description of the library used to perform the compression and decompression.
+Plucker documents may be keyed to a specific string of 40 or fewer ASCII characters, called the owner-id. When such a key is specified, zlib compression must be used in the document. When an owner-id is specified, the beginning of each zlib-compressed data segment is XOR'ed with a value derived from the key, after compression, and must be XOR'ed again with the derived value before being decompressed. If an owner-id is specified for a document, the metadata record must exist, and must contain an OwnerID subrecord giving the CRC-32 of the owner-id string.
+The derived value mentioned above is a 40-byte value constructed by forming 10 strings by concatenating the owner-id string with itself 2, 3, 4, 5, 6, 7, 8, 9, 10, and 11 times, then taking the CRC-32 values of each of these concatenations, then packing those 32-bit values in big-endian order into a 40-byte buffer.
+
+
+
+
+
+For text data the data record header is followed by a series of paragraph
+headers, each representing a paragraph block in the text data. This series of paragraph headers is then followed by the compressed or uncompressed text data. Each paragraph header has the form:
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+size |
+
+2 |
+
+Numeric |
+
+Total length of paragraph before compression. NOTE: No text data should be larger than
+32k. If the original document is larger than 32k, then the
+parser must split it into several records.
+ |
+
+
+
+attributes |
+
+2 |
+
+Bitfield |
+
+Paragraph info. The high-order 13 bits are reserved for future use and should be set to zero; the 3 low-order bits contain a numeric value in the range [0..7] giving the
+amount of extra paragraph spacing (2*value pixels). |
+
+
+
+
+The (uncompressed) text data contains a character stream of ISO Latin-1 characters, interspersed with 'functions'.
+
+A function is introduced in the text stream by a NULL character (0x00), followed by a one-byte function code
+and up to 7 bytes of data. The 3 LSB of the function code represent the
+remaining function data length; the 5 MSB denote the actual function
+code. The following functions are valid:
+
+
+
+Function Code |
+Description |
+Bytes |
+Arguments |
+
+
+
+0x0A |
+Page link begins |
+2 |
+record ID |
+
+
+
+0x0B |
+Targeted page link begins |
+3 |
+record ID, target |
+
+
+
+0x0C |
+Paragraph link begins |
+4 |
+record ID, paragraph number |
+
+
+
+0x0D |
+Targeted paragraph link begins |
+5 |
+record ID, paragraph number, target |
+
+
+
+0x08 |
+Link ends |
+0 |
+no data |
+
+
+
+0x11 |
+Set font |
+1 |
+font specifier |
+
+
+
+0x1A |
+Embedded image |
+2 |
+image record ID |
+
+
+
+0x22 |
+Set margin |
+2 |
+left margin, right margin |
+
+
+
+0x29 |
+Alignment of text |
+1 |
+alignment |
+
+
+
+0x33 |
+Horizontal rule |
+3 |
+8-bit height, 8-bit width (pixels), 8-bit width (%, 1-100) |
+
+
+
+0x38 |
+New line |
+0 |
+no data |
+
+
+
+0x40 |
+Italic text begins |
+0 |
+no data |
+
+
+
+0x48 |
+Italic text ends |
+0 |
+no data |
+
+
+
+0x53 |
+Set text color |
+3 |
+8-bit red, 8-bit green, 8-bit blue |
+
+
+
+0x5C |
+Multiple embedded image |
+4 |
+alternate image record ID, image record ID |
+
+
+
+0x60 |
+Underline text begins |
+0 |
+no data |
+
+
+
+0x68 |
+Underline text ends |
+0 |
+no data |
+
+
+
+0x70 |
+Strike-through text begins |
+0 |
+no data |
+
+
+
+0x78 |
+Strike-through text ends |
+0 |
+no data |
+
+
+
+0x83 |
+16-bit Unicode character |
+3 |
+alternate text length, 16-bit unicode character |
+
+
+
+0x85 |
+32-bit Unicode character |
+5 |
+alternate text length, 32-bit unicode character |
+
+
+
+0x8E |
+Begin custom font span |
+6 |
+font page record ID, X page position, Y page position |
+
+
+
+0x8C |
+Adjust custom font glyph position |
+4 |
+X page position, Y page position |
+
+
+
+0x8A |
+Change font page |
+2 |
+font record ID |
+
+
+
+0x88 |
+End custom font span |
+0 |
+no data |
+
+
+
+0x90 |
+Begin new table row |
+0 |
+no data |
+
+
+
+0x92 |
+Insert table (or table link) |
+2 |
+table record ID |
+
+
+
+0x97 |
+Table cell data |
+7 |
+8-bit alignment, 16-bit image record ID, 8-bit columns, 8-bit rows, 16-bit text length |
+
+
+
+0x9A |
+Exact link modifier |
+2 |
+Paragraph Offset (The Exact Link Modifier modifies a Paragraph Link or Targeted Paragraph Link function to specify an exact byte offset within the paragraph. This function must be followed immediately by the function it modifies). |
+
+
+
+
+The function arguments have the following definitions:
+
+
+
+Argument |
+Bytes |
+Notes |
+
+
+
+record ID |
+
+2 |
+
+This is either a reference to a record in Plucker document (that is, a real record ID), or an index into the list of URLs, for URLs which have not been included in the document. |
+
+
+
+image record ID |
+
+2 |
+
+reference to image in Plucker document |
+
+
+
+paragraph number |
+
+2 |
+
+paragraph number (starting from 0) to jump to or an index into the external anchor name data if the record ID is pseudo-Record ID for a URL which has not been included in the document. |
+
+
+
+font specifier |
+
+1 |
+
+
+The font concept used in Plucker is that of a 'standard' font, along with bold and italic versions of that font. There is no font notion corresponding to HTML's <BIG> or <SMALL>. In this markup, boldness and size are specified with a font specifier; italic is specified with a separate function code. There are currently 11 font specification values, with the following meanings (the actual PalmOS fonts used by the Palm viewer are also given):
+
+
+Value |
+Description |
+PalmOS 2.x |
+PalmOS 3.x |
+
+
+0 |
+Regular text. |
+stdFont |
+stdFont |
+
+
+1 |
+Suitable for <H1> HTML tags. |
+boldFont |
+largeBoldFont |
+
+
+2 |
+Suitable for <H2> HTML tags. |
+boldFont |
+largeBoldFont |
+
+
+3 |
+Suitable for <H3> HTML tags. |
+boldFont |
+largeFont |
+
+
+4 |
+Suitable for <H4> HTML tags. |
+boldFont |
+largeFont |
+
+
+5 |
+Suitable for <H5> HTML tags. |
+stdFont |
+boldFont |
+
+
+6 |
+Suitable for <H6> HTML tags. |
+stdFont |
+boldFont |
+
+
+7 |
+Regular text, but bold. |
+stdFont |
+boldFont |
+
+
+8 |
+Fixed-width text, suitable for <TT> HTML tags. |
+stdFont |
+fixedWidthFont |
+
+
+9 |
+Small normal text, suitable for <SMALL> HTML tags. |
+stdFont |
+stdFont |
+
+
+10 |
+Small subscript text, suitable for <SUB> HTML tags. |
+stdFont |
+stdFont |
+
+
+11 |
+Small superscript text, suitable for <SUP> HTML tags. |
+stdFont |
+stdFont |
+
+
+ |
+
+
+
+left margin |
+
+1 |
+
+left margin in pixels |
+
+
+
+right margin |
+
+1 |
+
+right margin in pixels |
+
+
+
+alignment |
+
+1 |
+
+alignment code (left = 0, right = 1, center = 2, justify = 3) |
+
+
+
+height |
+
+1 |
+
+height of horizontal rule in pixels, if not given a default value
+of 2 pixels will be used |
+
+
+
+width (pixels) |
+
+1 |
+
+width in pixels, should be 0 if percentage value should be used |
+
+
+
+width (%) |
+
+1 |
+
+width as the percentage between the current left and right margins.
+The default is 100% |
+
+
+
+alternate text length |
+
+1 |
+
+When a Unicode character not representable in ISO-Latin-1 is encountered in an HTML document, a Unicode-character function code is inserted, with the 16-bit or 32-bit value of the character. This is followed by a "alternate representation" of the character in ISO-Latin-1 text. This parameter gives the length, in bytes, of the alternate text span. If the viewer can present the Unicode character directly, display of the alternate text should be suppressed. |
+
+
+16 or 32 bit Unicode character |
+
+2, 4 |
+
+When a Unicode character not representable in ISO-Latin-1 is encountered in an HTML document, a Unicode-character function code is inserted, with the 16-bit or 32-bit Unicode character code for the character, which this parameter supplies. This is followed by a "alternate representation" of the character in ISO-Latin-1 text. If the viewer can present the Unicode character directly, display of the alternate text should be suppressed. |
+
+
+
+target |
+
+1 |
+
+The target parameter of a link function allows an alternate default target view for a link
+to be specified. By default, a link will always open in the same view as the current content
+location. Valid link targets are as follows:
+
+
+Value |
+Description |
+
+
+0 |
+Default View. If specified, the link will be opened in the default view as
+determined by the reader. This value causes a Targeted Paragraph or Page
+Link to behave identical to a standard Paragraph or Page Link. |
+
+
+1 |
+Primary View. Specifies that the link will be opened in the primary window
+regardless of current content location. |
+
+
+2 |
+Secondary View/Popup View. Specifies that the link will be opened in the
+secondary or popup view regardless of current content location. |
+
+
+ |
+
+
+
+Paragraph Offset |
+
+2 |
+
+specifies an exact byte offset within a paragraph relative to the beginning of the paragraph. |
+
+
+
+
+
+
+The image data consists of an image in Palm image format, compressed or uncompressed as specified in the document's index record. The image may in addition be internally compressed, via any of the compression techniques allowed in the Palm image format. The fundamental size of an image must be less than 480,000; this size is calculated by multiplying the width (in pixels) by the height (in pixels) by the depth (in bits).
+
+If the fundamental size is greater than 480,000, most parsers can be told to create a Multi-image group. This is a group of image records consisting of parts of the image which the viewer displays as one image. The parts are standard Image data records and the Multi-image record tells how many columns and rows the image has, and the record numbers of the parts.
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+columns |
+
+2 |
+
+Numeric |
+
+number of columns in this image |
+
+
+
+rows |
+
+2 |
+
+Numeric |
+
+number of rows in this image |
+
+
+
+image record IDs |
+
+2 * columns * rows |
+
+Numeric |
+
+References to images in Plucker document. There are (columns * rows) images listed here |
+
+
+
+
+
+
+
+
+This data is optionally appended to the end of a text or image data record based on the
+setting of the Navigation Metadata flag in the record header. If the Navigation Metadata flag
+is set to one, the image or text data is immediately followed by the following data structures.
+
+
+NOTE: If navigation data is appended to a record then the last two bytes
+in the record shall contain the byte offset from the beginning of the record to the start of the
+navigation data.
+
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+anchor name offset |
+
+2 |
+
+Numeric |
+
+Byte offset from the beginning of the metadata to the anchor name table for this record or 0xffff if there is no anchor name data. |
+
+
+
+pagelist offset |
+
+2 |
+
+Numeric |
+
+Byte offset from the beginning of the metadata to the page list table for this record or 0xffff if there is no page list data. |
+
+
+
+hierarchy offset |
+
+2 |
+
+Numeric |
+
+Byte offset from the beginning of the metadata to the hierarchy table for this record or 0xffff if there is no hierarchy data. |
+
+
+
+topic offset |
+
+2 |
+
+Numeric |
+
+Byte offset from the beginning of the metadata to the list of topics associates with this record or 0xffff if there is no topic data . |
+
+
+
+Title Strings |
+
+2+ |
+
+String sequence |
+
+A series of concatenated NUL-terminated strings in the following order:
+
+ - Long Record Title - The title of the record. This title should allow the record to be identified out of context.
+ - Short Record Title - A title string which allows the record to be identified in context.
+
+If a given string in the list is not defined, an empty string (NUL) must still be entered
+in the appropriate location in the string sequence. |
+
+
+
+
+The offsets block and title strings are followed by a series of tables.
+
+The anchor name table specifies the offset of the anchor names within the text record.
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+anchor names |
+
+2 |
+
+Numeric |
+
+number of anchor names |
+
+
+
+anchor name data |
+
+2*anchor names |
+
+Numeric Array |
+
+This field is an array of 2 byte offsets, each representing the corresponding
+offset associated with an anchor name relative to the beginning of the text
+record. The order of each offset corresponds to the order of the string for that anchor name
+within the anchor name string sequence below. |
+
+
+
+anchor name strings |
+
+variable size |
+
+String sequence |
+
+A concatenated sequence of NUL-terminated strings, each an anchor name name. The relative
+location of an anchor name string in the string sequence represents its index into the anchor
+name data. |
+
+
+
+
+
+The page list table contains
+the uid of the previous and next records relative to this record for one or more
+page lists. Each page list, combined with the page lists from other
+Incoming Navigation data records defines one of more unique a linear navigation
+schemes for the document. The default scheme is always associated with a
+list id of 0.
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+pagelists |
+
+2 |
+
+Numeric |
+
+number of page list entries for this record. |
+
+
+
+pagelist data |
+
+6*pagelists |
+
+Page List Data |
+
+Block of data containing an array of Page List Data (described below). |
+
+
+
+
+
+The page list data consists of a series of structures containing a list id followed by the
+unique ID of the previous and next records associated with that list id.
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+list id |
+
+2 |
+
+Numeric |
+
+the list id for this list. A list id of 0 should be used for the default linear ordering. |
+
+
+
+prev uid |
+
+2 |
+
+Numeric |
+
+the uid of the previous record in the series for this list id or 0xffff if this is the
+first record in the series. |
+
+
+
+next uid |
+
+2 |
+
+Numeric |
+
+the uid of the next record in the series for the list id or 0xffff if this is the
+last record in the series. |
+
+
+
+
+The hierarchy table specifies unique ID of each text record above this record in
+the document hierarchy that serves as an index leading to the current record.
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+levels |
+
+2 |
+
+Numeric |
+
+number of levels above the current record in the hierarchy. |
+
+
+
+hierarchy data |
+
+2*levels |
+
+Numeric Array |
+
+This field is an array of 2 byte uids, each corresponding to the text record
+that serves as the index at a given level in the document hierarchy relative to
+the current record. The order of each uid in the array corresponds to the
+order of its corresponding string description in the string sequence below. |
+
+
+
+hierarchy strings |
+
+variable size |
+
+String sequence |
+
+An abbreviated string that identifies the level index. This string should be
+as short as possible, ideally only a few characters. |
+
+
+
+
+The topic table provides a list of topics associated with the record.
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+topics |
+
+2 |
+
+Numeric |
+
+number of topics in the topic string sequence. |
+
+
+
+topic strings |
+
+0+ |
+
+String sequence |
+
+A concatenated sequence of one or more NUL-terminated
+ISO Latin-1 strings. Each string represents a topic associated with this
+text record. |
+
+
+
+
+
+
+The mailto data contains info about e-mail addresses that are
+referenced by the mailto anchors. All the offsets are counting
+from the end of the header.
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+to offset |
+
+2 |
+
+Numeric |
+
+offset to TO string |
+
+
+
+cc offset |
+
+2 |
+
+Numeric |
+
+offset to CC string |
+
+
+
+subject offset |
+
+2 |
+
+Numeric |
+
+offset to SUBJECT string |
+
+
+
+body offset |
+
+2 |
+
+Numeric |
+
+offset to BODY string |
+
+
+
+strings |
+
+0+ |
+
+String sequence |
+
+A concatenated sequence of one or more NUL-terminated US-ASCII strings. Each contains a header-value, which follows the contraints on header values laid down in IETF RFC 2822. Header folding is not allowed. Any of the four headers shown above may be absent; header values should be accessed via the above offsets. |
+
+
+
+
+
+
+Optionally, URL information for the records in the document may be stored. This information includes URL strings both for the pages actually included in the document, and for those pages excluded from the document. This information is conceptually stored as a sequence of strings, where the position of the URL in the sequence corresponds to the record ID of its page in the document. In the case of a page which is not actually included in the document, a pseudo-record-ID is assigned, greater than any actual record IDs in the document, and the URL of that page is associated with that pseudo-record-ID.
+
+In practice, there are two kinds of records used to store the URL strings, the URL handling data record, which serves as an index into the sequence of strings, and the URL data record, one or more of which contain the actual strings.
+
+
+For cross-document linking support, the URL strings must be of the format "doc://[external doc name]:[url]" where external doc name is the name of the external document and url is the URL string associated with a given record.
+
+The URL handling data is used to find the record ID of the record which contains the correct URL string. It
+contains a series of 2 byte number pairs.
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+last url |
+
+2 |
+
+Numeric |
+
+the ordinal number of the last URL in record |
+
+
+
+id |
+
+2 |
+
+Numeric |
+
+record ID for record |
+
+
+
+
+
+
+The URL data contains a list of the URLs. Additional records
+are created if needed and contain up to 200 URLs.
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+URLs |
+
+1+ |
+
+String sequence |
+
+a concatenated sequence of NUL-terminated URL strings following the constraints of IETF RFC 1738. The list may contain up to 200 URLs (only text and image records are included,
+other records are represented only by the presence of a NUL; that is, by an empty string) |
+
+
+
+
+These records may or may not be compressed. This is indicated
+by the type in the header. These records are used by the Details
+form to display the URL of the current record and by the External
+Reference form to display the URL of not collected pages. From
+either form you can copy the URL to a Memo to remind you to pluck
+it at a later date. For inter-document links, a paragraph link
+function may be specified to contain a pseudo-Record ID in place
+of an actual-Record ID, and an index into the
+external anchor names record
+in place of the paragraph number.
+
+
+
+
+The external bookmarks data contains a list of bookmarks added by the
+parser. It will work similar to named anchors.
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+bookmarks |
+
+2 |
+
+Numeric |
+
+number of bookmarks |
+
+
+
+offset |
+
+2 |
+
+Numeric |
+
+offset to the start of the bookmark data (counting from the beginning
+of the record) |
+
+
+
+names |
+
+variable size |
+
+String sequence |
+
+A concatenated sequence of NUL-terminated strings, each a bookmark name |
+
+
+
+bookmark data |
+
+4*bookmarks |
+
+Bookmark Data |
+
+block of data for the location of the external bookmarks (see below) |
+
+
+
+
+
+The bookmark data is a series of uid/offset pairs.
+
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+uid |
+
+2 |
+
+Numeric |
+
+unique ID for record |
+
+
+
+offset |
+
+2 |
+
+Numeric |
+
+paragraph offset |
+
+
+
+
+
+
+Each Plucker document can be assigned to a number of named categories. This record stores the names of default categories for the document. The data consists of a concatenated series of NUL-terminated strings that
+should be used as the default category/categories for this document.
+
+
+
+There should only be one of these per document. This record begins with a two byte numeric value, giving the number of subrecords that follow, followed by that number of subrecords. The subrecords are a sequence of tagged variable length items. Each subrecord consists of three fields:
+
+
+
+
+Field |
+Bytes |
+Type |
+Description |
+
+
+
+type code |
+2 |
+Numeric |
+Specifies what piece of extra information is in this subrecord |
+
+
+
+length |
+2 |
+Numeric |
+Number of 2-byte words in the argument |
+
+
+
+argument |
+2 * length |
+(type code specific) |
+Data |
+
+
+
+
+The following table describes the valid subrecord type codes, and describes the structure of the associated data for each subrecord type. Subrecords with unknown type codes should be ignored.
+
+
+
+Type code |
+Name |
+Description |
+Argument |
+
+
+
+1 |
+CharSet |
+This is the character set and encoding used by text records in this document, unless otherwise specified for particular records. |
+a two-byte numeric value, specifying the IETF IANA MIBenum value for the character set. See the IANA registry of character sets for valid values. |
+
+
+
+2 |
+ExceptionalCharSets |
+This is a list of text records which use a charset other than that specified by the default CharSet. Note that if no default CharSet is specified, the default charset should be thought of as "unknown". |
+a sequence of (length / 2) record-ID, IANA-MIBenum pairs, where MIBenum values are as specified for CharSet. The invalid MIBenum value of 0 (zero) is used for records which have an unknown charset, if necessary.
+
+
+Field |
+Bytes |
+Type |
+Notes |
+
+
+
+record ID |
+
+2 |
+
+Numeric |
+
+unique ID for record |
+
+
+
+MIBenum |
+
+2 |
+
+Numeric |
+
+IANA MIBenum for the character set used in this record |
+
+
+
+ |
+
+
+
+3 |
+OwnerID |
+This is the CRC-32 of the specified owner-id for the document, if any. Note that associating an owner-id with a document also affects the calculation of zlib compression. |
+a four-byte numeric value giving the CRC-32 of the owner-id string. |
+
+
+
+4 |
+Author |
+The name of the author of the document. |
+A string value in the document's default character set, padded at the end with NUL characters to an even number of bytes. |
+
+
+
+5 |
+Title |
+The full title of the document. |
+A string value in the document's default character set, padded at the end with NUL characters to an even number of bytes. |
+
+
+
+6 |
+PublicationDate |
+The date and time this document was created. |
+A 4-byte unsigned integer giving the number of seconds from 12:00 AM on January 1, 1904, to the time when this document was created. |
+
+
+
+7 |
+Linked Documents |
+The list of external documents that this document links to. |
+A concatenated sequence of NUL-terminated strings representing the document names for each external document linked to within this document. The string sequence should be padded at the end with NUL characters to an even number of bytes. |
+
+
+
+
+
+
+TBD
+
+
+
+TBD
+
+
+
+The Table Record describes an HTML table. It begins with a structure with the following format.
+
+
+
+Field |
+Bytes |
+Type |
+Description |
+
+
+
+size |
+2 |
+Numeric |
+Size of the following data |
+
+
+
+columns |
+2 |
+Numeric |
+Number of columns the table contains |
+
+
+
+rows |
+2 |
+Numeric |
+Number of rows the table contains |
+
+
+
+depth |
+1 |
+Numeric |
+Bits per pixel (BPP) needed to render the table |
+
+
+
+border |
+1 |
+Numeric |
+Draw table borders (0 = no, any other value = yes, 1 pixel wide) |
+
+
+
+border color |
+4 |
+Numeric |
+RGB value of border color |
+
+
+
+link color |
+4 |
+Numeric |
+RGB value of link color |
+
+
+
+
+This is followed by table row and table cell functions (their ends are implied). Each table cell function is followed by 'text length' (from the function) text, containing text and/or formatting functions. (Such as links, style, underline, strike through, italic, etc.)
+
+
+
+
+There should only be one of these per document. This record is used to assign the name and initial record associated with each page list in the document. Page lists are used to define the default ordering of pages within the document. More than one page list can be specified, which can be useful for defining tours through a document.
+
+
+
+
+Field |
+Bytes |
+Type |
+Description |
+
+
+
+lists |
+
+2 |
+
+Numeric |
+
+The number of page lists or tours in the document. |
+
+
+
+first record |
+
+2 * sequences |
+
+Numeric Array |
+
+An array of uids corresponding to the first record in each page sequence. The zero
+based index into this array represents the sequence id or tour id. The first entry should be
+considered the default page ordering for the document. |
+
+
+
+list name |
+
+1+ |
+
+String sequence |
+
+a concatenated sequence of NUL-terminated strings, each representing the name of a
+page list. The first entry in the list corresponds to the default page ordering. For
+unnamed page lists, a NUL charater should still be specified. |
+
+
+
+
+Page lists can be thought of as linked lists of records. The first record field in the
+Page List Metadata record is equivalent to the head pointer of the list.
+Each text record contains a previous/next record pointer within it's navigation
+metadata.
+
+
+
+
+The Sorted URL handling record is used to find the record ID of the Sorted URL data record
+containing data for a given URL string. It contains a series of 2 byte number pairs.
+
+
+
+
+Field |
+Bytes |
+Type |
+Description |
+
+
+
+last URL |
+
+2 |
+
+Numeric |
+
+the ordinal number of the last URL in record |
+
+
+
+id |
+
+2 |
+
+Numeric |
+
+record ID for record |
+
+
+
+
+
+
+
+The sorted URL data record contains a list of URL/UID pointers sorted according to the
+lexicographical order of the URL strings pointed to by the url uid and
+url offset fields. This data is used in cross-document linking to
+facilitate a binary search of the URL strings in order to lookup the record ID
+for an incoming URL string. Only URLs for records actually contained in the document
+should be included in the Sorted URL data records. URLs for external
+records should be omitted.
+
+
+
+Field |
+Bytes |
+Type |
+Description |
+
+
+
+url uid |
+
+2 |
+
+Numeric |
+
+unique ID for URL data record that contains the sorted URL string. |
+
+
+
+url offset |
+
+2 |
+
+Numeric |
+
+byte offset of the first character of the sorted URL string in the URL data record. |
+
+
+
+record uid |
+
+2 |
+
+Numeric |
+
+unique ID of the text or image record that pertains to the sorted URL string. |
+
+
+
+
+
+
+
+The External Anchor handling record is used to find the record ID of the External
+Anchor data record containing a given external anchor string. It contains a series of
+2 byte number pairs.
+
+
+
+
+Field |
+Bytes |
+Type |
+Description |
+
+
+
+last anchor |
+
+2 |
+
+Numeric |
+
+the ordinal number of the last anchor in record |
+
+
+
+id |
+
+2 |
+
+Numeric |
+
+record ID for record |
+
+
+
+
+
+
+
+The External Anchor data record is a string table containing the unique names for all
+external anchor name strings referenced in this document. These strings
+are used to query the record specific anchor name tables in a target book to
+determine a paragraph offset for cross-document linking. This information is
+conceptually stored as a sequence of strings, where the position of the anchor
+name in the sequence corresponds to it's index.
+
+
+
+Field |
+Bytes |
+Type |
+Description |
+
+
+
+anchor name list |
+
+1+ |
+
+String sequence |
+
+a concatenated sequence of unique NUL-terminated strings, each representing an anchor
+name from an external link found within this document. |
+
+
+
+
+
+These records may or may not be compressed. This is indicated by the type in the header. These
+records are used in conjunction with the Sorted URL Data records and record specific anchor
+name tables to facilitate cross-document linking.
+
+
+
+
+© Copyright 2000 Michael
+Nordström
+<micke@sslug.dk> · Copyright 2001 Bill Janssen <bill@janssen.org |
+
+$Id: DBFormat.html,v 1.27 2005/10/29 14:14:21 nordstrom Exp $ |
+
+
+
+
diff --git a/format_docs/pdb/pml.txt b/format_docs/pdb/pml.txt
new file mode 100644
index 0000000000..b5b357f381
--- /dev/null
+++ b/format_docs/pdb/pml.txt
@@ -0,0 +1,936 @@
+Palm Markup Language
+--------------------
+
+This page explains how to use the Palm Markup Language (PML) to specify
+formatting and other information in a text file for later reading using the
+eReader.
+
+PML commands start with a backslash, "\", and usually consist of a single
+character after that. Some PML commands are paired, such as those that specify
+italicized text. Other commands are directives, such as the "\p", which
+specifies a page break. PML is not meant to be an industrial-strength markup
+language, but it is easy to understand, easy to parse, and creates high-quality
+electronic books.
+
+Since PML and Palm DropBook are not without flaws, there is a page of Tips and
+Pitfalls.
+
+
+Let's Dive Right In
+-------------------
+
+palmsample.txt contains examples of formatting text, specifying chapters, etc.
+Use it to start from, or just as an example when making your own books.
+
+The following table specifies the Palm Markup Language commands, and what
+they do.
+
+\p New page
+\x New chapter; also causes a new page break.
+ Enclose chapter title (and any style codes)
+ with \x and \x
+\Xn New chapter, indented n levels (n between 0 and
+ 4 inclusive) in the Chapter dialog; doesn't
+ cause a page break. Enclose chapter title (and
+ any style codes) with \Xn and \Xn
+\Cn="Chapter title" Insert "Chapter title" into the chapter
+ listing, with level n (like \Xn). The text is
+ not shown on the page and does not force a page
+ break. This can sometimes be useful to insert a
+ chapter mark at the beginning of an
+ introduction to the chapter, for example.
+\c Center this block of text; close with \c on
+ beginning of line
+\r Right justify text block; close with \r on
+ beginning of line
+\i Italicize block; close with \i
+\u Underline block; close with \u
+\o Overstrike block; close with \o
+\v Invisible text; close with \v (can be used for
+ comments)
+\t Indent block. Start at beginning of a line,
+ close with \t at end of a line
+\T="50%" Indents the specified percentage of the screen
+ width, 50% in this case. If the current drawing
+ position is already past the specified screen
+ location, this tag is ignored.
+\w="50%" Embed a horizontal rule of a given percentage
+ width of the screen, in this case 50%. This tag
+ causes a line break before and after it. The
+ rule is centered. The percent sign is mandatory.
+\n Switch to the "normal" font, which is specified
+ by the user
+\s Switch to stdFont; close with \s to revert to
+ normal font
+\b Switch to boldFont; close with \b to revert to
+ normal font (deprecated; use \B instead)
+\l Switch to largeFont; close with \l to revert to
+ normal font
+\B Mark text as bold. Unlike the \b tag, \B
+ doesn't change the font, so you can have large
+ bold text. You cannot mix \b and \B in the same
+ PML file.
+\Sp Mark text as superscript. Should not be mixed
+ with other styles such as bold, italic, etc.
+ Enclose superscripted text with \Sp.
+\Sb Mark text as subscript. Should not be mixed
+ with other styles such as bold, italic, etc.
+ Enclose subscripted text with \Sb.
+\k Make enclosed text into small-caps; close with
+ \k. Any characters enclosed in \k tags
+ (including those with accents) are made
+ uppercase and are rendered at a smaller point
+ size than a regular uppercase character.
+\\ Represents a single backslash
+\aXXX Insert non-ASCII character whose Windows 1252
+ code is decimal XXX. See the PML character
+ table for details.
+\UXXXX Insert non-ASCII character whose Unicode code
+ is hexidecimal XXXX. See the Extended PML
+ character table for details.
+\m="imagename.png" Insert the named image. See the section on
+ Images below.
+\q="#linkanchor"Some text\q Reference a link anchor which is at another
+ spot in the document. The string after the
+ anchor specification and before the trailing\q
+ is underlined or otherwise shown to be a link
+ when viewing the document.
+\Q="linkanchor" Specify a link anchor in the document.
+\- Insert a soft hyphen. A soft hyphen shows up
+ only if it is necessary to break a word across
+ a line.
+\Fn="footnote1"1\Fn Link the "1" to a footnote whose name is
+ footnote1, tagged at the end of the PML
+ document. See the section on Footnotes and
+ Sidebars below.
+\Sd="sidebar1"Sidebar\Sd Link the "Sidebar" text to a sidebar whose name
+ is sidebar1, tagged at the end of the PML
+ document. See the section on Footnotes and
+ Sidebars below.
+\I Mark as a reference index item. Enclose index
+ item (and any style codes) with \I and \I. See
+ Creating Dictionaries for more information.
+
+
+Examples
+--------
+
+\pThis is a new page
+
+\xChapter III\x
+
+\X1Chapter III, part A\X1
+
+\p\C="Introduction"The following story is one of my favorites...
+
+\cProperty of
+Gateway Senior High School
+\c
+
+\rJustify my love
+\r
+
+This stuff is \ireally\i cool.
+
+I just read \uMoby Dick.\u
+
+This is a \obig\o mistake.
+
+Copyright 1917\v Date of magazine serialization \v
+
+\tOnce upon a time
+there was a wicked queen
+called Esmerelda.\t
+
+Mammals:\T="40%"Lions
+\T="40%"Tigers
+\T="40%"Bears
+
+He walked away.
+\w="80%"
+Later that day, he ran into an old friend.
+
+\nIn the normal ways...
+
+The \stitle page\s should be formatted...
+
+I just \bcan't\b believe that you...
+
+This \lREALLY\l is a large tiger...
+
+This \Bbold\B text can be either \l\Blarge bold\B\l or \s\Bsmall bold\B\s.
+
+e\Spx + 2\Sp = 9
+
+C\Sb2\SbH\Sb3\SbO\Sb2\Sb should be used in moderation.
+
+See also \kanteater\k.
+
+The DOS prompt said "C:\\windows\\"
+
+The man said \a147Yeah.\a148
+
+Arrows can point \U2190 left or right \U2192.
+
+A Yield sign looks like this: \m="yieldsign.png".
+
+See the \q="#detailedinstructions"Detailed Instructions\q for how to install your eBook.
+
+\Q="detailedinstructions"\bDetailed Instructions\b - This section
+describes how to install an eBook to your handheld device.
+
+Very long words like anti\-dis\-establish\-ment\-arian\-ism may benefit from
+the use of soft hyphens.
+
+The Emerson case\Fn="emerson"[1]\Fn will be very important...
+
+For more information, see the \Sd="moreinfo"sidebar\Sd.
+
+\I\Baardvark\B\I \in.\i a large burrowing nocturnal mammal that feeds especially on termites and ants
+
+
+Footnotes and Sidebars
+----------------------
+
+Footnotes and Sidebars are specified with an XML-like syntax at the end of the
+PML document. For example,
+
+
+
+would specify the sidebar to be displayed when the user taps on a sidebar link
+in the text that was specified using the \Sd tag.
+
+Any text or PML placed after the first footnote or sidebar is ignored as part
+of the book text.
+
+Sidebars and footnotes can include most PML features, but there are some PML
+tags that cannot be used inside of a sidebar or footnote.
+
+These include
+Chapters \x, \X, \C
+Links \q, \Q
+Footnotes \Fn
+Sidebars \Sd
+
+See the palmsample.txt file for examples of how to use many of the PML tags.
+
+
+Images
+------
+
+The following rules are intended to guarantee that images in your eBook will be
+viewable on all platforms that eReader runs on.
+
+On low-resolution Palm OS handhelds, an image wider than 158 pixels or taller
+than 148 pixels will be represented in the text by a thumbnail that the user
+can tap to view the entire image. Images smaller than 158 x 148 will be
+presented in-line with the text.
+
+On high-resolution Palm OS handhelds (those having screens of 320x320 pixels or
+more), images smaller than 158 by 148 pixels will be pixel-doubled. Images
+larger than 158x148 may be shown in-line with the text, if they will fit on
+the screen.
+
+On non-Palm OS platforms, small images will be scaled up appropriately. Large
+images will be scaled down to fit on the page; in this case the user can tap on
+the image to view the entire image and zoom in or out.
+
+For DropBook to find the image, it must be present in a directory whose name
+matches that of the PML text file. For example, if "pmlsample.txt" contains a
+reference to an image called "intro.png", then there must be a directory called
+"pmlsample_img" that contains intro.png. The directory's name is the name of
+the PML file (without the .txt extension) with "_img" appended.
+
+Images must be in PNG format and cannot be filtered or interlaced. Image depth
+must be 8 bits or less. Any color table may be used for color images.
+
+Image files must be less than or equal to 65505 bytes in size, since they are
+embedded into the .pdb format of the book; Palm database records are limited to
+65505 bytes in length. Since images are compressed, the actual image displayed
+by the reader may be much larger than 64K.
+
+Any or all of these restrictions may eventually be removed.
+
+
+Adding a Title, Cover Art, and Other Meta-information to Your eBook
+-------------------------------------------------------------------
+
+DropBook normally presents a dialog in which the title and other information
+for the eBook may be specified. This information may be embedded in the PML
+file instead.
+
+To specify the eBook title as it will appear in the Open dialog on the
+handheld, place a block of invisible comment text at the beginning of the file
+using \v tags. Inside this comment block, put the string TITLE="My eBook",
+where "My eBook" is replaced with the name of your eBook. It should look
+something like this:
+
+\vTITLE="Palm Sample Document"\v
+
+You can also specify the author using the AUTHOR meta-tag, the publisher with
+PUBLISHER, copyright information with COPYRIGHT, and the eBook ISBN with EISBN.
+A fully-specified set of meta-information might appear in PML as:
+
+\vTITLE="Palm Sample Document" AUTHOR="Sam Morgenstern" PUBLISHER="eReader.com"
+EISBN="X-XXXX-XXXX" COPYRIGHT="Copyright \a169 2004 by Sam Morgenstern"\v
+
+Cover art: If an image named "cover.png" is present in the eBook, it is assumed
+to be the cover art for the eBook. See the rules for images for sizing and
+other information.
+
+Some or all of this information may appear in the book information dialog in
+eReader, and may be used for other purposes in future products.
+
+
+Creating Dictionaries
+---------------------
+
+The \I PML tag is used to delimit an index item. Example: \Iaardvark\I
+
+Each entry must start in the normal font. If DropBook shows an error beginning
+with "No styles permitted before...", there is probably a missing end style tag
+before the text shown in the error message.
+
+Links, chapters and other PML structures are not permitted in dictionaries.
+Images, however, are.
+
+A special dictionary entry, "(Front matter)" is shown before other entries in
+the list of entries, and should be used to include pronunciation symbols and
+other front matter.
+
+Note that use of dictionaries requires eReader Pro.
+
+
+Tips and Pitfalls
+-----------------
+
+This page explains some common mistakes, some bugs in DropBook and/or the
+eReader, and some techniques that will allow you to create quality electronic
+books for the eReader.
+
+ * Check out the Converting to Palm eBooks page for some pointers on
+ converting text from various formats into the Palm Markup Language.
+ * Use a return at the end of each paragraph, not each line.
+ * Using an extra return between paragraphs reads easier than paragraph
+ indentation.
+ * The eReader doesn't display empty lines at the top of a page. If you need
+ to have some "empty" lines at the top of a page, put a space on each line.
+ * Don't use tables if you can possibly avoid it.
+
+ None of the fonts that the eReader supports are monospaced, so tables can
+ be difficult to represent. Break out the information in another way, or
+ use the \T tag, but beware of tables that look great on a Palm OS
+ handheld but not on a Pocket PC or vice versa.
+
+ * The Reader breaks lines on spaces, dashes or underscores. This has
+ several implications.
+
+ 1. Don't fill more than a line with spaces, dashes or underscores.
+ There's a bug (which will be fixed in a future release) which
+ causes MakeBook to hang on such a line. Note that in the large
+ font, the number of spaces, dashes or underscores will be much
+ smaller than in the small font.
+ 2. A string such as He shouted "Wait!--" may place the last quote on
+ the beginning of a line, since the line would break after the
+ second dash. Prevent this by using the PML string: He shouted
+ "Wait!\a150\a150". The non-breaking dash, code 150, will not break
+ a line. Use \a160 for a non-breaking space. Even better: use \a151,
+ a long dash, instead of two short dashes.
+
+ * The justification codes \c and \r (center and right justification) must
+ have closing codes on the beginning of the line following the justified
+ text.
+ * The indentation tag \t must have a closing tag at the end of a line of
+ the indented text.
+ * Use \s (small font) in the title page(s) of books to force the page(s) to
+ format nicely. Other than that, \n, \s and \l should rarely be necessary;
+ the font size used for most text display should be chosen by the user.
+
+
+Converting Uncommon Characters to PML
+-------------------------------------
+
+Use this chart to convert uncommon characters to their Palm Markup Language
+(PML) equivalent. Most characters are simply represented as themselves in PML
+and don't require this chart. But some uncommon characters can only be
+represented in PML by their "\aXXX" syntax. Use this chart to look up that
+"\aXXX" syntax.
+
+For Example, if you wanted to write the following phrase in PML:
+
+ Copyright © 1999 by Samuel Morgenstern
+
+In PML, you would write it as:
+
+ Copyright \a169 1999 by Samuel Morgenstern
+
+Char HTML # Code HTML Char Code PML Char Code Description
+
+ - Normal space
+! ! - ! Exclamation
+" " " " Double quote
+# # - # Hash
+$ $ - $ Dollar
+% % - % Percent
+& & & & Ampersand
+' ' - ' Apostrophe
+( ( - ( Open bracket
+) ) - ) Close bracket
+* * - * Asterisk
++ + - + Plus sign
+, , - , Comma
+- - - - Minus sign
+. . - . Period
+/ / - / Forward slash
+0 0 - 0 Digit 0
+1 1 - 1 Digit 1
+2 2 - 2 Digit 2
+3 3 - 3 Digit 3
+4 4 - 4 Digit 4
+5 5 - 5 Digit 5
+6 6 - 6 Digit 6
+7 7 - 7 Digit 7
+8 8 - 8 Digit 8
+9 9 - 9 Digit 9
+: : - : Colon
+; ; - ; Semicolon
+ < < < Less than
+= = - = Equals
+ > > > Greater than
+? ? - ? Question mark
+@ @ - @ At sign
+A A - A A
+B B - B B
+C C - C C
+D D - D D
+E E - E E
+F F - F F
+G G - G G
+H H - H H
+I I - I I
+J J - J J
+K K - K K
+L L - L L
+M M - M M
+N N - N N
+O O - O O
+P P - P P
+Q Q - Q Q
+R R - R R
+S S - S S
+T T - T T
+U U - U U
+V V - V V
+W W - W W
+X X - X X
+Y Y - Y Y
+Z Z - Z Z
+[ [ - [ Open square bracket
+\ \ - \\ Backslash
+] ] - ] Close square bracket
+^ ^ - ^ Caret
+_ _ - _ Underscore
+` ` - ` Grave accent
+a a - a a
+b b - b b
+c c - c c
+d d - d d
+e e - e e
+f f - f f
+g g - g g
+h h - h h
+i i - i i
+j j - j j
+k k - k k
+l l - l l
+m m - m m
+n n - n n
+o o - o o
+p p - p p
+q q - q q
+r r - r r
+s s - s s
+t t - t t
+u u - u u
+v v - v v
+w w - w w
+x x - x x
+y y - y y
+z z - z z
+{ { - { Left brace
+| | - | Vertical bar
+} } - } Right brace
+~ ~ - ~ Tilde
+
+ \a160 Non-breaking space
+ ¡ ¡ \a161 Inverted exclamation
+ ¢ ¢ \a162 Cent sign
+ £ £ \a163 Pound sign
+ ¤ ¤ \a164 Currency sign
+ ¥ ¥ \a165 Yen sign
+ ¦ ¦ \a166 Broken bar
+ § § \a167 Section sign
+ ¨ ¨ \a168 Umlaut or diaeresis
+ © © \a169 Copyright sign
+ ª ª \a170 Feminine ordinal
+ « « \a171 Left angle quotes
+ ¬ ¬ \a172 Logical not sign
+ \a173 Soft hyphen
+ ® ® \a174 Registered trademark
+ ¯ ¯ \a175 Spacing macron
+ ° ° \a176 Degree sign
+ ± ± \a177 Plus-minus sign
+ ² ² \a178 Superscript 2
+ ³ ³ \a179 Superscript 3
+ ´ ´ \a180 Spacing acute
+ µ µ \a181 Micro sign
+ ¶ ¶ \a182 Paragraph sign
+ · · \a183 Middle dot
+ ¸ ¸ \a184 Spacing cedilla
+ ¹ ¹ \a185 Superscript 1
+ º º \a186 Masculine ordinal
+ » » \a187 Right angle quotes
+ ¼ ¼ \a188 One quarter
+ ½ ½ \a189 One half
+ ¾ ¾ \a190 Three quarters
+ ¿ ¿ \a191 Inverted question mark
+ À À \a192 A grave
+ Á Á \a193 A acute
+ Â Â \a194 A circumflex
+ Ã Ã \a195 A tilde
+ Ä Ä \a196 A diaeresis
+ Å Å \a197 A ring
+ Æ &Aelig; \a198 AE ligature
+ Ç Ç \a199 C cedilla
+ È È \a200 E grave
+ É É \a201 E acute
+ Ê Ê \a202 E circumflex
+ Ë Ë \a203 E diaeresis
+ Ì Ì \a204 I grave
+ Í Í \a205 I acute
+ Î Î \a206 I circumflex
+ Ï Ï \a207 I diaeresis
+ Ð Ð \a208 Eth
+ Ñ Ñ \a209 N tilde
+ Ò Ò \a210 O grave
+ Ó Ó \a211 O acute
+ Ô Ô \a212 O circumflex
+ Õ Õ \a213 O tilde
+ Ö Ö \a214 O diaeresis
+ × × \a215 Multiplication sign
+ Ø Ø \a216 O slash
+ Ù Ù \a217 U grave
+ Ú Ú \a218 U acute
+ Û Û \a219 U circumflex
+ Ü Ü \a220 U diaeresis
+ Ý Ý \a221 Y acute
+ Þ Þ \a222 THORN
+ ß ß \a223 sharp s
+ à à \a224 a grave
+ á á \a225 a acute
+ â â \a226 a circumflex
+ ã ã \a227 a tilde
+ ä ä \a228 a diaeresis
+ å å \a229 a ring
+ æ æ \a230 ae ligature
+ ç ç \a231 c cedilla
+ è è \a232 e grave
+ é é \a233 e acute
+ ê ê \a234 e circumflex
+ ë ë \a235 e diaeresis
+ ì ì \a236 i grave
+ í í \a237 i acute
+ î î \a238 i circumflex
+ ï ï \a239 i diaeresis
+ ð ð \a240 eth
+ ñ ñ \a241 n tilde
+ ò ò \a242 o grave
+ ó ó \a243 o acute
+ ô ô \a244 o circumflex
+ õ õ \a245 o tilde
+ ö ö \a246 o diaeresis
+ ÷ ÷ \a247 division sign
+ ø ø \a248 o slash
+ ù ù \a249 u grave
+ ú ú \a250 u acute
+ û û \a251 u circumflex
+ ü ü \a252 u diaeresis
+ ý ý \a253 y acute
+ þ þ \a254 thorn
+ ÿ ÿ \a255 y diaeresis
+, ‚ ‚ \a130 single low quote
+ ƒ ƒ \a131 Scripted f
+ „ „ \a132 low quote
+ … … \a133 Ellipsis
+ † † \a134 Dagger
+ ‡ &Dagger \a135 Double dagger
+ Š Š \a138 Large S w/inverted caret
+< ‹ ‹ \a139 single left angle quote
+ Œ Œ \a140 Large combined oe
+ ‘ ‘ \a145 Open single smart quote
+ ’ ’ \a146 Close single smart quote
+ “ “ \a147 Open double smart quote
+ ” ” \a148 Close double smart quote
+ • • \a149 Bullet
+ – – \a150 Small dash (en dash)
+ — — \a151 Large dash (em dash)
+ ™ ™ \a153 Trademark
+ š š \a154 Small S w/inverted caret
+> › › \a155 single right angle quote
+ œ œ \a156 Small combined oe
+ Ÿ Ÿ \a159 Large Y with diaeresis
+
+
+Extended Character Set
+----------------------
+
+In addition to the special characters supported by earlier versions of eReader
+(which can be accessed using the \a### tag), all versions of eReader Pro and
+eReader version 2.4 and later include support for additional special characters
+and symbols. These symbols can be accessed using the \U#### tag, where #### are
+four hexidecimal digits giving the Unicode encoding of the special character.
+
+Only the limited subset of Unicode characters given in the table below are
+supported. In addition, some of the characters that are included in the table
+are not present in eReader Pro versions prior to 2.4. To ensure that the
+characters are displayed correctly, books using these tags should be read using
+eReader or eReader Pro version 2.4 or later.
+
+On Palm OS handhelds these special symbols are only available in one size,
+matching the "Small" font. For best results on Palm OS handhelds the \U tag
+should only be used inside blocks set to the "Small" font by way of \s tags.
+On Palm OS handhelds these special characters are not affected by the font tags
+(\s, \l, \b and \n), the bold style tag (\B), or the small caps style tag (\k).
+
+If the \U characters are not showing up correctly using eReader on your Windows
+desktop or laptop this problem is a result of the fonts for eReader not being
+installed properly. The solution is to go to the directory C:\Windows\Fonts\
+and "double click" on each font that starts with "Maynard". This will open each
+font and allow the system to register it. Close the windows that were opened a
+result of the mouse clicks and the problem should be resolved.
+
+Char HTML Code PML Code Description
+
+Latin Extended-A
+Ā Ā \U0100 LATIN CAPITAL LETTER A WITH MACRON
+ā ā \U0101 LATIN SMALL LETTER A WITH MACRON
+Ă Ă \U0102 LATIN CAPITAL LETTER A WITH BREVE
+ă ă \U0103 LATIN SMALL LETTER A WITH BREVE
+ą ą \U0105 LATIN SMALL LETTER A WITH OGONEK
+ć ć \U0107 LATIN SMALL LETTER C WITH ACUTE
+Č Č \U010C LATIN CAPITAL LETTER C WITH CARON
+č č \U010D LATIN SMALL LETTER C WITH CARON
+Ē Ē \U0112 LATIN CAPITAL LETTER E WITH MACRON
+ē ē \U0113 LATIN SMALL LETTER E WITH MACRON
+ĕ ĕ \U0115 LATIN SMALL LETTER E WITH BREVE
+ė ė \U0117 LATIN SMALL LETTER E WITH DOT ABOVE
+ę ę \U0119 LATIN SMALL LETTER E WITH OGONEK
+ě ě \U011B LATIN SMALL LETTER E WITH CARON
+ĝ ĝ \U011D LATIN SMALL LETTER G WITH CIRCUMFLEX
+ğ ğ \U011F LATIN SMALL LETTER G WITH BREVE
+Ī Ī \U012A LATIN CAPITAL LETTER I WITH MACRON
+ī ī \U012B LATIN SMALL LETTER I WITH MACRON
+ĭ ĭ \U012D LATIN SMALL LETTER I WITH BREVE
+į į \U012F LATIN SMALL LETTER I WITH OGONEK
+ı ı \U0131 LATIN SMALL LETTER DOTLESS I
+Ł Ł \U0141 LATIN CAPITAL LETTER L WITH STROKE
+ł ł \U0142 LATIN SMALL LETTER L WITH STROKE
+ń ń \U0144 LATIN SMALL LETTER N WITH ACUTE
+ň ň \U0148 LATIN SMALL LETTER N WITH CARON
+ŋ ŋ \U014B LATIN SMALL LETTER ENG
+Ō Ō \U014C LATIN CAPITAL LETTER O WITH MACRON
+ō ō \U014D LATIN SMALL LETTER O WITH MACRON
+ŏ ŏ \U014F LATIN SMALL LETTER O WITH BREVE
+ő ő \U0151 LATIN SMALL LETTER O WITH DOUBLE ACUTE
+ŕ ŕ \U0155 LATIN SMALL LETTER R WITH ACUTE
+ř ř \U0159 LATIN SMALL LETTER R WITH CARON
+Ś Ś \U015A LATIN CAPITAL LETTER S WITH ACUTE
+ś ś \U015B LATIN SMALL LETTER S WITH ACUTE
+ş ş \U015F LATIN SMALL LETTER S WITH CEDILLA
+ţ ţ \U0163 LATIN SMALL LETTER T WITH CEDILLA
+ũ ũ \U0169 LATIN SMALL LETTER U WITH TILDE
+ū ū \U016B LATIN SMALL LETTER U WITH MACRON
+ŭ ŭ \U016D LATIN SMALL LETTER U WITH BREVE
+ŷ ŷ \U0177 LATIN SMALL LETTER Y WITH CIRCUMFLEX
+ź ź \U017A LATIN SMALL LETTER Z WITH ACUTE
+Ž Ž \U017D LATIN CAPITAL LETTER Z WITH CARON
+ž ž \U017E LATIN SMALL LETTER Z WITH CARON
+Latin Extended-B
+ ƿ \U01BF LATIN LETTER WYNN
+ ǎ \U01CE LATIN SMALL LETTER A WITH CARON
+ ǐ \U01D0 LATIN SMALL LETTER I WITH CARON
+ ǒ \U01D2 LATIN SMALL LETTER O WITH CARON
+ ǔ \U01D4 LATIN SMALL LETTER U WITH CARON
+ ǡ \U01E1 LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON
+ ǣ \U01E3 LATIN SMALL LETTER AE WITH MACRON
+ ǧ \U01E7 LATIN SMALL LETTER G WITH CARON
+ ǫ \U01EB LATIN SMALL LETTER O WITH OGONEK
+ ǰ \U01F0 LATIN SMALL LETTER J WITH CARON
+ ȇ \U0207 LATIN SMALL LETTER E WITH INVERTED BREVE
+ ȝ \U021D LATIN SMALL LETTER YOGH
+ ȧ \U0227 LATIN SMALL LETTER A WITH DOT ABOVE
+ ȯ \U022F LATIN SMALL LETTER O WITH DOT ABOVE
+ ȳ \U0233 LATIN SMALL LETTER Y WITH MACRON
+IPA Extensions
+ ɑ \U0251 LATIN SMALL LETTER SCRIPT A
+ ɒ \U0252 LATIN SMALL LETTER TURNED SCRIPT A
+ ɔ \U0254 LATIN SMALL LETTER OPEN O
+ ə \U0259 LATIN SMALL LETTER SCHWA
+ ɜ \U025C LATIN SMALL LETTER REVERSED OPEN E
+ ɥ \U0265 LATIN LETTER SMALL LETTER TURNED H
+ ɪ \U026A LATIN LETTER SMALL CAPITAL I
+ ɲ \U0272 LATIN SMALL LETTER N WITH LEFT HOOK
+ ʃ \U0283 LATIN SMALL LETTER ESH
+ ʉ \U0289 LATIN SMALL LETTER U BAR
+ ʊ \U028A LATIN SMALL LETTER UPSILON
+ ʌ \U028C LATIN SMALL LETTER TURNED V
+ ʏ \U028F LATIN LETTER SMALL CAPITAL Y
+ ʒ \U0292 LATIN SMALL LETTER EZH
+ ʔ \U0294 LATIN LETTER GLOTTAL STOP
+ ʜ \U029C LATIN LETTER SMALL CAPITAL H
+Spacing Modifier Letters
+ ʾ \U02BE MODIFIER LETTER RIGHT HALF RING
+ ʿ \U02BF MODIFIER LETTER LEFT HALF RING
+ˇ ˇ \U02C7 CARON
+ ˈ \U02C8 MODIFIER LETTER VERTICAL LINE
+ ˌ \U02CC MODIFIER LETTER LOW VERTICAL LINE
+ ː \U02D0 MODIFIER LETTER TRIANGULAR COLON
+˘ ˘ \U02D8 BREVE
+˙ ˙ \U02D9 DOT ABOVE
+Greek and Coptic
+Α Α \U0391 GREEK CAPTIAL LETTER ALPHA
+Β Β \U0392 GREEK CAPTIAL LETTER BETA
+Γ Γ \U0393 GREEK CAPTIAL LETTER GAMMA
+Δ Ε \U0394 GREEK CAPTIAL LETTER DELTA
+Ε Ε \U0395 GREEK CAPTIAL LETTER EPSILON
+Ζ Ζ \U0396 GREEK CAPTIAL LETTER ZETA
+Η Η \U0397 GREEK CAPTIAL LETTER ETA
+Θ Θ \U0398 GREEK CAPTIAL LETTER THETA
+Ι Ι \U0399 GREEK CAPTIAL LETTER IOTA
+Κ Κ \U039A GREEK CAPTIAL LETTER KAPPA
+Λ Λ \U039B GREEK CAPTIAL LETTER LAMBDA
+Μ Μ \U039C GREEK CAPTIAL LETTER MU
+Ν Ν \U039D GREEK CAPTIAL LETTER NU
+Ξ Ξ \U039E GREEK CAPTIAL LETTER XI
+Ο Ο \U039F GREEK CAPTIAL LETTER OMICRON
+Π Π \U03A0 GREEK CAPTIAL LETTER PI
+Ρ Ρ \U03A1 GREEK CAPTIAL LETTER RHO
+Σ Σ \U03A3 GREEK CAPTIAL LETTER SIGMA
+Τ Τ \U03A4 GREEK CAPTIAL LETTER TAU
+Υ Υ \U03A5 GREEK CAPTIAL LETTER UPSILON
+Φ Φ \U03A6 GREEK CAPTIAL LETTER PHI
+Χ Χ \U03A7 GREEK CAPTIAL LETTER CHI
+Ψ Ψ \U03A8 GREEK CAPTIAL LETTER PSI
+Ω Ω \U03A9 GREEK CAPTIAL LETTER OMEGA
+α α \U03B1 GREEK SMALL LETTER ALPHA
+β β \U03B2 GREEK SMALL LETTER BETA
+γ γ \U03B3 GREEK SMALL LETTER GAMMA
+δ δ \U03B4 GREEK SMALL LETTER DELTA
+ε ε \U03B5 GREEK SMALL LETTER EPSILON
+ζ ζ \U03B6 GREEK SMALL LETTER ZETA
+η η \U03B7 GREEK SMALL LETTER ETA
+θ θ \U03B8 GREEK SMALL LETTER THETA
+ι ι \U03B9 GREEK SMALL LETTER IOTA
+κ κ \U03BA GREEK SMALL LETTER KAPPA
+λ λ \U03BB GREEK SMALL LETTER LAMBDA
+μ μ \U03BC GREEK SMALL LETTER MU
+ν ν \U03BD GREEK SMALL LETTER NU
+ξ ξ \U03BE GREEK SMALL LETTER XI
+ο ο \U03BF GREEK SMALL LETTER OMICRON
+π π \U03C0 GREEK SMALL LETTER PI
+ρ ρ \U03C1 GREEK SMALL LETTER RHO
+ς ς \U03C2 GREEK SMALL LETTER FINAL SIGMA
+σ σ \U03C3 GREEK SMALL LETTER SIGMA
+τ τ \U03C4 GREEK SMALL LETTER TAU
+υ υ \U03C5 GREEK SMALL LETTER UPSILON
+φ φ \U03C6 GREEK SMALL LETTER PHI
+χ χ \U03C7 GREEK SMALL LETTER CHI
+ψ ψ \U03C8 GREEK SMALL LETTER PSI
+ω ω \U03C9 GREEK SMALL LETTER OMEGA
+ ϑ \U03D1 GREEK THETA SYMBOL
+ ϝ \U03DD GREEK SMALL LETTER DIGAMMA
+Hebrew
+א א \U05D0 HEBREW LETTER ALEPH
+ב ב \U05D1 HEBREW LETTER BET
+ג ג \U05D2 HEBREW LETTER GIMEL
+ד ד \U05D3 HEBREW LETTER DALET
+ה ה \U05D4 HEBREW LETTER HE
+ו ו \U05D5 HEBREW LETTER VAV
+ז ז \U05D6 HEBREW LETTER ZAYIN
+ח ח \U05D7 HEBREW LETTER HET
+ט ט \U05D8 HEBREW LETTER TET
+י י \U05D9 HEBREW LETTER YOD
+ך ך \U05DA HEBREW LETTER FINAL KAF
+כ כ \U05DB HEBREW LETTER KAF
+ל ל \U05DC HEBREW LETTER LAMED
+ם ם \U05DD HEBREW LETTER FINAL MEM
+מ מ \U05DE HEBREW LETTER MEM
+ן ן \U05DF HEBREW LETTER FINAL NUN
+נ נ \U05E0 HEBREW LETTER NUN
+ס ס \U05E1 HEBREW LETTER SAMEKH
+ע ע \U05E2 HEBREW LETTER AYIN
+ף ף \U05E3 HEBREW LETTER FINAL PE
+פ פ \U05E4 HEBREW LETTER PE
+ץ ץ \U05E5 HEBREW LETTER FINAL TSADI
+צ צ \U05E6 HEBREW LETTER TSADI
+ק ק \U05E7 HEBREW LETTER QOF
+ר ר \U05E8 HEBREW LETTER RESH
+ת ת \U05EA HEBREW LETTER TAV
+Latin Extended Additional
+ ḋ \U1E0B LATIN SMALL LETTER D WITH DOT ABOVE
+ ḍ \U1E0D LATIN SMALL LETTER D WITH DOT BELOW
+ ḗ \U1E17 LATIN SMALL LETTER E WITH MACRON AND ACUTE
+ Ḣ \U1E22 LATIN CAPITAL LETTER H WITH DOT ABOVE
+ Ḥ \U1E24 LATIN CAPITAL LETTER H WITH DOT BELOW
+ ḥ \U1E25 LATIN SMALL LETTER H WITH DOT BELOW
+ ḫ \U1E2B LATIN SMALL LETTER H WITH BREVE BELOW
+ ḳ \U1E33 LATIN SMALL LETTER K WITH DOT BELOW
+ ḷ \U1E37 LATIN SMALL LETTER L WITH DOT BELOW
+ ṁ \U1E41 LATIN SMALL LETTER M WITH DOT ABOVE
+ ṃ \U1E43 LATIN SMALL LETTER M WITH DOT BELOW
+ ṅ \U1E45 LATIN SMALL LETTER N WITH DOT ABOVE
+ ṇ \U1E47 LATIN SMALL LETTER N WITH DOT BELOW
+ ṓ \U1E53 LATIN SMALL LETTER O WITH MACRON AND ACUTE
+ ṙ \U1E59 LATIN SMALL LETTER R WITH DOT ABOVE
+ Ṛ \U1E5A LATIN CAPITAL LETTER R WITH DOT BELOW
+ ṛ \U1E5B LATIN SMALL LETTER R WITH DOT BELOW
+ ṡ \U1E61 LATIN SMALL LETTER S WITH DOT ABOVE
+ ṣ \U1E63 LATIN SMALL LETTER S WITH DOT BELOW
+ ṫ \U1E6B LATIN SMALL LETTER T WITH DOT ABOVE
+ ṭ \U1E6D LATIN SMALL LETTER T WITH DOT BELOW
+ ṯ \U1E6F LATIN SMALL LETTER T WITH LINE BELOW
+ ẑ \U1E91 LATIN SMALL LETTER Z WITH CIRCUMFLEX
+ ẓ \U1E93 LATIN SMALL LETTER Z WITH DOT BELOW
+ ẖ \U1E96 LATIN SMALL LETTER H WITH LINE BELOW
+ ạ \U1EA1 LATIN SMALL LETTER A WITH DOT BELOW
+ ọ \U1ECD LATIN SMALL LETTER O WITH DOT BELOW
+ ỹ \U1EF9 LATIN SMALL LETTER Y WITH TILDE
+General Punctuation
+- ‑ \U2011 NON-BREAKING HYPHEN
+ ‸ \U2038 CARET
+ ‽ \U203D INTERROBANG
+ ⁂ \U2042 ASTERISM
+Arrows
+← ← \U2190 LEFTWARDS ARROW
+→ → \U2192 RIGHTWARDS ARROW
+Mathematical Operators
+∂ ∂ \U2202 PARTIAL DIFFERENTIAL
+√ √ \U221A SQUARE ROOT
+∞ ∞ \U221E INFINITY
+∥ ∥ \U2225 PARALLEL TO
+∫ ∫ \U222B INTEGRAL
+≠ ≠ \U2260 NOT EQUAL TO
+ ⊔ \U2294 SQUARE CUP
+ ⊕ \U2295 CIRCLED PLUS
+ ⋮ \U22EE VERTICAL ELLIPSIS
+Enclosed Alphanumerics
+ Ⓤ \U24CA CIRCLED LATIN CAPITAL LETTER U
+Miscellaneous Symbols
+☜ ☜ \U261C WHITE LEFT POINTING INDEX
+☞ ☞ \U261E WHITE RIGHT POINTING INDEX
+ ☿ \U263F MERCURY
+ ♀ \U2640 FEMALE SIGN
+ ♂ \U2642 MALE SIGN
+ ♃ \U2643 JUPITER
+ ♄ \U2644 SATURN
+ ♅ \U2645 URANUS
+ ♆ \U2646 NEPTUNE
+ ♇ \U2647 PLUTO
+ ♠ \U2660 BLACK SPADE SUIT
+ ♡ \U2661 WHITE HEART SUIT
+ ♢ \U2662 WHITE DIAMOND SUIT
+ ♣ \U2663 BLACK CLUB SUIT
+ ♭ \U266D MUSIC FLAT SIGN
+ ♮ \U266E MUSIC NATURAL SIGN
+ ♯ \U266F MUSIC SHARP SIGN
+Dingbats
+ ✓ \U2713 CHECK MARK
+ ✠ \U2720 MALTESE CROSS
+Private Use Area
+ - \UE000 LATIN SMALL LETTER A WITH MACRON AND ACUTE
+ - \UE001 LATIN SMALL LETTER A WITH MACRON AND TILDE
+ - \UE002 LATIN SMALL LETTER A WITH VERTICAL LINE ABOVE
+ - \UE003 LATIN CAPITAL LETTER C WITH MACRON
+ - \UE004 LATIN SMALL LETTER C WITH MACRON
+ - \UE005 LATIN SMALL LETTER C WITH BREVE
+ - \UE006 LATIN SMALL LETTER C WITH DOT BELOW
+ - \UE007 LATIN SMALL LIGATURE CH
+ - \UE008 LATIN CAPITAL LETTER D WITH MACRON
+ - \UE009 LATIN SMALL LETTER E WITH BAR BELOW
+ - \UE00A LATIN SMALL LETTER E WITH TILDE
+ - \UE00B LATIN SMALL LETTER E WITH MACRON AND BREVE
+ - \UE00C LATIN SMALL LETTER E WITH TILDE AND DOT ABOVE
+ - \UE00D LATIN SMALL LETTER E WITH HOOK RIGHT BELOW
+ - \UE00E LATIN SMALL LETTER G WITH INVERTED BREVE
+ - \UE00F LATIN SMALL LETTER I WITH INVERTED BREVE BELOW
+ - \UE010 LATIN SMALL LETTER I WITH MACRON AND ACUTE
+ - \UE011 LATIN SMALL LETTER K WITH CIRCUMFLEX
+ - \UE012 LATIN SMALL LETTER K WITH BREVE
+ - \UE013 LATIN SMALL LETTER K WITH INVERTED BREVE
+ - \UE014 LATIN SMALL LIGATURE KH
+ - \UE015 LATIN CAPITAL LETTER L WITH MACRON
+ - \UE016 LATIN SMALL LETTER L WITH TILDE
+ - \UE017 LATIN SMALL LETTER L WITH INVERTED BREVE
+ - \UE018 LATIN CAPITAL LETTER M WITH MACRON
+ - \UE019 LATIN SMALL LETTER M WITH MACRON
+ - \UE01A LATIN SMALL LETTER M WITH TILDE
+ - \UE01B LATIN SMALL LETTER O WITH CEDILLA
+ - \UE01C LATIN SMALL LETTER O WITH MACRON AND CIRUMFLEX
+ - \UE01E LATIN SMALL LIGATURE OI
+ - \UE01F LATIN SMALL LIGATURE OO
+ - \UE020 LATIN SMALL LIGATURE OO WITH MACRON
+ - \UE021 LATIN SMALL LIGATURE OU
+ - \UE022 LATIN SMALL LETTER OPEN O WITH ACUTE
+ - \UE023 LATIN SMALL LETTER R WITH DIARESIS
+ - \UE024 LATIN SMALL LETTER R WITH CIRCUMFLEX
+ - \UE025 LATIN SMALL LETTER R WITH RING BELOW
+ - \UE026 LATIN SMALL LETTER S WITH VERTICAL LINE ABOVE
+ - \UE027 LATIN SMALL LETTER S WITH OGONEK
+ - \UE028 LATIN SMALL LETTER S WITH COMMA
+ - \UE02A LATIN SMALL LETTER S WITH BREVE
+ - \UE02B LATIN SMALL LIGATURE SH
+ - \UE02C LATIN SMALL LIGATURE TH
+ - \UE02D LATIN SMALL LETTER U WITH MACRON AND ACUTE
+ - \UE02E LATIN CAPITAL LETTER V WITH MACRON
+ - \UE02F LATIN CAPITAL LETTER X WITH MACRON
+ - \UE030 LATIN SMALL LETTER X WITH CIRCUMFLEX
+ - \UE031 LATIN SMALL LETTER Y WITH BREVE
+ - \UE032 LATIN SMALL LIGATURE ZH
+ - \UE033 LATIN SMALL LETTER TURNED E WITH ACUTE
+ - \UE034 LATIN SMALL LETTER TURNED E WITH CIRCUMFLEX
+ - \UE035 GREEK SMALL LETTER ALPHA WITH GRAVE
+ - \UE036 MUSICAL SYMBOL SEGNO
+ - \UE037 MUSICAL SYMBOL FERMATA
+ - \UE038 MUSICAL SYMBOL CRESCENDO
+ - \UE039 MUSICAL SYMBOL DECRESCENDO
+ - \UE03A MUSICAL SYMBOL DOUBLE SHARP
+ - \UE03B MUSICAL SYMBOL BREVE
+ - \UE03C MUSICAL SYMBOL DOWN BOW
+ - \UE03D MUSICAL SYMBOL UP BOW
+ - \UE03E MUSICAL SYMBOL BREVE ALTERNATE
+ - \UE03F PRINTING SYMBOL DELE
+ - \UE040 PRINTING SYMBOL FRACTIONAL EM
+ - \UE041 INVERTED ASTERISM
+ - \UE042 LATIN SMALL LETTER SCHWA SUPERSCRIPT
+ - \UE043 LATIN SMALL LETTER TURNED Y
+ - \UE044 LATIN SMALL LIGATURE OE WITH MACRON
+ - \UE045 SQUARE ROOT WITH BAR
+ - \UE046 LATIN SMALL LETTER U WITH DOT ABOVE
+ - \UE047 LATIN SMALL LIGATURE UE
+ - \UE048 LATIN SMALL LIGATURE UE WITH MACRON
+ - \UE049 LATIN SMALL LETTER OPEN O WITH TILDE
+ - \UE04A LATIN SMALL LETTER T WITH CARON BELOW
+ - \UE04B LATIN SMALL LETTER SCRIPT A WITH TILDE
+ - \UE04C GREEK SMALL LETTER EPSILON WITH TILDE
+ - \UE04D LATIN SMALL LIGATURE OE WITH TILDE
+ - \UE04E MODIFIER LETTER DOUBLE VERTICAL LINE
+ - \UE04F DOUBLE HYPHEN
+ - \UE050 LATIN SMALL LETTER SCHWA WITH DOT ABOVE
+ - \UE051 LATIN SMALL LETTER SCHWA WITH MACRON
+Alphabetic Presentation Forms
+fl fl \UFB02 LATIN SMALL LIGATURE FL
+שׁ שׁ \UFB2A HEBREW LETTER SINH WITH SHIN DOT
+שׂ שׂ \UFB2B HEBREW LETTER SINH WITH SIN DOT
+
diff --git a/format_docs/pdb/ztxt.txt b/format_docs/pdb/ztxt.txt
new file mode 100644
index 0000000000..98fb6bae3e
--- /dev/null
+++ b/format_docs/pdb/ztxt.txt
@@ -0,0 +1,226 @@
+The zTXT Format
+---------------
+
+The zTXT format is relatively straightforward. The simplest zTXT contains a
+Palm database header, followed by zTXT record #0, followed by the compressed
+data. The compressed data can be in one of two formats: one long data stream,
+or split into chunks for random access. If there are any bookmarks, they occupy
+the record immediately after the compressed data. If there are any annotations,
+the annotation index occupies the record immediately after the bookmarks with
+each annotation in the index having a record immediately after the annotation
+index. Here are diagrams of a simple zTXT and a full featured zTXT:
+
+ DB Header
+0 Record 0
+1
+2
+3
+... Compressed Data
+36
+37
+38
+
+ DB Header
+0 Record 0
+1
+2
+3
+... Compressed Data
+36
+37
+38
+39 Bookmarks
+40 Annotation Index
+41 Annotation 1
+42 Annotation 2
+43 Annotation 3
+
+
+Compression Modes
+-----------------
+
+zTXT version 1.40 and later supports two modes of compression. Mode 1 is a
+random access mode, and mode 2 consists of one long data stream. Both modes
+work on 8K (the default record size) blocks of text.
+
+Please note, however, that as of Weasel Reader version 1.60 the old style
+(mode 2) zTXT format is no longer supported. makeztxt and libztxt still support
+creating these documents for backwards compatibility, but you should not use
+mode 2 if possible.
+
+
+Mode 1
+------
+
+In mode one, 8K blocks of text are compressed into an equal number of blocks of
+compressed data. Using the Z_FULL_FLUSH flush mode with zLib allows for random
+access among the blocks of data. In order for this to function, the first block
+must be decompressed first, and after that any block in the file may be
+decompressed in any order. In mode 1, the blocks of compressed data will likely
+not all have the same size.
+
+
+Mode 2
+------
+
+In zTXT versions before 1.40, this was the only method of compression. This
+mode involves compressing the entire input buffer into a single output buffer
+and then splitting the resulting buffer into 8K segments. This mode requires
+that all of the compressed data be decompressed in one pass. Since there are no
+real 'blocks' of data, the resulting output can be of any blocksize, though
+typically the default of 8K should be fine. The advantage to mode 2 is that it
+will give about 10% - 15% more compression.
+
+
+zTXT Record #0 Definition (version 1.44)
+----------------------------------------
+
+Record 0 provides all of the information about the zTXT contents. Be sure it is
+correct, lest firey death rain down upon your program.
+
+typedef struct zTXT_record0Type {
+ UInt16 version;
+ UInt16 numRecords;
+ UInt32 size;
+ UInt16 recordSize;
+ UInt16 numBookmarks;
+ UInt16 bookmarkRecord;
+ UInt16 numAnnotations;
+ UInt16 annotationRecord;
+ UInt8 flags;
+ UInt8 reserved;
+ UInt32 crc32;
+ UInt8 padding[0x20 - 24];
+} zTXT_record0;
+
+
+Structure Elements
+------------------
+
+UInt16 version;
+
+This is mostly just informational. Your program can figure out what features
+might be available from the version. However, the remaining parts of the
+structure are designed such that their value will be 0 if that particular
+feature is not present, so that is the correct way to test. The version is
+stored as two 8 bit integers. For example, version 1.42 is 0x012A.
+
+UInt16 numRecords;
+
+This is the number of DATA records only and does not include record 0,
+bookmarks, or annotations. With compression mode 1, this is also the number of
+uncompressed text records. With mode 2, you must decompress the file to figure
+out how many text records there will be.
+
+UInt32 size;
+
+The size in bytes of the uncompressed data in the zTXT. Check this value with
+the amount of free storage memory on the Palm to make sure there's enough room
+to decompress the data in full or in part.
+
+UInt16 recordSize;
+
+recordSize is the size in bytes of a text record. This field is important, as
+the size of text and decompression buffers is based on this value. It is used
+by Weasel to navigate though the text so it can map absolute offsets to record
+numberss. 8192 is the default. With compression mode 1, this is the amount of
+data inside each compressed record (except maybe the last one), but the actual
+compressed records will likely have varying sizes. In mode 2, both compressed
+records and the resulting text records are all of this size (except, again, the
+last record).
+
+UInt16 numBookmarks;
+
+The definitive count of how many bookmarks are stored in the bookmark index
+record. See the section on bookmarks below.
+
+UInt16 bookmarkRecord;
+
+If there are any bookmarks, this is set to the record index number that
+contains the bookmark listing, otherwise it is 0.
+
+UInt16 numAnnotations;
+
+Like the bookmark count, this is the definitive count of how many annotations
+are in the annotation index and how many annotation records follow it. See the
+section on annotation below.
+
+UInt16 annotationRecord;
+
+If there are any annotations, this is set to the record index number that
+contains the annotation index, otherwise it is 0.
+
+UInt8 flags;
+
+These flags indicate various features of the zTXT database. flags is a bitmask
+and at present the only two defined bits are:
+
+ZTXT_RANDOMACCESS (0x01)
+ If the zTXT was compressed according to the method in mode 1, then it
+ supports random access and this should be set.
+ZTXT_NONUNIFORM (0x02)
+ Setting this bit indicates that the text records within the zTXT database
+ are not of uniform length. That is, when the blocks of text are
+ decompressed they will not have identical block sizes. If this is not set,
+ the compressed blocks are assumed to all have the same size when
+ decompressed (typically 8K) except for the last block which can be smaller.
+
+UInt32 crc32;
+
+A CRC32 value for checking data integrity. This value is computer over all text
+data record only and does not include record 0 nor any bookmark/annotation
+records. The current implementation in makeztxt/Weasel computes this value
+using the crc32 function in zLib which should be the standard CRC32 definition.
+
+UInt8 padding[0x20 - 24];
+
+zTXT record zero is 32 bytes in length, so the unused portion is padded.
+
+
+zTXT Bookmarks
+--------------
+
+zTXT bookmarks are stored in a simple array in a record at the end of a zTXT.
+The format is as follows:
+
+#define MAX_BMRK_LENGTH 20
+
+typedef struct GPlmMarkType {
+ UInt32 offset;
+ Char title[MAX_BMRK_LENGTH];
+} GPlmMark;
+
+In the structure, offset is counted as an absolute offset into the text. The
+bookmarks must be sorted in ascending order.
+
+If there are no bookmarks, then the bookmark index does not exist. When the
+user creates the first bookmark, the record containing the index will then be
+created. If there are annotations, when the bookmark record is created it must
+go before the annotation index. This will require incrementing annotationRecord
+in record 0 to point to the new record index.
+
+Similarly, when all bookmarks are deleted the bookmark index record is also
+deleted. If there are annotations, annotationRecord in record 0 must be
+decremented to point to the new index.
+
+
+zTXT Annotations
+----------------
+
+zTXT annotations have a format almost identical to that of the bookmark index:
+
+typedef struct GPlmAnnotationType {
+ UInt32 offset;
+ Char title[MAX_BMRK_LENGTH];
+} GPlmAnnotation;
+
+Like the bookmarks, offset is an absolute offset into the text. The annotation
+index is organized just as the bookmarks are, as a single array in a record.
+Note that this structure does NOT store the actual annotation text.
+
+The text of each annotation is stored in its own record immediately following
+the index. So, the first annotation in the index will occupy the first record
+following the index, and the second annotation will be in the second record
+following the index, and so on. The text of each annotation is limited to
+4096 bytes.
+
diff --git a/format_docs/rb.txt b/format_docs/rb.txt
new file mode 100644
index 0000000000..2eb1992afb
--- /dev/null
+++ b/format_docs/rb.txt
@@ -0,0 +1,303 @@
+Rocket eBook File Format
+------------------------
+
+from http://rbmake.sourceforge.net/rb_format.html
+
+
+Overview
+--------
+
+This document attempts to describe the format of a .rb file -- the book
+format that is downloaded into NuvoMedia's
+hand-held wonder, the Rocket eBook
+.
+
+*Note:* All multi-byte integers are stored in Vax/Intel order (the
+opposite of network byte order). Most integers are 4 bytes (an int32),
+but there are some minor exceptions (as detailed below).
+
+Also, the following document refers to the .rb file sections as "pages".
+
+
+Details
+-------
+
+The first 4 bytes of the file seem to be a magic number (in hex): B0 0C
+B0 0C. I like to think of this as a hexidecimal pun on the word "book"
+(repeated). [Matt Greenwood has reported seeing a magic number of "B0 0C
+F0 0D" in another type of ReB-related file -- i.e. "book food".]
+
+The next two bytes appear to be a version number, currently "02 00". I
+assume this means major version 2, minor version 0.
+
+The next 4 bytes are the string "NUVO", followed by 4 bytes of 00h. (I
+have also seen an old title that had 0s in place of the "NUVO".)
+
+This brings us up to offset 0Eh, at which point we have a 4-byte
+representation of the date the book was created (Matt Greenwood pointed
+this out to me -- thanks!). The year is encoded as an int16. On older
+version of the RocketLibrary was encoding the year's full value (e.g.
+1999 was "CF 07" and 2000 was "D0 07"), but a more recent version is now
+using the tm_year value verbatim -- i.e. it's storing 100 for the year
+2000 ("64 00"). The year is followed by an int8 for the 1-relative month
+number, and an int8 for the day of the month.
+
+After that is 6 bytes of 00h. These may be reserved for setting the time
+of creation (at a guess).
+
+Then, at offset 18h, we have an int32 that contains the absolute offset
+of the "Table of Contents" (the directory of the pages contained within
+this .rb file). In all of the .rb file's I've seen, this remains
+constant with a value of 128h. However, I have tested an atypical .rb
+file where I placed the ToC at the end of the file (after all the file
+contents), and it worked fine. (I've chosen not to build any books in
+such a non-standard format, however.)
+
+Immediately following this is an int32 with the length of the .rb file
+(so we can check if the file is complete or not).
+
+All the bytes from here (offset 20h) up to offset 128h appear to only be
+used by an encrypted title. In a non-encrypted title, they are always 0.
+
+The table of contents typically comes next (at offset 128h). It starts
+with an int32 count of the number of "page" entries (.rb-file sections)
+in the ToC. Each entry consists of a name (zero-padded to 32 bytes),
+followed by 3 int32s: the length of this entry's data segment, the
+absolute offset of the data in the .rb file, and a flag. The known flag
+values are: 1 (encrypted), 2 (info page), and 8 (deflated). The names
+are tweaked as needed to ensure that they are all unique. The current
+RocketWriter software uses a unique 6-digit number, a dash, up to 8
+characters from the filename, and then the re-mapped suffix for the data
+(.html, .hidx, .png, .info, etc.). My rbmake library simply ensures that
+the names are no longer than 15 characters (not counting the suffix) and
+are all unique.
+
+Often the first item in the ToC is the info page, but it doesn't have to
+be. This page of information contains NAME=VALUE pairs that note the
+author, title, what the root-page's name is, etc. (See appendix A). This
+data is never encrypted nor compressed, so this entry's flag value is
+always "2".
+
+An image page is always stored as a B&W image in PNG format. Since it
+has its own compression, it is stored without any additional attempt at
+deflation. I have also never seen an encrypted image, so its flag value
+is always 0.
+
+An HTML page contains the tags and text that were re-written into a
+consistent syntax (this presumably makes the HTML renderer in the ReB
+itself simpler). HTML pages are typically compressed (See appendix B).
+Every HTML page appears to use the suffix .html no matter what the file
+name was on import (but I have seen older files with .htm used as the
+suffix, so the rocket appears to support both).
+
+For every HTML page there is a corresponding .hidx page that contains a
+summary of the paragraph formatting and the position of the anchor names
+in the associated .html page (See appendix C). This page is sometimes
+compressed, depending on length (See appendix B).
+
+There are also reference titles that have a .hkey page that contains a
+list of words that can be looked up in the associated .html page (See
+appendix D).
+
+Immediately following the ToC is the data for each piece mentioned in
+the ToC, in the same order as it appeared in the ToC.
+
+Finally, the end of the file appears to be padded with 20 bytes of 01h.
+
+
+Appendix A: Info Page Format
+----------------------------
+
+The info page consists of a series of lines that contain "NAME=VALUE"
+strings. Each line is terminated by a single newline. Here are the
+values that the RocketWriter generates:
+
+ COMMENT=Info file for
+ TYPE=2
+ TITLE=
+ AUTHOR=
+ URL=ebook:
+ GENERATOR=
+ PARSE=1
+ OUTPUT=1
+ BODY=
+ MENUMARK=menumark.html
+ SuggestedRetailPrice=
+
+Encrypted titles have a few more entries (including those listed above):
+
+ ISBN=
+ REVISION=
+ TITLE_LANGUAGE=
+ PUB_NAME=
+ PUBSERVER_ID=
+ GENERATOR=
+ VERSION=
+ USERNAME=
+ COPY_ID=
+ COPYRIGHT=
+ COPYTITLE=
+
+A reference title also has an indication that there is a .hkey page
+present, and may also have a GENRE of "Reference":
+
+ HKEY=1
+ GENRE=Reference
+
+
+Appendix B: The format of compressed data
+-----------------------------------------
+
+Compressed pages have a data section in the .rb file with the following
+format:
+
+The first int32 is a count of the number of 4096-byte chunks of data we
+broke the uncompressed page into (the last chunk can be shorter than
+4096 bytes, of course).
+
+This is immediately followed by an int32 with the length of the entire
+uncompressed data.
+
+After this there are int32s that indicate the size of each
+chunk's compressed data.
+
+Following these length int32s is the output from a deflation (the
+algorithm used in gzip) for each 4096-byte chunk of the original data.
+It appears that you must use a window-bit size of 13 and a compression
+level of "best" to be compatible with the Rocket eBook's system software.
+
+
+Appendix C: HTML-index Page Format
+----------------------------------
+
+The .hidx page's purpose is to allow the renderer to quickly look up the
+format of each paragraph (useful for random access to the data), and the
+position of the anchor names.
+
+The first section lists the various paragraph-producing tags. It is
+headed by a line of "[tags ]", where is the number of
+tags that follow this header. The tags are listed one per line, and have
+an implied enumeration from 0 to N-1 (which the other tags and the
+upcoming paragraph sections reference).
+
+The first tag is typically (always?) " -1". The number trailing
+the tag indicates what other tag (or sequence of tags, one per line) in
+which we are nested. So, if we have a
nested inside a , it would be listed separately from a
that was
+nested inside a normal paragraph, and each one would have a different
+trailing index number.
+
+Following the tag section is the paragraph section. The heading is
+"[paragraphs ]", and is followed by a line for each paragraph.
+These lines consist of a character offset into the .html page for the
+start of the paragraph followed by a 0-relative offset into the tag
+section (indicating what kind of formatting to use for the indicated
+paragraph).
+
+The paragraph-section character offsets point to the first bit of text
+after the associated tag.
+
+The last section details the anchor names. The heading is
+"[names ]", and each item that follows is a quoted string of the
+anchor name, followed by a character offset into the .html page where
+we'll find that name. If there are no names in the associated HTML
+section, the heading is included with a 0 count (i.e. "[names 0]").
+
+The name-section character offsets point to the start of the anchor tag
+(not after the tag, like the offsets in the "paragraphs" section).
+
+The lines are terminated by newlines (in standard unix fashion).
+
+For example:
+
+ [tags 10]
+ -1
+ 0
+ 1
+
1
+
1
+
1
+
1
+
6
+
1
+
1
+
+ [paragraphs 42]
+ 160 9
+ 164 9
+ 184 8
+ 220 8
+ 261 6
+ 316 5
+ 359 1
+ 379 6
+ 410 6
+ 460 7
+ 511 7
+ 564 7
+ 616 7
+ 668 7
+ 720 7
+ 773 7
+ 827 7
+ 880 7
+ 933 7
+ 988 7
+ 1043 7
+ 1100 7
+ 1157 7
+ 1214 7
+ 1270 7
+ 1328 7
+ 1385 7
+ 1442 7
+ 1497 7
+ 1556 7
+ 1561 7
+ 1635 1
+ 1656 5
+ 1690 6
+ 1737 7
+ 1773 5
+ 1798 4
+ 1826 3
+ 2663 1
+ 2668 4
+ 2689 2
+ 2730 8
+
+ [names 1]
+ "ch1" 2689
+
+
+Appendix D: HTML-key Page Format
+--------------------------------
+
+The .hkey page contains a list of words, one per line, sorted in a
+strict ASCII sequence, each one followed by a tab and the offset in the
+.html page of the word's data. I presume that the .hkey page must share
+the same name prefix as its related .html page.
+
+If the names contain high-bit characters, they are translated into
+regular ASCII in the .hkey file, since this allows the user to search
+for the words using unaccented characters.
+
+The lines are terminated with a newline (in standard unix fashion).
+
+An example:
+
+ a 5
+ apple 38
+ b 84
+ book 104
+
+Each of these offsets points to a paragraph tag in the associated .html
+page. I have only seen this sequence of tags used so far:
+
+
word other stuff
+
+I have seen multiple ... tags in the middle of the single set of
+... tags, but this is the basic tag format.
+
+The offset in the .hkey page points to the start of the tag.
+
diff --git a/format_docs/tcr.txt b/format_docs/tcr.txt
new file mode 100644
index 0000000000..dbbbbaa869
--- /dev/null
+++ b/format_docs/tcr.txt
@@ -0,0 +1,56 @@
+About
+-----
+
+Text compression format that can be decompressed starting at any point.
+Little-endian byte ordering is used.
+
+
+Header
+------
+
+TCR files always start with:
+
+!!8-Bit!!
+
+
+Layout
+------
+
+Header
+256 key dictionary
+compressed text
+
+
+Dictionary
+----------
+
+A dictionary of key and replacement string. There are a total of 256 keys,
+0 - 255. Each string is preceded with one byte that represents the length of
+the string.
+
+
+Compressed text
+---------------
+
+The compressed text is a series of values 0-255 which correspond to a key and
+thus a string. Reassembling is replacing each key in the compressed text with
+its corresponding string.
+
+
+Compressor
+-----------------
+
+From Andrew Giddings TCR.c (http://www.cix.co.uk/~gidds/Software/TCR.html):
+
+The TCR compression format is easy to describe: after the fixed header is a
+dictionary of 256 strings, each preceded by a length byte. The rest of the
+file is a list of codes from this dictionary.
+
+The compressor works by starting with each code defined as itself. While
+there's an unused code, it finds the most common two-code combination, and
+creates a new code for it, replacing all occurrences in the text with the
+new code.
+
+It also searches for codes that are always followed by another, which it can
+merge, possibly freeing up some.
+
diff --git a/resources/images/news/latimes.png b/resources/images/news/latimes.png
new file mode 100644
index 0000000000..62bb4d0b8a
Binary files /dev/null and b/resources/images/news/latimes.png differ
diff --git a/resources/mime.types b/resources/mime.types
index ab98b3bf4a..a2a67c38f9 100644
--- a/resources/mime.types
+++ b/resources/mime.types
@@ -585,7 +585,6 @@ application/vnd.osa.netdeploy
application/vnd.osgi.bundle
application/vnd.osgi.dp dp
application/vnd.otps.ct-kip+xml
-application/vnd.palm oprc pdb pqa
application/vnd.paos.xml
application/vnd.pg.format str
application/vnd.pg.osasli ei6
@@ -1082,7 +1081,6 @@ chemical/x-ncbi-asn1 asn
chemical/x-ncbi-asn1-ascii ent prt
chemical/x-ncbi-asn1-binary aso val
chemical/x-ncbi-asn1-spec asn
-chemical/x-pdb ent pdb
chemical/x-rosdal ros
chemical/x-swissprot sw
chemical/x-vamas-iso14976 vms
@@ -1379,3 +1377,5 @@ application/x-cbr cbr
application/x-cb7 cb7
application/x-koboreader-ebook kobo
image/wmf wmf
+application/ereader pdb
+
diff --git a/resources/recipes/180.recipe b/resources/recipes/180.recipe
index 5158bb99e0..4f00314795 100644
--- a/resources/recipes/180.recipe
+++ b/resources/recipes/180.recipe
@@ -12,7 +12,7 @@ class Noticias(BasicNewsRecipe):
title = '180.com.uy'
__author__ = 'Gustavo Azambuja'
description = 'Noticias de Uruguay'
- language = 'es'
+ language = 'es_UY'
timefmt = '[%a, %d %b, %Y]'
use_embedded_content = False
recursion = 5
diff --git a/resources/recipes/20_minutos.recipe b/resources/recipes/20_minutos.recipe
index cb3002a76c..106c0dcffa 100644
--- a/resources/recipes/20_minutos.recipe
+++ b/resources/recipes/20_minutos.recipe
@@ -1,25 +1,25 @@
-# -*- coding: utf-8
__license__ = 'GPL v3'
__author__ = 'Luis Hernandez'
__copyright__ = 'Luis Hernandez'
-description = 'Periódico gratuito en español - v0.8 - 27 Jan 2011'
+__version__ = 'v0.85'
+__date__ = '31 January 2011'
'''
www.20minutos.es
'''
-
+import re
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1294946868(BasicNewsRecipe):
- title = u'20 Minutos'
+ title = u'20 Minutos new'
publisher = u'Grupo 20 Minutos'
- __author__ = 'Luis Hernández'
- description = 'Periódico gratuito en español'
+ __author__ = 'Luis Hernandez'
+ description = 'Free spanish newspaper'
cover_url = 'http://estaticos.20minutos.es/mmedia/especiales/corporativo/css/img/logotipos_grupo20minutos.gif'
- oldest_article = 5
+ oldest_article = 2
max_articles_per_feed = 100
remove_javascript = True
@@ -29,6 +29,7 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
encoding = 'ISO-8859-1'
language = 'es'
timefmt = '[%a, %d %b, %Y]'
+ remove_empty_feeds = True
keep_only_tags = [
dict(name='div', attrs={'id':['content','vinetas',]})
@@ -43,13 +44,21 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
remove_tags = [
dict(name='ol', attrs={'class':['navigation',]})
,dict(name='span', attrs={'class':['action']})
- ,dict(name='div', attrs={'class':['twitter comments-list hidden','related-news','col','photo-gallery','calendario','article-comment','postto estirar','otras_vinetas estirar','kment','user-actions']})
+ ,dict(name='div', attrs={'class':['twitter comments-list hidden','related-news','col','photo-gallery','photo-gallery side-art-block','calendario','article-comment','postto estirar','otras_vinetas estirar','kment','user-actions']})
,dict(name='div', attrs={'id':['twitter-destacados','eco-tabs','inner','vineta_calendario','vinetistas clearfix','otras_vinetas estirar','MIN1','main','SUP1','INT']})
,dict(name='ul', attrs={'class':['article-user-actions','stripped-list']})
,dict(name='ul', attrs={'id':['site-links']})
,dict(name='li', attrs={'class':['puntuacion','enviar','compartir']})
]
+ extra_css = """
+ p{text-align: justify; font-size: 100%}
+ body{ text-align: left; font-size:100% }
+ h3{font-family: sans-serif; font-size:150%; font-weight:bold; text-align: justify; }
+ """
+
+ preprocess_regexps = [(re.compile(r'', re.DOTALL), lambda m: '')]
+
feeds = [
(u'Portada' , u'http://www.20minutos.es/rss/')
,(u'Nacional' , u'http://www.20minutos.es/rss/nacional/')
@@ -65,6 +74,6 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
,(u'Empleo' , u'http://www.20minutos.es/rss/empleo/')
,(u'Cine' , u'http://www.20minutos.es/rss/cine/')
,(u'Musica' , u'http://www.20minutos.es/rss/musica/')
- ,(u'Vinetas' , u'http://www.20minutos.es/rss/vinetas/')
+ ,(u'Vinetas' , u'http://www.20minutos.es/rss/vinetas/')
,(u'Comunidad20' , u'http://www.20minutos.es/rss/zona20/')
]
diff --git a/resources/recipes/7dias.recipe b/resources/recipes/7dias.recipe
index e111617b8d..3dc93295a8 100644
--- a/resources/recipes/7dias.recipe
+++ b/resources/recipes/7dias.recipe
@@ -20,7 +20,7 @@ class SieteDias(BasicNewsRecipe):
no_stylesheets = True
use_embedded_content = False
encoding = 'utf-8'
- language = 'es'
+ language = 'es_AR'
lang = 'es-AR'
direction = 'ltr'
diff --git a/resources/recipes/ambito.recipe b/resources/recipes/ambito.recipe
index 7074463e34..dd92ee19b3 100644
--- a/resources/recipes/ambito.recipe
+++ b/resources/recipes/ambito.recipe
@@ -58,4 +58,4 @@ class Ambito(BasicNewsRecipe):
del item['style']
return soup
- language = 'es'
+ language = 'es_AR'
diff --git a/resources/recipes/animal_politico.recipe b/resources/recipes/animal_politico.recipe
index f48587ea94..5e57e266bc 100644
--- a/resources/recipes/animal_politico.recipe
+++ b/resources/recipes/animal_politico.recipe
@@ -12,7 +12,7 @@ class AdvancedUserRecipe1290663986(BasicNewsRecipe):
masthead_url = 'http://www.animalpolitico.com/wp-content/themes/animal_mu/images/logo.png'
oldest_article = 1
max_articles_per_feed = 100
- language = 'es'
+ language = 'es_MX'
#feeds = [(u'Animal Politico', u'http://www.animalpolitico.com/feed/')]
diff --git a/resources/recipes/axxon_magazine.recipe b/resources/recipes/axxon_magazine.recipe
index 331e6fe2b5..93cb5cd03b 100644
--- a/resources/recipes/axxon_magazine.recipe
+++ b/resources/recipes/axxon_magazine.recipe
@@ -17,7 +17,7 @@ class Axxon_news(BasicNewsRecipe):
max_articles_per_feed = 100
no_stylesheets = False
use_embedded_content = False
- language = 'es'
+ language = 'es_AR'
encoding = 'utf-8'
publication_type = 'magazine'
INDEX = 'http://axxon.com.ar/rev/'
diff --git a/resources/recipes/axxon_news.recipe b/resources/recipes/axxon_news.recipe
index a9a99e1de1..3d0f8effd0 100644
--- a/resources/recipes/axxon_news.recipe
+++ b/resources/recipes/axxon_news.recipe
@@ -18,7 +18,7 @@ class Axxon_news(BasicNewsRecipe):
max_articles_per_feed = 100
no_stylesheets = False
use_embedded_content = False
- language = 'es'
+ language = 'es_AR'
lang = 'es-AR'
diff --git a/resources/recipes/bbc_es.recipe b/resources/recipes/bbc_es.recipe
new file mode 100644
index 0000000000..b2b6192620
--- /dev/null
+++ b/resources/recipes/bbc_es.recipe
@@ -0,0 +1,53 @@
+__license__ = 'GPL v3'
+__author__ = 'Luis Hernandez'
+__copyright__ = 'Luis Hernandez'
+__version__ = 'v1.0'
+__date__ = '29 January 2011'
+
+'''
+http://www.bbc.co.uk/mundo/
+'''
+
+from calibre.web.feeds.news import BasicNewsRecipe
+
+class AdvancedUserRecipe1294946868(BasicNewsRecipe):
+
+ title = u'BBC Mundo'
+ publisher = u'BBC'
+
+ __author__ = 'Luis Hernandez'
+ description = 'BBC World for spanish readers'
+
+ cover_url = 'http://1.bp.blogspot.com/_NHiOjk_uZwU/TEYy7IJAdAI/AAAAAAAABP8/coAE-pJ7_5E/s1600/bbcmundo_h.png'
+ oldest_article = 2
+ max_articles_per_feed = 100
+
+ remove_javascript = True
+ no_stylesheets = True
+ use_embedded_content = False
+
+ language = 'es'
+ remove_empty_feeds = True
+ encoding = 'UTF-8'
+ timefmt = '[%a, %d %b, %Y]'
+
+ remove_tags_before = dict(name='div' , attrs={'class':['g-group']})
+ remove_tags_after = dict(name='div' , attrs={'class':[' g-w8']})
+
+ remove_tags = [
+ dict(name='ul', attrs={'class':['document-tools blq-clearfix','blq-clearfix']})
+ ,dict(name='div', attrs={'class':['box bx-quote-bubble','socialmedia-links','list li-carousel','list li-plain rolling-news','list li-plain','box bx-livestats','li-tab content','list li-relatedlinks','list li-relatedinternetlinks']})
+ ]
+
+ feeds = [
+ (u'Portada' , u'http://www.bbc.co.uk/mundo/index.xml')
+ ,(u'Ultimas Noticias' , u'http://www.bbc.co.uk/mundo/ultimas_noticias/index.xml')
+ ,(u'Internacional' , u'http://www.bbc.co.uk/mundo/temas/internacional/index.xml')
+ ,(u'Economia' , u'http://www.bbc.co.uk/mundo/temas/economia/index.xml')
+ ,(u'America Latina' , u'http://www.bbc.co.uk/mundo/temas/america_latina/index.xml')
+ ,(u'Ciencia' , u'http://www.bbc.co.uk/mundo/temas/ciencia/index.xml')
+ ,(u'Salud' , u'http://www.bbc.co.uk/mundo/temas/salud/index.xml')
+ ,(u'Tecnologia' , u'http://www.bbc.co.uk/mundo/temas/tecnologia/index.xml')
+ ,(u'Cultura' , u'http://www.bbc.co.uk/mundo/temas/cultura/index.xml')
+ ]
+
diff --git a/resources/recipes/bitacora.recipe b/resources/recipes/bitacora.recipe
index a36eb52988..7f41f8f32b 100644
--- a/resources/recipes/bitacora.recipe
+++ b/resources/recipes/bitacora.recipe
@@ -12,7 +12,7 @@ class General(BasicNewsRecipe):
title = 'bitacora.com.uy'
__author__ = 'Gustavo Azambuja'
description = 'Noticias de Uruguay'
- language = 'es'
+ language = 'es_UY'
timefmt = '[%a, %d %b, %Y]'
use_embedded_content = False
recursion = 5
diff --git a/resources/recipes/buenosaireseconomico.recipe b/resources/recipes/buenosaireseconomico.recipe
index c9f89269b4..782358e6d3 100644
--- a/resources/recipes/buenosaireseconomico.recipe
+++ b/resources/recipes/buenosaireseconomico.recipe
@@ -20,7 +20,7 @@ class BsAsEconomico(BasicNewsRecipe):
no_stylesheets = True
use_embedded_content = False
encoding = 'utf-8'
- language = 'es'
+ language = 'es_AR'
lang = 'es-AR'
direction = 'ltr'
diff --git a/resources/recipes/cinco_dias.recipe b/resources/recipes/cinco_dias.recipe
new file mode 100644
index 0000000000..40241aff5c
--- /dev/null
+++ b/resources/recipes/cinco_dias.recipe
@@ -0,0 +1,71 @@
+__license__ = 'GPL v3'
+__author__ = 'Luis Hernandez'
+__copyright__ = 'Luis Hernandez'
+__version__ = 'v1.2'
+__date__ = '31 January 2011'
+
+'''
+http://www.cincodias.com/
+'''
+
+from calibre.web.feeds.news import BasicNewsRecipe
+
+class AdvancedUserRecipe1294946868(BasicNewsRecipe):
+
+ title = u'Cinco Dias'
+ publisher = u'Grupo Prisa'
+
+ __author__ = 'Luis Hernandez'
+ description = 'spanish web about money and bussiness, free edition'
+
+ cover_url = 'http://www.prisa.com/images/logos/logo_cinco_dias.gif'
+ oldest_article = 2
+ max_articles_per_feed = 100
+
+ remove_javascript = True
+ no_stylesheets = True
+ use_embedded_content = False
+
+ language = 'es'
+ remove_empty_feeds = True
+ encoding = 'ISO-8859-1'
+ timefmt = '[%a, %d %b, %Y]'
+
+ keep_only_tags = [
+ dict(name='div', attrs={'class':['cab_articulo cab_noticia','pos_3','txt_noticia','mod_despiece']})
+ ,dict(name='p', attrs={'class':['cintillo']})
+ ]
+
+ remove_tags_before = dict(name='div' , attrs={'class':['publi_h']})
+ remove_tags_after = dict(name='div' , attrs={'class':['tab_util util_estadisticas']})
+
+ remove_tags = [
+ dict(name='div', attrs={'class':['util-1','util-2','util-3','inner estirar','inner1','inner2','inner3','cont','tab_util util_estadisticas','tab_util util_enviar','mod_list_inf','mod_similares','mod_divisas','mod_sectores','mod_termometro','mod post','mod_img','mod_txt','nivel estirar','barra estirar','info_brujula btnBrujula','utilidad_brujula estirar']})
+ ,dict(name='li', attrs={'class':['lnk-fcbook','lnk-retweet','lnk-meneame','desplegable','comentarios','list-options','estirar']})
+ ,dict(name='ul', attrs={'class':['lista-izquierda','list-options','estirar']})
+ ,dict(name='p', attrs={'class':['autor']})
+ ]
+
+ extra_css = """
+ p{text-align: justify; font-size: 100%}
+ body{ text-align: left; font-size:100% }
+ h1{font-family: sans-serif; font-size:150%; font-weight:bold; text-align: justify; }
+ h3{font-family: sans-serif; font-size:100%; font-style: italic; text-align: justify; }
+ """
+
+ feeds = [
+ (u'Ultima Hora' , u'http://www.cincodias.com/rss/feed.html?feedId=17029')
+ ,(u'Empresas' , u'http://www.cincodias.com/rss/feed.html?feedId=19')
+ ,(u'Mercados' , u'http://www.cincodias.com/rss/feed.html?feedId=20')
+ ,(u'Economia' , u'http://www.cincodias.com/rss/feed.html?feedId=21')
+ ,(u'Tecnorama' , u'http://www.cincodias.com/rss/feed.html?feedId=17230')
+ ,(u'Tecnologia' , u'http://www.cincodias.com/rss/feed.html?feedId=17106')
+ ,(u'Finanzas Personales' , u'http://www.cincodias.com/rss/feed.html?feedId=22')
+ ,(u'Fiscalidad' , u'http://www.cincodias.com/rss/feed.html?feedId=17107')
+ ,(u'Vivienda' , u'http://www.cincodias.com/rss/feed.html?feedId=17108')
+ ,(u'Tendencias' , u'http://www.cincodias.com/rss/feed.html?feedId=17109')
+ ,(u'Empleo' , u'http://www.cincodias.com/rss/feed.html?feedId=17110')
+ ,(u'IBEX 35' , u'http://www.cincodias.com/rss/feed.html?feedId=17125')
+ ,(u'Sectores' , u'http://www.cincodias.com/rss/feed.html?feedId=17126')
+ ,(u'Opinion' , u'http://www.cincodias.com/rss/feed.html?feedId=17105')
+ ]
diff --git a/resources/recipes/clarin.recipe b/resources/recipes/clarin.recipe
index cf9440ad55..7bbb663d1d 100644
--- a/resources/recipes/clarin.recipe
+++ b/resources/recipes/clarin.recipe
@@ -18,7 +18,7 @@ class Clarin(BasicNewsRecipe):
use_embedded_content = False
no_stylesheets = True
encoding = 'utf8'
- language = 'es'
+ language = 'es_AR'
publication_type = 'newspaper'
INDEX = 'http://www.clarin.com'
masthead_url = 'http://www.clarin.com/static/CLAClarin/images/logo-clarin-print.jpg'
diff --git a/resources/recipes/criticadigital.recipe b/resources/recipes/criticadigital.recipe
index d1ef97aef9..3cb72e6be4 100644
--- a/resources/recipes/criticadigital.recipe
+++ b/resources/recipes/criticadigital.recipe
@@ -14,7 +14,7 @@ class CriticaDigital(BasicNewsRecipe):
description = 'Noticias de Argentina'
oldest_article = 2
max_articles_per_feed = 100
- language = 'es'
+ language = 'es_AR'
no_stylesheets = True
use_embedded_content = False
diff --git a/resources/recipes/cubadebate.recipe b/resources/recipes/cubadebate.recipe
index f8887b2672..d563f0f86e 100644
--- a/resources/recipes/cubadebate.recipe
+++ b/resources/recipes/cubadebate.recipe
@@ -11,7 +11,7 @@ class CubaDebate(BasicNewsRecipe):
__author__ = 'Darko Miletic'
description = 'Contra el Terorismo Mediatico'
oldest_article = 15
- language = 'es'
+ language = 'es_CU'
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
@@ -20,8 +20,8 @@ class CubaDebate(BasicNewsRecipe):
encoding = 'utf-8'
masthead_url = 'http://www.cubadebate.cu/wp-content/themes/cubadebate/images/logo.gif'
publication_type = 'newsportal'
- extra_css = """
- #BlogTitle{font-size: xx-large; font-weight: bold}
+ extra_css = """
+ #BlogTitle{font-size: xx-large; font-weight: bold}
body{font-family: Verdana, Arial, Tahoma, sans-serif}
"""
@@ -41,7 +41,7 @@ class CubaDebate(BasicNewsRecipe):
feeds = [(u'Articulos', u'http://www.cubadebate.cu/feed/')]
remove_attributes=['width','height','lang']
-
+
def print_version(self, url):
return url + 'print/'
@@ -50,5 +50,5 @@ class CubaDebate(BasicNewsRecipe):
del item['style']
for item in soup.findAll('img'):
if not item.has_key('alt'):
- item['alt'] = 'image'
+ item['alt'] = 'image'
return soup
diff --git a/resources/recipes/deutsche_welle_es.recipe b/resources/recipes/deutsche_welle_es.recipe
index ab80b2f11f..c68a03b981 100644
--- a/resources/recipes/deutsche_welle_es.recipe
+++ b/resources/recipes/deutsche_welle_es.recipe
@@ -16,7 +16,7 @@ class DeutscheWelle_es(BasicNewsRecipe):
max_articles_per_feed = 100
use_embedded_content = False
no_stylesheets = True
- language = 'es'
+ language = 'de_ES'
publication_type = 'newsportal'
remove_empty_feeds = True
masthead_url = 'http://www.dw-world.de/skins/std/channel1/pics/dw_logo1024.gif'
diff --git a/resources/recipes/diagonales.recipe b/resources/recipes/diagonales.recipe
index 1604967e63..eff06efc1d 100644
--- a/resources/recipes/diagonales.recipe
+++ b/resources/recipes/diagonales.recipe
@@ -20,7 +20,7 @@ class Diagonales(BasicNewsRecipe):
no_stylesheets = True
use_embedded_content = False
encoding = 'utf-8'
- language = 'es'
+ language = 'es_AR'
lang = 'es-AR'
direction = 'ltr'
diff --git a/resources/recipes/el_mercurio_chile.recipe b/resources/recipes/el_mercurio_chile.recipe
index a8371f5af9..df4d027af3 100644
--- a/resources/recipes/el_mercurio_chile.recipe
+++ b/resources/recipes/el_mercurio_chile.recipe
@@ -20,8 +20,8 @@ class ElMercurio(BasicNewsRecipe):
masthead_url = 'http://www.emol.com/especiales/logo_emol/logo_emol.gif'
remove_javascript = True
use_embedded_content = False
- language = 'es'
-
+ language = 'es_CL'
+
conversion_options = {
'comment' : description
@@ -33,7 +33,7 @@ class ElMercurio(BasicNewsRecipe):
keep_only_tags = [dict(name='div', attrs={'id':['cont_iz_titulobajada','cont_iz_creditos_1_a','cont_iz_cuerpo']})]
remove_tags = [dict(name='div', attrs={'id':'cont_iz_cuerpo_relacionados'})]
remove_attributes = ['height','width']
-
+
feeds = [
(u'Noticias de ultima hora', u'http://rss.emol.com/rss.asp?canal=0')
,(u'Nacional', u'http://rss.emol.com/rss.asp?canal=1')
diff --git a/resources/recipes/el_observador.recipe b/resources/recipes/el_observador.recipe
index 6338426d59..994963671e 100644
--- a/resources/recipes/el_observador.recipe
+++ b/resources/recipes/el_observador.recipe
@@ -13,7 +13,7 @@ class ObservaDigital(BasicNewsRecipe):
title = 'Observa Digital'
__author__ = 'yrvn'
description = 'Noticias de Uruguay'
- language = 'es'
+ language = 'es_UY'
timefmt = '[%a, %d %b, %Y]'
use_embedded_content = False
recursion = 5
diff --git a/resources/recipes/el_pais_uy.recipe b/resources/recipes/el_pais_uy.recipe
index 1e9c400162..aceedf0b8c 100644
--- a/resources/recipes/el_pais_uy.recipe
+++ b/resources/recipes/el_pais_uy.recipe
@@ -14,7 +14,7 @@ class General(BasicNewsRecipe):
description = 'Noticias de Uruguay y el resto del mundo'
publisher = 'EL PAIS S.A.'
category = 'news, politics, Uruguay'
- language = 'es'
+ language = 'es_UY'
timefmt = '[%a, %d %b, %Y]'
use_embedded_content = False
recursion = 2
diff --git a/resources/recipes/el_universal.recipe b/resources/recipes/el_universal.recipe
index f053812c05..441f77cbe1 100644
--- a/resources/recipes/el_universal.recipe
+++ b/resources/recipes/el_universal.recipe
@@ -20,7 +20,7 @@ class ElUniversal(BasicNewsRecipe):
remove_javascript = True
remove_empty_feeds = True
publication_type = 'newspaper'
- language = 'es'
+ language = 'es_MX'
extra_css = '''
body{font-family:Arial,Helvetica,sans-serif}
diff --git a/resources/recipes/elargentino.recipe b/resources/recipes/elargentino.recipe
index c33b845e0a..6086ff6b59 100644
--- a/resources/recipes/elargentino.recipe
+++ b/resources/recipes/elargentino.recipe
@@ -12,7 +12,7 @@ class ElArgentino(BasicNewsRecipe):
__author__ = 'Darko Miletic'
description = 'Informacion Libre las 24 horas'
publisher = 'ElArgentino.com'
- category = 'news, politics, Argentina'
+ category = 'news, politics, Argentina'
oldest_article = 2
max_articles_per_feed = 100
remove_javascript = True
@@ -20,7 +20,7 @@ class ElArgentino(BasicNewsRecipe):
use_embedded_content = False
encoding = 'utf8'
cover_url = 'http://www.elargentino.com/TemplateWeb/MediosFooter/tapa_elargentino.png'
- language = 'es'
+ language = 'es_AR'
html2lrf_options = [
@@ -28,16 +28,16 @@ class ElArgentino(BasicNewsRecipe):
, '--category', category
, '--publisher', publisher
]
-
- html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
+
+ html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
remove_tags = [
dict(name='div', attrs={'id':'noprint' })
,dict(name='div', attrs={'class':'encabezadoImprimir'})
,dict(name='a' , attrs={'target':'_blank' })
]
-
- feeds = [
+
+ feeds = [
(u'Portada' , u'http://www.elargentino.com/Highlights.aspx?Content-Type=text/xml&ChannelDesc=Home' )
,(u'Pais' , u'http://www.elargentino.com/Highlights.aspx?ParentType=Section&ParentId=112&Content-Type=text/xml&ChannelDesc=Pa%C3%ADs' )
,(u'Economia' , u'http://www.elargentino.com/Highlights.aspx?ParentType=Section&ParentId=107&Content-Type=text/xml&ChannelDesc=Econom%C3%ADa' )
@@ -51,12 +51,12 @@ class ElArgentino(BasicNewsRecipe):
def print_version(self, url):
main, sep, article_part = url.partition('/nota-')
- article_id, rsep, rrest = article_part.partition('-')
+ article_id, rsep, rrest = article_part.partition('-')
return u'http://www.elargentino.com/Impresion.aspx?Id=' + article_id
def preprocess_html(self, soup):
mtag = '\n\n'
soup.head.insert(0,mtag)
for item in soup.findAll(style=True):
- del item['style']
+ del item['style']
return soup
diff --git a/resources/recipes/elcomercio.recipe b/resources/recipes/elcomercio.recipe
index 37733bda8b..69c513a835 100644
--- a/resources/recipes/elcomercio.recipe
+++ b/resources/recipes/elcomercio.recipe
@@ -18,7 +18,7 @@ class ElComercio(BasicNewsRecipe):
no_stylesheets = True
encoding = 'utf-8'
use_embedded_content = True
- language = 'es'
+ language = 'es_EC'
masthead_url = 'http://ww1.elcomercio.com/nv_images/headers/EC/logo_new_08.gif'
extra_css = ' body{font-family: Arial,Verdana,sans-serif} img{margin-bottom: 1em} '
diff --git a/resources/recipes/elcronista.recipe b/resources/recipes/elcronista.recipe
index d96a9746d6..93615f8f42 100644
--- a/resources/recipes/elcronista.recipe
+++ b/resources/recipes/elcronista.recipe
@@ -13,7 +13,7 @@ class ElCronista(BasicNewsRecipe):
__author__ = 'Darko Miletic'
description = 'Noticias de Argentina'
oldest_article = 2
- language = 'es'
+ language = 'es_AR'
max_articles_per_feed = 100
no_stylesheets = True
@@ -25,14 +25,14 @@ class ElCronista(BasicNewsRecipe):
, '--category' , 'news, Argentina'
, '--publisher' , title
]
-
+
keep_only_tags = [
dict(name='table', attrs={'width':'100%' })
,dict(name='h1' , attrs={'class':'Arialgris16normal'})
]
remove_tags = [dict(name='a', attrs={'class':'Arialazul12'})]
-
+
feeds = [
(u'Economia' , u'http://www.cronista.com/adjuntos/8/rss/Economia_EI.xml' )
,(u'Negocios' , u'http://www.cronista.com/adjuntos/8/rss/negocios_EI.xml' )
@@ -69,4 +69,4 @@ class ElCronista(BasicNewsRecipe):
if link_item:
cover_url = index + link_item.img['src']
return cover_url
-
+
diff --git a/resources/recipes/eltiempo_hn.recipe b/resources/recipes/eltiempo_hn.recipe
index e4d11466ee..81d1ecbb9d 100644
--- a/resources/recipes/eltiempo_hn.recipe
+++ b/resources/recipes/eltiempo_hn.recipe
@@ -21,7 +21,7 @@ class ElTiempoHn(BasicNewsRecipe):
no_stylesheets = True
remove_javascript = True
encoding = 'utf-8'
- language = 'es'
+ language = 'es_HN'
lang = 'es-HN'
direction = 'ltr'
diff --git a/resources/recipes/eluniversal_ve.recipe b/resources/recipes/eluniversal_ve.recipe
index af5d75e3e7..28667cd39b 100644
--- a/resources/recipes/eluniversal_ve.recipe
+++ b/resources/recipes/eluniversal_ve.recipe
@@ -18,7 +18,7 @@ class ElUniversal(BasicNewsRecipe):
encoding = 'cp1252'
publisher = 'El Universal'
category = 'news, Caracas, Venezuela, world'
- language = 'es'
+ language = 'es_VE'
cover_url = strftime('http://static.eluniversal.com/%Y/%m/%d/portada.jpg')
conversion_options = {
diff --git a/resources/recipes/eluniversalimpresa.recipe b/resources/recipes/eluniversalimpresa.recipe
index 7263a76e2a..a48556eedb 100644
--- a/resources/recipes/eluniversalimpresa.recipe
+++ b/resources/recipes/eluniversalimpresa.recipe
@@ -3,7 +3,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
class ElUniversalImpresaRecipe(BasicNewsRecipe):
__license__ = 'GPL v3'
__author__ = 'kwetal'
- language = 'es'
+ language = 'es_MX'
version = 1
title = u'El Universal (Edici\u00F3n Impresa)'
diff --git a/resources/recipes/eluniverso_ec.recipe b/resources/recipes/eluniverso_ec.recipe
index a0e8b46474..8696e9d2f6 100644
--- a/resources/recipes/eluniverso_ec.recipe
+++ b/resources/recipes/eluniverso_ec.recipe
@@ -17,7 +17,7 @@ class ElUniverso_Ecuador(BasicNewsRecipe):
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
- language = 'es'
+ language = 'es_EC'
remove_empty_feeds = True
publication_type = 'newspaper'
masthead_url = 'http://servicios2.eluniverso.com/versiones/v1/img/Hd/lg_ElUniverso.gif'
diff --git a/resources/recipes/endgadget_ja.recipe b/resources/recipes/endgadget_ja.recipe
index 891e6720a5..3c20380e9b 100644
--- a/resources/recipes/endgadget_ja.recipe
+++ b/resources/recipes/endgadget_ja.recipe
@@ -18,3 +18,6 @@ class EndgadgetJapan(BasicNewsRecipe):
language = 'ja'
encoding = 'utf-8'
feeds = [(u'engadget', u'http://japanese.engadget.com/rss.xml')]
+
+ remove_tags_before = dict(name="div", attrs={'id':"content_wrap"})
+ remove_tags_after = dict(name='h3', attrs={'id':'addcomments'})
diff --git a/resources/recipes/explosm.recipe b/resources/recipes/explosm.recipe
new file mode 100644
index 0000000000..8cdff609cb
--- /dev/null
+++ b/resources/recipes/explosm.recipe
@@ -0,0 +1,54 @@
+from calibre.web.feeds.news import BasicNewsRecipe
+import re
+
+class Explosm(BasicNewsRecipe):
+ title = u'Explosm Rotated'
+ __author__ = 'Andromeda Rabbit'
+ description = 'Explosm'
+ language = 'en'
+ use_embedded_content = False
+ no_stylesheets = True
+ oldest_article = 24
+ remove_javascript = True
+ remove_empty_feeds = True
+ max_articles_per_feed = 10
+
+ feeds = [
+ (u'Explosm Feed', u'http://feeds.feedburner.com/Explosm')
+ ]
+
+ #match_regexps = [r'http://www.explosm.net/comics/.*']
+
+ keep_only_tags = [dict(name='img', attrs={'alt':'Cyanide and Happiness, a daily webcomic'})]
+ remove_tags = [dict(name='div'), dict(name='span'), dict(name='table'), dict(name='br'), dict(name='nobr'), dict(name='a'), dict(name='b')]
+
+ extra_css = '''
+ h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
+ h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
+ p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
+ body{font-family:Helvetica,Arial,sans-serif;font-size:small;}'''
+
+ def get_cover_url(self):
+ return 'http://cdn.shopify.com/s/files/1/0059/1872/products/cyanidetitle_large.jpg?1295846286'
+
+ def parse_feeds(self):
+ feeds = BasicNewsRecipe.parse_feeds(self)
+
+ for curfeed in feeds:
+ delList = []
+ for a,curarticle in enumerate(curfeed.articles):
+ if re.search(r'http://www.explosm.net/comics', curarticle.url) == None:
+ delList.append(curarticle)
+ if len(delList)>0:
+ for d in delList:
+ index = curfeed.articles.index(d)
+ curfeed.articles[index:index+1] = []
+
+ return feeds
+
+ def skip_ad_pages(self, soup):
+ # Skip ad pages served before actual article
+ skip_tag = soup.find(name='img', attrs={'alt':'Cyanide and Happiness, a daily webcomic'})
+ if skip_tag is None:
+ return soup
+ return None
diff --git a/resources/recipes/freeway.recipe b/resources/recipes/freeway.recipe
index cb6d41ebb2..c50c35d8ca 100644
--- a/resources/recipes/freeway.recipe
+++ b/resources/recipes/freeway.recipe
@@ -12,7 +12,7 @@ class General(BasicNewsRecipe):
title = 'freeway.com.uy'
__author__ = 'Gustavo Azambuja'
description = 'Revista Freeway, Montevideo, Uruguay'
- language = 'es'
+ language = 'es_UY'
timefmt = '[%a, %d %b, %Y]'
use_embedded_content = False
recursion = 1
diff --git a/resources/recipes/granma.recipe b/resources/recipes/granma.recipe
index 75e89a3d03..01f383cb68 100644
--- a/resources/recipes/granma.recipe
+++ b/resources/recipes/granma.recipe
@@ -20,7 +20,7 @@ class Granma(BasicNewsRecipe):
use_embedded_content = False
encoding = 'cp1252'
cover_url = 'http://www.granma.cubaweb.cu/imagenes/granweb229d.jpg'
- language = 'es'
+ language = 'es_CU'
remove_javascript = True
diff --git a/resources/recipes/ieco.recipe b/resources/recipes/ieco.recipe
index 8c59d033a1..eb11c103d2 100644
--- a/resources/recipes/ieco.recipe
+++ b/resources/recipes/ieco.recipe
@@ -18,7 +18,7 @@ class iEco(BasicNewsRecipe):
encoding = 'utf-8'
publisher = 'Grupo Clarin'
category = 'news, economia, mercados, bolsa de valores, finanzas, empresas, negocios, empleos, emprendedores, marketinguniversidades, tecnologia, agronegocios, noticias, informacion'
- language = 'es'
+ language = 'es_AR'
cover_url = 'http://www.ieco.clarin.com/static2/images/Tapa-PDF.gif'
extra_css = ' #bd{font-family: sans-serif} '
diff --git a/resources/recipes/infobae.recipe b/resources/recipes/infobae.recipe
index b7f9cd3c6c..9553746449 100644
--- a/resources/recipes/infobae.recipe
+++ b/resources/recipes/infobae.recipe
@@ -16,7 +16,7 @@ class Infobae(BasicNewsRecipe):
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
- language = 'es'
+ language = 'es_AR'
encoding = 'cp1252'
masthead_url = 'http://www.infobae.com/imgs/header/header.gif'
remove_javascript = True
@@ -25,7 +25,7 @@ class Infobae(BasicNewsRecipe):
body{font-family:Arial,Helvetica,sans-serif;}
.popUpTitulo{color:#0D4261; font-size: xx-large}
'''
-
+
conversion_options = {
'comment' : description
, 'tags' : category
@@ -33,7 +33,7 @@ class Infobae(BasicNewsRecipe):
, 'language' : language
, 'linearize_tables' : True
}
-
+
feeds = [
(u'Noticias' , u'http://www.infobae.com/adjuntos/html/RSS/hoy.xml' )
diff --git a/resources/recipes/juventudrebelde.recipe b/resources/recipes/juventudrebelde.recipe
index dd908d57b2..93d550027a 100644
--- a/resources/recipes/juventudrebelde.recipe
+++ b/resources/recipes/juventudrebelde.recipe
@@ -20,7 +20,7 @@ class Juventudrebelde(BasicNewsRecipe):
no_stylesheets = True
use_embedded_content = False
encoding = 'cp1252'
- language = 'es'
+ language = 'es_CU'
cover_url = strftime('http://www.juventudrebelde.cu/UserFiles/File/impreso/iportada-%Y-%m-%d.jpg')
remove_javascript = True
diff --git a/resources/recipes/la_cuarta.recipe b/resources/recipes/la_cuarta.recipe
index ad1a6a975e..10d6b6ce3a 100644
--- a/resources/recipes/la_cuarta.recipe
+++ b/resources/recipes/la_cuarta.recipe
@@ -50,4 +50,4 @@ class LaCuarta(BasicNewsRecipe):
feeds = [(u'Noticias', u'http://lacuarta.cl/app/rss?sc=TEFDVUFSVEE=')]
- language = 'es'
+ language = 'es_CL'
diff --git a/resources/recipes/la_diaria.recipe b/resources/recipes/la_diaria.recipe
index d89eb465dd..8aa98483c0 100644
--- a/resources/recipes/la_diaria.recipe
+++ b/resources/recipes/la_diaria.recipe
@@ -12,7 +12,7 @@ class General(BasicNewsRecipe):
title = 'La Diaria'
__author__ = 'Gustavo Azambuja'
description = 'Noticias de Uruguay'
- language = 'es'
+ language = 'es_UY'
timefmt = '[%a, %d %b, %Y]'
use_embedded_content = False
recursion = 5
diff --git a/resources/recipes/la_jornada.recipe b/resources/recipes/la_jornada.recipe
index 3ee6dc4b3f..71c526a0a0 100644
--- a/resources/recipes/la_jornada.recipe
+++ b/resources/recipes/la_jornada.recipe
@@ -19,7 +19,7 @@ class LaJornada_mx(BasicNewsRecipe):
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
- language = 'es'
+ language = 'es_MX'
remove_empty_feeds = True
cover_url = strftime("http://www.jornada.unam.mx/%Y/%m/%d/portada.pdf")
masthead_url = 'http://www.jornada.unam.mx/v7.0/imagenes/la-jornada-trans.png'
@@ -34,8 +34,8 @@ class LaJornada_mx(BasicNewsRecipe):
.credito{font-weight: bold; margin-left: 1em}
.credito-autor{font-variant: small-caps; font-weight: bold }
.credito-titulo{text-align: right}
- .hemero{text-align: right; font-size: 0.9em; margin-bottom: 0.5em }
- .loc{font-weight: bold}
+ .hemero{text-align: right; font-size: 0.9em; margin-bottom: 0.5em }
+ .loc{font-weight: bold}
.carton{text-align: center}
.credit{font-weight: bold}
.sumario{font-weight: bold; text-align: center}
@@ -56,7 +56,7 @@ class LaJornada_mx(BasicNewsRecipe):
,re.DOTALL|re.IGNORECASE)
,lambda match: '' + match.group(1) + '
')
]
-
+
keep_only_tags = [
dict(name='div', attrs={'class':['documentContent','cabeza','sumarios','credito-articulo','text','carton']})
,dict(name='div', attrs={'id':'renderComments'})
@@ -88,4 +88,4 @@ class LaJornada_mx(BasicNewsRecipe):
def get_article_url(self, article):
rurl = article.get('link', None)
return rurl.rpartition('&partner=')[0]
-
+
diff --git a/resources/recipes/la_razon_bo.recipe b/resources/recipes/la_razon_bo.recipe
index 18a00d6763..6af899b760 100644
--- a/resources/recipes/la_razon_bo.recipe
+++ b/resources/recipes/la_razon_bo.recipe
@@ -18,7 +18,7 @@ class LaRazon_Bol(BasicNewsRecipe):
no_stylesheets = True
encoding = 'cp1252'
use_embedded_content = False
- language = 'es'
+ language = 'es_BO'
publication_type = 'newspaper'
delay = 1
remove_empty_feeds = True
diff --git a/resources/recipes/la_segunda.recipe b/resources/recipes/la_segunda.recipe
index 537bff4104..8ce25e35b3 100644
--- a/resources/recipes/la_segunda.recipe
+++ b/resources/recipes/la_segunda.recipe
@@ -9,7 +9,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
class LaSegunda(BasicNewsRecipe):
title = 'La Segunda'
__author__ = 'Darko Miletic'
- description = 'El sitio de noticias online de Chile'
+ description = 'El sitio de noticias online de Chile'
publisher = 'La Segunda'
category = 'news, politics, Chile'
oldest_article = 2
@@ -19,9 +19,9 @@ class LaSegunda(BasicNewsRecipe):
encoding = 'cp1252'
masthead_url = 'http://www.lasegunda.com/imagenes/logotipo_lasegunda_Oli.gif'
remove_empty_feeds = True
- language = 'es'
- extra_css = ' .titulonegritastop{font-size: xx-large; font-weight: bold} '
-
+ language = 'es_CL'
+ extra_css = ' .titulonegritastop{font-size: xx-large; font-weight: bold} '
+
conversion_options = {
'comment' : description
, 'tags' : category
@@ -29,13 +29,13 @@ class LaSegunda(BasicNewsRecipe):
, 'language' : language
, 'linearize_tables' : True
}
-
+
remove_tags_before = dict(attrs={'class':'titulonegritastop'})
remove_tags = [dict(name='img')]
remove_attributes = ['width','height']
-
-
- feeds = [
+
+
+ feeds = [
(u'Noticias de ultima hora', u'http://www.lasegunda.com/rss20/index.asp?canal=0')
,(u'Politica' , u'http://www.lasegunda.com/rss20/index.asp?canal=21')
,(u'Cronica' , u'http://www.lasegunda.com/rss20/index.asp?canal=20')
@@ -49,6 +49,6 @@ class LaSegunda(BasicNewsRecipe):
]
def print_version(self, url):
- rest, sep, article_id = url.partition('index.asp?idnoticia=')
+ rest, sep, article_id = url.partition('index.asp?idnoticia=')
return u'http://www.lasegunda.com/edicionOnline/include/secciones/_detalle_impresion.asp?idnoticia=' + article_id
-
+
diff --git a/resources/recipes/la_tribuna.recipe b/resources/recipes/la_tribuna.recipe
index 739d11cc8d..c6d3f5cb28 100644
--- a/resources/recipes/la_tribuna.recipe
+++ b/resources/recipes/la_tribuna.recipe
@@ -2,24 +2,23 @@
__license__ = 'GPL v3'
__author__ = 'Luis Hernandez'
__copyright__ = 'Luis Hernandez'
-description = 'Diario local de Talavera de la Reina - v1.2 - 27 Jan 2011'
+__version__ = 'v1.0'
+__date__ = '01 Feb 2011'
'''
-http://www.latribunadetalavera.es/
+http://www.promecal.es/
'''
-
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1294946868(BasicNewsRecipe):
- title = u'La Tribuna de Talavera'
+ title = u'La Tribuna de'
publisher = u'Grupo PROMECAL'
__author__ = 'Luis Hernández'
- description = 'Diario local de Talavera de la Reina'
- cover_url = 'http://www.latribunadetalavera.es/entorno/mancheta.gif'
+ description = 'Varios diarios locales del grupo PROMECAL'
- oldest_article = 5
+ oldest_article = 3
max_articles_per_feed = 50
remove_javascript = True
@@ -27,7 +26,7 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
use_embedded_content = False
encoding = 'utf-8'
- language = 'es'
+ language = 'es_ES'
timefmt = '[%a, %d %b, %Y]'
keep_only_tags = [
@@ -39,7 +38,20 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
remove_tags_before = dict(name='div' , attrs={'class':['comparte']})
remove_tags_after = dict(name='div' , attrs={'id':['relacionadas']})
- extra_css = ' p{text-align: justify; font-size: 100%} body{ text-align: left; font-family: serif; font-size: 100% } h1{ font-family: sans-serif; font-size:150%; font-weight: 700; text-align: justify; } h2{ font-family: sans-serif; font-size:120%; font-weight: 600; text-align: justify } h3{ font-family: sans-serif; font-size:60%; font-weight: 600; text-align: left } h4{ font-family: sans-serif; font-size:80%; font-weight: 600; text-align: left } h5{ font-family: sans-serif; font-size:70%; font-weight: 600; text-align: left }img{margin-bottom: 0.4em} '
+ remove_tags = [
+ dict(name='div', attrs={'id':['relacionadas']})
+ ,dict(name='h3')
+ ,dict(name='h5')
+ ]
+
+ extra_css = """
+ p{text-align: justify; font-size: 100%}
+ body{text-align: left; font-family: serif; font-size: 100%}
+ h1{font-family: sans; font-size:150%; font-weight: bold; text-align: justify;}
+ h2{font-family: sans-serif; font-size:85%; font-style: italic; text-align: justify;}
+ h4{font-family: sans; font-size:75%; font-weight: bold; text-align: center;}
+ img{margin-bottom: 0.4em}
+ """
def preprocess_html(self, soup):
for alink in soup.findAll('a'):
@@ -48,4 +60,15 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
alink.replaceWith(tstr)
return soup
- feeds = [(u'Portada', u'http://www.latribunadetalavera.es/rss.html')]
+
+ feeds = [
+ (u'Albacete', u'http://www.latribunadealbacete.es/rss.html')
+ ,(u'Avila', u'http://www.diariodeavila.es/rss.html')
+ ,(u'Burgos', u'http://www.diariodeburgos.es/rss.html')
+ ,(u'Ciudad Real', u'http://www.latribunadeciudadreal.es/rss.html')
+ ,(u'Palencia', u'http://www.diariopalentino.es/rss.html')
+ ,(u'Puertollano', u'http://www.latribunadepuertollano.es/rss.html')
+ ,(u'Talavera de la Reina', u'http://www.latribunadetalavera.es/rss.html')
+ ,(u'Toledo', u'http://www.latribunadetoledo.es/rss.html')
+ ,(u'Valladolid', u'http://www.eldiadevalladolid.com/rss.html')
+ ]
diff --git a/resources/recipes/lamujerdemivida.recipe b/resources/recipes/lamujerdemivida.recipe
index 207646902b..be7a3fad0d 100644
--- a/resources/recipes/lamujerdemivida.recipe
+++ b/resources/recipes/lamujerdemivida.recipe
@@ -11,15 +11,15 @@ from calibre.web.feeds.news import BasicNewsRecipe
class LaMujerDeMiVida(BasicNewsRecipe):
title = 'La Mujer de mi Vida'
__author__ = 'Darko Miletic'
- description = 'Cultura de otra manera'
+ description = 'Cultura de otra manera'
oldest_article = 90
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'cp1252'
publisher = 'La Mujer de mi Vida'
- category = 'literatura, critica, arte, ensayos'
- language = 'es'
+ category = 'literatura, critica, arte, ensayos'
+ language = 'es_AR'
INDEX = 'http://www.lamujerdemivida.com.ar/'
html2lrf_options = [
@@ -28,8 +28,8 @@ class LaMujerDeMiVida(BasicNewsRecipe):
, '--publisher', publisher
, '--ignore-tables'
]
-
- html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True'
+
+ html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True'
keep_only_tags = [dict(name='table', attrs={'width':'570'})]
@@ -51,7 +51,7 @@ class LaMujerDeMiVida(BasicNewsRecipe):
if cover_item:
cover_url = self.INDEX + cover_item['src']
return cover_url
-
+
def parse_index(self):
totalfeeds = []
lfeeds = self.get_feeds()
@@ -74,4 +74,4 @@ class LaMujerDeMiVida(BasicNewsRecipe):
})
totalfeeds.append((feedtitle, articles))
return totalfeeds
-
+
diff --git a/resources/recipes/lanacion.recipe b/resources/recipes/lanacion.recipe
index 050cb2e79c..05e777ec67 100644
--- a/resources/recipes/lanacion.recipe
+++ b/resources/recipes/lanacion.recipe
@@ -16,17 +16,17 @@ class Lanacion(BasicNewsRecipe):
max_articles_per_feed = 100
use_embedded_content = False
no_stylesheets = True
- language = 'es'
+ language = 'es_AR'
publication_type = 'newspaper'
- remove_empty_feeds = True
+ remove_empty_feeds = True
masthead_url = 'http://www.lanacion.com.ar/imgs/layout/logos/ln341x47.gif'
extra_css = """ h1{font-family: Georgia,serif}
- h2{color: #626262}
- body{font-family: Arial,sans-serif}
+ h2{color: #626262}
+ body{font-family: Arial,sans-serif}
img{margin-top: 0.5em; margin-bottom: 0.2em; display: block}
- .notaFecha{color: #808080}
- .notaEpigrafe{font-size: x-small}
- .topNota h1{font-family: Arial,sans-serif}
+ .notaFecha{color: #808080}
+ .notaEpigrafe{font-size: x-small}
+ .topNota h1{font-family: Arial,sans-serif}
"""
@@ -45,7 +45,7 @@ class Lanacion(BasicNewsRecipe):
,dict(attrs={'class':['titulosMultimedia','derecha','techo color','encuesta','izquierda compartir','floatFix','videoCentro']})
,dict(name=['iframe','embed','object','form','base','hr','meta','link','input'])
]
- remove_tags_after = dict(attrs={'class':['tags','nota-destacado']})
+ remove_tags_after = dict(attrs={'class':['tags','nota-destacado']})
remove_attributes = ['height','width','visible','onclick','data-count','name']
feeds = [
diff --git a/resources/recipes/lanacion_chile.recipe b/resources/recipes/lanacion_chile.recipe
index f913b61855..0c0d4550fa 100644
--- a/resources/recipes/lanacion_chile.recipe
+++ b/resources/recipes/lanacion_chile.recipe
@@ -51,4 +51,4 @@ class LaNacionChile(BasicNewsRecipe):
del item['style']
return soup
- language = 'es'
+ language = 'es_CL'
diff --git a/resources/recipes/laprensa.recipe b/resources/recipes/laprensa.recipe
index 1f8c708b32..eb54ec989a 100644
--- a/resources/recipes/laprensa.recipe
+++ b/resources/recipes/laprensa.recipe
@@ -21,9 +21,9 @@ class LaPrensa(BasicNewsRecipe):
encoding = 'cp1252'
# cover_url = 'http://www.laprensa.com.ar/imgs/logo.gif'
remove_javascript = True
- language = 'es'
+ language = 'es_AR'
lang = 'es'
-
+
html2lrf_options = [
'--comment', description
, '--category', category
@@ -32,7 +32,7 @@ class LaPrensa(BasicNewsRecipe):
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
filter_regexps = [r'.*archive.aspx.*']
-
+
remove_tags = [
dict(name='td', attrs={'class':["link-registro","link-buscador"]}),
dict(name='td', attrs={'id':["TDTabItem1","TDTabItem2","TDTabItem3","TDTabItem4"]}),
@@ -58,9 +58,9 @@ class LaPrensa(BasicNewsRecipe):
dict(name='img', src = "/versions/1/imgs/separador-linea-azul.gif"),
dict(name='img', src = " /versions/1/imgs/separador-linea.gif"),
dict(name='a',text ="Powered by Civinext Groupware - V. 2.0.3567.23706"),
- dict(name='img', height ="0")
+ dict(name='img', height ="0")
]
-
+
extra_css = '''
.seccion{font-size:xx-small;}
body{font-family:Arial,Helvetica,sans-serif;font-size:x-small;}
@@ -69,7 +69,7 @@ class LaPrensa(BasicNewsRecipe):
.fecha{font-size:xx-small;}
.volanta{font-size:xx-small;}
'''
-
+
feeds = [
(u'Politica' , u'http://www.laprensa.com.ar/ResourcesManager.aspx?Resource=Rss.aspx&Rss=4' )
,(u'Economia' , u'http://www.laprensa.com.ar/ResourcesManager.aspx?Resource=Rss.aspx&Rss=5' )
@@ -80,14 +80,14 @@ class LaPrensa(BasicNewsRecipe):
,(u'Espectaculos', u'http://www.laprensa.com.ar/ResourcesManager.aspx?Resource=Rss.aspx?Rss=10')
]
-
+
def preprocess_html(self, soup):
-
+
for t in soup.findAll(['table','td','tr','span','tbody']):
t.name = 'div'
for t in soup.findAll(['hr']):
t.extract()
-
+
mtag = ''
soup.head.insert(0,mtag)
for item in soup.findAll(style=True):
@@ -95,8 +95,8 @@ class LaPrensa(BasicNewsRecipe):
for item in soup.findAll(align = "center"):
del item['align']
for item in soup.findAll(bgcolor="ffffff"):
- del item['bgcolor']
+ del item['bgcolor']
return soup
-
-
-
+
+
+
diff --git a/resources/recipes/laprensa_hn.recipe b/resources/recipes/laprensa_hn.recipe
index 356882d177..d7895bf09d 100644
--- a/resources/recipes/laprensa_hn.recipe
+++ b/resources/recipes/laprensa_hn.recipe
@@ -21,7 +21,7 @@ class LaPrensaHn(BasicNewsRecipe):
no_stylesheets = True
remove_javascript = True
encoding = 'utf-8'
- language = 'es'
+ language = 'es_HN'
lang = 'es-HN'
direction = 'ltr'
diff --git a/resources/recipes/laprensa_ni.recipe b/resources/recipes/laprensa_ni.recipe
index c7f35a6d6a..9aba2839ef 100644
--- a/resources/recipes/laprensa_ni.recipe
+++ b/resources/recipes/laprensa_ni.recipe
@@ -22,7 +22,7 @@ class LaPrensa_ni(BasicNewsRecipe):
use_embedded_content = False
encoding = 'cp1252'
remove_javascript = True
- language = 'es'
+ language = 'es_NI'
months_es = ['enero','febrero','marzo','abril','mayo','junio','julio','agosto','septiembre','octubre','noviembre','diciembre']
current_month = months_es[datetime.date.today().month - 1]
diff --git a/resources/recipes/latimes.recipe b/resources/recipes/latimes.recipe
index bd426c1f33..930b986315 100644
--- a/resources/recipes/latimes.recipe
+++ b/resources/recipes/latimes.recipe
@@ -1,73 +1,92 @@
-#!/usr/bin/env python
-
__license__ = 'GPL v3'
-__copyright__ = '2008-2009, Darko Miletic '
+__copyright__ = '2008-2011, Darko Miletic '
'''
-latimes.com
+www.latimes.com
'''
+
from calibre.web.feeds.news import BasicNewsRecipe
class LATimes(BasicNewsRecipe):
- title = u'The Los Angeles Times'
- __author__ = u'Darko Miletic and Sujata Raman'
- description = u'News from Los Angeles'
- oldest_article = 7
- max_articles_per_feed = 100
- language = 'en'
+ title = 'Los Angeles Times'
+ __author__ = 'Darko Miletic'
+ description = 'The Los Angeles Times is a leading source of news on Southern California, entertainment, movies, television, music, politics, business, health, technology, travel, sports, environment, economics, autos, jobs, real estate and other topics affecting California'
+ publisher = 'Tribune Company'
+ category = 'news, politics, USA, Los Angeles, world'
+ oldest_article = 2
+ max_articles_per_feed = 200
no_stylesheets = True
+ encoding = 'utf8'
use_embedded_content = False
- encoding = 'utf-8'
- lang = 'en-US'
+ language = 'en'
+ remove_empty_feeds = True
+ publication_type = 'newspaper'
+ masthead_url = 'http://www.latimes.com/images/logo.png'
+ cover_url = 'http://www.latimes.com/includes/sectionfronts/A1.pdf'
+ extra_css = """
+ body{font-family: Georgia,"Times New Roman",Times,serif }
+ img{margin-bottom: 0.4em; margin-top: 0.8em; display:block}
+ h2{font-size: 1.1em}
+ .deckhead{font-size: small; text-transform: uppercase}
+ .small{color: gray; font-size: small}
+ .date,.time,.copyright{font-size: x-small; color:gray; font-style:italic;}
+ """
conversion_options = {
- 'comment' : description
- , 'language' : lang
- }
+ 'comment' : description
+ , 'tags' : category
+ , 'publisher' : publisher
+ , 'language' : language
+ , 'linearize_tables' : 'Yes'
+ }
- extra_css = '''
- h1{font-family :Georgia,"Times New Roman",Times,serif; font-size:large; }
- h2{font-family :Georgia,"Times New Roman",Times,serif; font-size:x-small;}
- .story{font-family :Georgia,"Times New Roman",Times,serif; font-size: x-small;}
- .entry-body{font-family :Georgia,"Times New Roman",Times,serif; font-size: x-small;}
- .entry-more{font-family :Georgia,"Times New Roman",Times,serif; font-size: x-small;}
- .credit{color:#666666; font-family :Georgia,"Times New Roman",Times,serif; font-size: xx-small;}
- .small{color:#666666; font-family :Georgia,"Times New Roman",Times,serif; font-size: xx-small;}
- .byline{font-family :Georgia,"Times New Roman",Times,serif; font-size: xx-small;}
- .date{font-family :Georgia,"Times New Roman",Times,serif; font-size: xx-small;color:#930000; font-style:italic;}
- .time{font-family :Georgia,"Times New Roman",Times,serif; font-size: xx-small;color:#930000; font-style:italic;}
- .copyright{font-family :Georgia,"Times New Roman",Times,serif; font-size: xx-small;color:#930000; }
- .subhead{font-family :Georgia,"Times New Roman",Times,serif; font-size:x-small;}
- '''
-
- # recursions = 1
- # match_regexps = [r'http://www.latimes.com/.*page=[2-9]']
-
- keep_only_tags = [dict(name='div', attrs={'class':["story" ,"entry"] })]
+ keep_only_tags = [
+ dict(name='div', attrs={'class':'story'})
+ ,dict(attrs={'class':['entry-header','time','entry-content']})
+ ]
+ remove_tags_after=dict(name='p', attrs={'class':'copyright'})
+ remove_tags = [
+ dict(name=['meta','link','iframe','object','embed'])
+ ,dict(attrs={'class':['toolSet','articlerail','googleAd','entry-footer-left','entry-footer-right','entry-footer-social','google-ad-story-bottom','sphereTools']})
+ ,dict(attrs={'id':['article-promo','googleads','moduleArticleToolsContainer','gallery-subcontent']})
+ ]
+ remove_attributes=['lang','xmlns:fb','xmlns:og','border','xtags','i','article_body']
- remove_tags = [ dict(name='div', attrs={'class':['articlerail',"sphereTools","tools","toppaginate","entry-footer-left","entry-footer-right"]}),
- dict(name='div', attrs={'id':["moduleArticleToolsContainer",]}),
- dict(name='p', attrs={'class':["entry-footer",]}),
- dict(name='ul', attrs={'class':"article-nav clearfix"}),
- dict(name=['iframe'])
- ]
-
-
- feeds = [(u'News', u'http://feeds.latimes.com/latimes/news')
- ,(u'Local','http://feeds.latimes.com/latimes/news/local')
- ,(u'MostEmailed','http://feeds.latimes.com/MostEmailed')
- ,(u'Politics','http://feeds.latimes.com/latimes/news/local/politics/cal/')
- ,('OrangeCounty','http://feeds.latimes.com/latimes/news/local/orange/')
- ,('National','http://feeds.latimes.com/latimes/news/nationworld/nation')
- ,('Politics','http://feeds.latimes.com/latimes/news/politics/')
- ,('Business','http://feeds.latimes.com/latimes/business')
- ,('Sports','http://feeds.latimes.com/latimes/sports/')
- ,('Entertainment','http://feeds.latimes.com/latimes/entertainment/')
- ]
-
+ feeds = [
+ (u'Top News' , u'http://feeds.latimes.com/latimes/news' )
+ ,(u'Local News' , u'http://feeds.latimes.com/latimes/news/local' )
+ ,(u'National' , u'http://feeds.latimes.com/latimes/news/nationworld/nation' )
+ ,(u'National Politics' , u'http://feeds.latimes.com/latimes/news/politics/' )
+ ,(u'Business' , u'http://feeds.latimes.com/latimes/business' )
+ ,(u'Education' , u'http://feeds.latimes.com/latimes/news/education' )
+ ,(u'Environment' , u'http://feeds.latimes.com/latimes/news/science/environment' )
+ ,(u'Religion' , u'http://feeds.latimes.com/latimes/features/religion' )
+ ,(u'Science' , u'http://feeds.latimes.com/latimes/news/science' )
+ ,(u'Technology' , u'http://feeds.latimes.com/latimes/technology' )
+ ,(u'Africa' , u'http://feeds.latimes.com/latimes/africa' )
+ ,(u'Asia' , u'http://feeds.latimes.com/latimes/asia' )
+ ,(u'Europe' , u'http://feeds.latimes.com/latimes/europe' )
+ ,(u'Latin America' , u'http://feeds.latimes.com/latimes/latinamerica' )
+ ,(u'Middle East' , u'http://feeds.latimes.com/latimes/middleeast' )
+ ,(u'Arts&Culture' , u'http://feeds.feedburner.com/latimes/entertainment/news/arts' )
+ ,(u'Entertainment News' , u'http://feeds.feedburner.com/latimes/entertainment/news/' )
+ ,(u'Movie News' , u'http://feeds.feedburner.com/latimes/entertainment/news/movies/' )
+ ,(u'Movie Reviews' , u'http://feeds.feedburner.com/movies/reviews/' )
+ ,(u'Music News' , u'http://feeds.feedburner.com/latimes/entertainment/news/music/' )
+ ,(u'Pop Album Reviews' , u'http://feeds.feedburner.com/latimes/pop-album-reviews' )
+ ,(u'Restaurant Reviews' , u'http://feeds.feedburner.com/latimes/restaurant/reviews' )
+ ,(u'Theatar and Dance' , u'http://feeds.feedburner.com/latimes/theaterdance' )
+ ,(u'Autos' , u'http://feeds.latimes.com/latimes/classified/automotive/highway1/')
+ ,(u'Books' , u'http://feeds.latimes.com/features/books' )
+ ,(u'Food' , u'http://feeds.latimes.com/latimes/features/food/' )
+ ,(u'Health' , u'http://feeds.latimes.com/latimes/features/health/' )
+ ,(u'Real Estate' , u'http://feeds.latimes.com/latimes/classified/realestate/' )
+ ,(u'Commentary' , u'http://feeds2.feedburner.com/latimes/news/opinion/commentary/' )
+ ,(u'Sports' , u'http://feeds.latimes.com/latimes/sports/' )
+ ]
def get_article_url(self, article):
- ans = article.get('feedburner_origlink').rpartition('?')[0]
+ ans = BasicNewsRecipe.get_article_url(self, article).rpartition('?')[0]
try:
self.log('Looking for full story link in', ans)
@@ -83,4 +102,22 @@ class LATimes(BasicNewsRecipe):
pass
return ans
-
+ def preprocess_html(self, soup):
+ for item in soup.findAll(style=True):
+ del item['style']
+ for item in soup.findAll('img'):
+ if not item.has_key('alt'):
+ item['alt'] = 'image'
+ for item in soup.findAll('a'):
+ limg = item.find('img')
+ if item.string is not None:
+ str = item.string
+ item.replaceWith(str)
+ else:
+ if limg:
+ item.name ='div'
+ item.attrs =[]
+ else:
+ str = self.tag_to_string(item)
+ item.replaceWith(str)
+ return soup
diff --git a/resources/recipes/latribuna.recipe b/resources/recipes/latribuna.recipe
index e7e461cd01..13c59dc4aa 100644
--- a/resources/recipes/latribuna.recipe
+++ b/resources/recipes/latribuna.recipe
@@ -21,7 +21,7 @@ class LaTribuna(BasicNewsRecipe):
no_stylesheets = True
remove_javascript = True
encoding = 'utf-8'
- language = 'es'
+ language = 'es_HN'
lang = 'es-HN'
direction = 'ltr'
diff --git a/resources/recipes/le_temps.recipe b/resources/recipes/le_temps.recipe
index c33d9a51d2..7e320fe710 100644
--- a/resources/recipes/le_temps.recipe
+++ b/resources/recipes/le_temps.recipe
@@ -15,12 +15,26 @@ class LeTemps(BasicNewsRecipe):
oldest_article = 7
max_articles_per_feed = 100
__author__ = 'Sujata Raman'
+ description = 'French news. Needs a subscription from http://www.letemps.ch'
no_stylesheets = True
remove_javascript = True
recursions = 1
encoding = 'UTF-8'
match_regexps = [r'http://www.letemps.ch/Page/Uuid/[-0-9a-f]+\|[1-9]']
language = 'fr'
+ needs_subscription = True
+
+ def get_browser(self):
+ br = BasicNewsRecipe.get_browser(self)
+ br.open('http://www.letemps.ch/login')
+ br['username'] = self.username
+ br['password'] = self.password
+ raw = br.submit().read()
+ if '>Login' in raw:
+ raise ValueError('Failed to login to letemp.ch. Check '
+ 'your username and password')
+ return br
+
keep_only_tags = [dict(name='div', attrs={'id':'content'}),
dict(name='div', attrs={'class':'story'})
diff --git a/resources/recipes/ledevoir.recipe b/resources/recipes/ledevoir.recipe
index c54f21c7ec..bc473be181 100644
--- a/resources/recipes/ledevoir.recipe
+++ b/resources/recipes/ledevoir.recipe
@@ -9,6 +9,8 @@ __description__ = 'Canadian Paper '
http://www.ledevoir.com/
'''
+import re
+
from calibre.web.feeds.news import BasicNewsRecipe
class ledevoir(BasicNewsRecipe):
@@ -32,6 +34,8 @@ class ledevoir(BasicNewsRecipe):
remove_javascript = True
no_stylesheets = True
+ preprocess_regexps = [(re.compile(r'(title|alt)=".*?>.*?"', re.DOTALL), lambda m: '')]
+
keep_only_tags = [
dict(name='div', attrs={'id':'article'}),
dict(name='ul', attrs={'id':'ariane'})
diff --git a/resources/recipes/los_tiempos_bo.recipe b/resources/recipes/los_tiempos_bo.recipe
index ae2774ff59..00ddd9d7c1 100644
--- a/resources/recipes/los_tiempos_bo.recipe
+++ b/resources/recipes/los_tiempos_bo.recipe
@@ -18,7 +18,7 @@ class LosTiempos_Bol(BasicNewsRecipe):
no_stylesheets = True
encoding = 'cp1252'
use_embedded_content = False
- language = 'es'
+ language = 'es_BO'
publication_type = 'newspaper'
delay = 1
remove_empty_feeds = True
diff --git a/resources/recipes/milenio.recipe b/resources/recipes/milenio.recipe
index a279eb37dc..e6ca103952 100644
--- a/resources/recipes/milenio.recipe
+++ b/resources/recipes/milenio.recipe
@@ -12,7 +12,7 @@ import datetime
class Milenio(BasicNewsRecipe):
title = u'Milenio-diario'
__author__ = 'Bmsleight'
- language = 'es'
+ language = 'es_MX'
description = 'Milenio-diario'
oldest_article = 10
max_articles_per_feed = 100
diff --git a/resources/recipes/miradasalsur.recipe b/resources/recipes/miradasalsur.recipe
index f966a94ee9..fd306adc86 100644
--- a/resources/recipes/miradasalsur.recipe
+++ b/resources/recipes/miradasalsur.recipe
@@ -20,7 +20,7 @@ class MiradasAlSur(BasicNewsRecipe):
no_stylesheets = True
use_embedded_content = False
encoding = 'utf-8'
- language = 'es'
+ language = 'es_AR'
lang = 'es-AR'
direction = 'ltr'
diff --git a/resources/recipes/montevideo_com.recipe b/resources/recipes/montevideo_com.recipe
index cabd4181d6..6f1474f0c0 100644
--- a/resources/recipes/montevideo_com.recipe
+++ b/resources/recipes/montevideo_com.recipe
@@ -12,7 +12,7 @@ class Noticias(BasicNewsRecipe):
title = 'Montevideo COMM'
__author__ = 'Gustavo Azambuja'
description = 'Noticias de Uruguay'
- language = 'es'
+ language = 'es_UY'
timefmt = '[%a, %d %b, %Y]'
use_embedded_content = False
recursion = 5
diff --git a/resources/recipes/msnsankei.recipe b/resources/recipes/msnsankei.recipe
index ae195559d5..59664d055f 100644
--- a/resources/recipes/msnsankei.recipe
+++ b/resources/recipes/msnsankei.recipe
@@ -13,15 +13,12 @@ class MSNSankeiNewsProduct(BasicNewsRecipe):
description = 'Products release from Japan'
oldest_article = 7
max_articles_per_feed = 100
- encoding = 'Shift_JIS'
+ encoding = 'utf-8'
language = 'ja'
cover_url = 'http://sankei.jp.msn.com/images/common/sankeShinbunLogo.jpg'
masthead_url = 'http://sankei.jp.msn.com/images/common/sankeiNewsLogo.gif'
feeds = [(u'\u65b0\u5546\u54c1', u'http://sankei.jp.msn.com/rss/news/release.xml')]
- remove_tags_before = dict(id="__r_article_title__")
- remove_tags_after = dict(id="ajax_release_news")
- remove_tags = [{'class':"parent chromeCustom6G"},
- dict(id="RelatedImg")
- ]
+ remove_tags_before = dict(id="NewsTitle")
+ remove_tags_after = dict(id="RelatedTitle")
diff --git a/resources/recipes/newsweek_argentina.recipe b/resources/recipes/newsweek_argentina.recipe
index 96c50bf6c3..a474f75f60 100644
--- a/resources/recipes/newsweek_argentina.recipe
+++ b/resources/recipes/newsweek_argentina.recipe
@@ -20,7 +20,7 @@ class Newsweek_Argentina(BasicNewsRecipe):
no_stylesheets = True
use_embedded_content = False
encoding = 'utf-8'
- language = 'es'
+ language = 'es_AR'
lang = 'es-AR'
direction = 'ltr'
diff --git a/resources/recipes/observa_digital.recipe b/resources/recipes/observa_digital.recipe
index 375d67236c..cb493bcb26 100644
--- a/resources/recipes/observa_digital.recipe
+++ b/resources/recipes/observa_digital.recipe
@@ -12,7 +12,7 @@ class Noticias(BasicNewsRecipe):
title = 'Observa Digital'
__author__ = '2010, Gustavo Azambuja '
description = 'Noticias desde Uruguay'
- language = 'es'
+ language = 'es_UY'
timefmt = '[%a, %d %b, %Y]'
use_embedded_content = False
recursion = 5
diff --git a/resources/recipes/pagina12.recipe b/resources/recipes/pagina12.recipe
index 2809e87e2a..b706acb7d7 100644
--- a/resources/recipes/pagina12.recipe
+++ b/resources/recipes/pagina12.recipe
@@ -19,15 +19,15 @@ class Pagina12(BasicNewsRecipe):
no_stylesheets = True
encoding = 'cp1252'
use_embedded_content = False
- language = 'es'
+ language = 'es_AR'
remove_empty_feeds = True
publication_type = 'newspaper'
masthead_url = 'http://www.pagina12.com.ar/commons/imgs/logo-home.gif'
- extra_css = """
- body{font-family: Arial,Helvetica,sans-serif }
+ extra_css = """
+ body{font-family: Arial,Helvetica,sans-serif }
img{margin-bottom: 0.4em; display:block}
- #autor{font-weight: bold}
- #fecha,#epigrafe{font-size: 0.9em; margin: 5px}
+ #autor{font-weight: bold}
+ #fecha,#epigrafe{font-size: 0.9em; margin: 5px}
#imagen{border: 1px solid black; margin: 0 0 1.25em 1.25em; width: 232px }
.fgprincipal{font-size: large; font-weight: bold}
"""
@@ -83,7 +83,7 @@ class Pagina12(BasicNewsRecipe):
del it['href']
del it['title']
for item in soup.findAll('p'):
- it = item.find('h3')
+ it = item.find('h3')
if it:
it.name='span'
- return soup
\ No newline at end of file
+ return soup
diff --git a/resources/recipes/perfil.recipe b/resources/recipes/perfil.recipe
index 7f51485bd0..7db86f9d4a 100644
--- a/resources/recipes/perfil.recipe
+++ b/resources/recipes/perfil.recipe
@@ -17,7 +17,7 @@ class Perfil(BasicNewsRecipe):
no_stylesheets = True
encoding = 'cp1252'
use_embedded_content = False
- language = 'es'
+ language = 'es_AR'
remove_empty_feeds = True
masthead_url = 'http://www.perfil.com/export/sites/diarioperfil/arte/10/logo_perfilcom_mm.gif'
extra_css = """
diff --git a/resources/recipes/reptantes.recipe b/resources/recipes/reptantes.recipe
index 51b65c6639..8e5ccbd192 100644
--- a/resources/recipes/reptantes.recipe
+++ b/resources/recipes/reptantes.recipe
@@ -13,7 +13,7 @@ class Reptantes(BasicNewsRecipe):
description = u"cada vez que te haces acupuntura, tu muñeco vudú sufre en algún lado"
oldest_article = 130
max_articles_per_feed = 100
- language = 'es'
+ language = 'es_AR'
encoding = 'utf-8'
no_stylesheets = True
use_embedded_content = False
diff --git a/resources/recipes/revista_bla.recipe b/resources/recipes/revista_bla.recipe
index 15c7e7fb3f..a575d01d0b 100644
--- a/resources/recipes/revista_bla.recipe
+++ b/resources/recipes/revista_bla.recipe
@@ -12,7 +12,7 @@ class Noticias(BasicNewsRecipe):
title = 'Revista Bla'
__author__ = 'Gustavo Azambuja'
description = 'Moda | Uruguay'
- language = 'es'
+ language = 'es_UY'
timefmt = '[%a, %d %b, %Y]'
use_embedded_content = False
recursion = 5
diff --git a/resources/recipes/theonion.recipe b/resources/recipes/theonion.recipe
index 3be4ae4e04..b0eacbb5e0 100644
--- a/resources/recipes/theonion.recipe
+++ b/resources/recipes/theonion.recipe
@@ -1,7 +1,5 @@
-#!/usr/bin/env python
-
__license__ = 'GPL v3'
-__copyright__ = '2009, Darko Miletic '
+__copyright__ = '2009-2011, Darko Miletic '
'''
theonion.com
@@ -12,35 +10,73 @@ from calibre.web.feeds.news import BasicNewsRecipe
class TheOnion(BasicNewsRecipe):
title = 'The Onion'
__author__ = 'Darko Miletic'
- description = "America's finest news source"
- oldest_article = 2
+ description = "America's finest news source"
+ oldest_article = 2
max_articles_per_feed = 100
- publisher = u'Onion, Inc.'
- category = u'humor, news, USA'
- language = 'en'
-
+ publisher = 'Onion, Inc.'
+ category = 'humor, news, USA'
+ language = 'en'
no_stylesheets = True
use_embedded_content = False
encoding = 'utf-8'
- remove_javascript = True
- html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
-
- html2lrf_options = [
- '--comment' , description
- , '--category' , category
- , '--publisher' , publisher
- ]
+ publication_type = 'newsportal'
+ masthead_url = 'http://o.onionstatic.com/img/headers/onion_190.png'
+ extra_css = """
+ body{font-family: Helvetica,Arial,sans-serif}
+ .section_title{color: gray; text-transform: uppercase}
+ .title{font-family: Georgia,serif}
+ .meta{color: gray; display: inline}
+ .has_caption{display: block}
+ .caption{font-size: x-small; color: gray; margin-bottom: 0.8em}
+ """
- keep_only_tags = [dict(name='div', attrs={'id':'main'})]
-
+ conversion_options = {
+ 'comment' : description
+ , 'tags' : category
+ , 'publisher': publisher
+ , 'language' : language
+ }
+
+ keep_only_tags = [
+ dict(name='h2', attrs={'class':['section_title','title']})
+ ,dict(attrs={'class':['main_image','meta','article_photo_lead','article_body']})
+ ,dict(attrs={'id':['entries']})
+ ]
+ remove_attributes=['lang','rel']
+ remove_tags_after = dict(attrs={'class':['article_body','feature_content']})
remove_tags = [
- dict(name=['object','link','iframe','base'])
+ dict(name=['object','link','iframe','base','meta'])
,dict(name='div', attrs={'class':['toolbar_side','graphical_feature','toolbar_bottom']})
,dict(name='div', attrs={'id':['recent_slider','sidebar','pagination','related_media']})
]
-
+
feeds = [
(u'Daily' , u'http://feeds.theonion.com/theonion/daily' )
,(u'Sports' , u'http://feeds.theonion.com/theonion/sports' )
]
+
+ def get_article_url(self, article):
+ artl = BasicNewsRecipe.get_article_url(self, article)
+ if artl.startswith('http://www.theonion.com/audio/'):
+ artl = None
+ return artl
+
+ def preprocess_html(self, soup):
+ for item in soup.findAll(style=True):
+ del item['style']
+ for item in soup.findAll('a'):
+ limg = item.find('img')
+ if item.string is not None:
+ str = item.string
+ item.replaceWith(str)
+ else:
+ if limg:
+ item.name = 'div'
+ item.attrs = []
+ if not limg.has_key('alt'):
+ limg['alt'] = 'image'
+ else:
+ str = self.tag_to_string(item)
+ item.replaceWith(str)
+ return soup
diff --git a/resources/recipes/veintitres.recipe b/resources/recipes/veintitres.recipe
index 6c8d3d1260..d36ac8568b 100644
--- a/resources/recipes/veintitres.recipe
+++ b/resources/recipes/veintitres.recipe
@@ -20,7 +20,7 @@ class Veintitres(BasicNewsRecipe):
no_stylesheets = True
use_embedded_content = False
encoding = 'utf-8'
- language = 'es'
+ language = 'es_AR'
lang = 'es-AR'
direction = 'ltr'
diff --git a/resources/recipes/vijesti.recipe b/resources/recipes/vijesti.recipe
index c901755b78..d650bdec06 100644
--- a/resources/recipes/vijesti.recipe
+++ b/resources/recipes/vijesti.recipe
@@ -1,6 +1,6 @@
__license__ = 'GPL v3'
-__copyright__ = '2009-2010, Darko Miletic '
+__copyright__ = '2009-2011, Darko Miletic '
'''
vijesti.me
@@ -18,12 +18,16 @@ class Vijesti(BasicNewsRecipe):
oldest_article = 2
max_articles_per_feed = 150
no_stylesheets = True
- encoding = 'cp1250'
+ encoding = 'utf8'
use_embedded_content = False
language = 'sr'
publication_type = 'newspaper'
- masthead_url = 'http://www.vijesti.me/img/logo.gif'
- extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: sans1, sans-serif}'
+ extra_css = """
+ @font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
+ @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
+ body{font-family: Georgia,"Times New Roman",Times,serif1,serif}
+ .articledescription,.article,.chapter{font-family: sans1, sans-serif}
+ """
conversion_options = {
'comment' : description
@@ -34,11 +38,11 @@ class Vijesti(BasicNewsRecipe):
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
- keep_only_tags = [dict(name='div', attrs={'id':'mainnews'})]
+ keep_only_tags = [dict(name='div', attrs={'id':['article_intro_text','article_text']})]
remove_tags = [dict(name=['object','link','embed','form'])]
- feeds = [(u'Sve vijesti', u'http://www.vijesti.me/rss.php' )]
+ feeds = [(u'Sve vijesti', u'http://www.vijesti.me/rss/' )]
def preprocess_html(self, soup):
return self.adeify_images(soup)
diff --git a/resources/recipes/wsj.recipe b/resources/recipes/wsj.recipe
index 4ce315200c..f2854e65ca 100644
--- a/resources/recipes/wsj.recipe
+++ b/resources/recipes/wsj.recipe
@@ -35,7 +35,7 @@ class WallStreetJournal(BasicNewsRecipe):
remove_tags_before = dict(name='h1')
remove_tags = [
- dict(id=["articleTabs_tab_article", "articleTabs_tab_comments", "articleTabs_tab_interactive","articleTabs_tab_video","articleTabs_tab_map","articleTabs_tab_slideshow"]),
+ dict(id=["articleTabs_tab_article", "articleTabs_tab_comments", "articleTabs_tab_interactive","articleTabs_tab_video","articleTabs_tab_map","articleTabs_tab_slideshow","articleTabs_tab_quotes","articleTabs_tab_document"]),
{'class':['footer_columns','network','insetCol3wide','interactive','video','slideshow','map','insettip','insetClose','more_in', "insetContent", 'articleTools_bottom', 'aTools', "tooltip", "adSummary", "nav-inline"]},
dict(rel='shortcut icon'),
]
@@ -101,7 +101,7 @@ class WallStreetJournal(BasicNewsRecipe):
title = 'Front Section'
url = 'http://online.wsj.com' + a['href']
feeds = self.wsj_add_feed(feeds,title,url)
- title = 'What''s News'
+ title = "What's News"
url = url.replace('pageone','whatsnews')
feeds = self.wsj_add_feed(feeds,title,url)
else:
@@ -131,6 +131,7 @@ class WallStreetJournal(BasicNewsRecipe):
'description':desc, 'date':''})
self.log('\tFound WN article:', title)
+ self.log('\t\t', desc)
return articles
@@ -157,17 +158,23 @@ class WallStreetJournal(BasicNewsRecipe):
meta = a.find(attrs={'class':'meta_sectionName'})
if meta is not None:
meta.extract()
- title = self.tag_to_string(a).strip() + ' [%s]'%self.tag_to_string(meta)
+ meta = self.tag_to_string(meta).strip()
+ if meta:
+ title = self.tag_to_string(a).strip() + ' [%s]'%meta
+ else:
+ title = self.tag_to_string(a).strip()
url = 'http://online.wsj.com'+a['href']
desc = ''
- p = container.find('p')
- if p is not None:
+ for p in container.findAll('p'):
desc = self.tag_to_string(p)
+ if not 'Subscriber Content' in desc:
+ break
articles.append({'title':title, 'url':url,
'description':desc, 'date':''})
self.log('\tFound article:', title)
+ self.log('\t\t', desc)
return articles
diff --git a/resources/recipes/wsj_free.recipe b/resources/recipes/wsj_free.recipe
index df8234e8e2..dd42fb5540 100644
--- a/resources/recipes/wsj_free.recipe
+++ b/resources/recipes/wsj_free.recipe
@@ -10,7 +10,10 @@ class WallStreetJournal(BasicNewsRecipe):
title = 'Wall Street Journal (free)'
__author__ = 'Kovid Goyal, Sujata Raman, Joshua Oster-Morris, Starson17'
- description = 'News and current affairs'
+ description = '''News and current affairs. This recipe only fetches complete
+ versions of the articles that are available free on the wsj.com website.
+ To get the rest of the articles, subscribe to the WSJ and use the other WSJ
+ recipe.'''
language = 'en'
cover_url = 'http://dealbreaker.com/images/thumbs/Wall%20Street%20Journal%20A1.JPG'
max_articles_per_feed = 1000
@@ -137,12 +140,17 @@ class WallStreetJournal(BasicNewsRecipe):
meta = a.find(attrs={'class':'meta_sectionName'})
if meta is not None:
meta.extract()
- title = self.tag_to_string(a).strip() + ' [%s]'%self.tag_to_string(meta)
+ meta = self.tag_to_string(meta).strip()
+ if meta:
+ title = self.tag_to_string(a).strip() + ' [%s]'%meta
+ else:
+ title = self.tag_to_string(a).strip()
url = 'http://online.wsj.com'+a['href']
desc = ''
- p = container.find('p')
- if p is not None:
+ for p in container.findAll('p'):
desc = self.tag_to_string(p)
+ if not 'Subscriber Content' in desc:
+ break
articles.append({'title':title, 'url':url,
'description':desc, 'date':''})
@@ -151,6 +159,4 @@ class WallStreetJournal(BasicNewsRecipe):
return articles
- def cleanup(self):
- self.browser.open('http://online.wsj.com/logout?url=http://online.wsj.com')
diff --git a/resources/template-functions.json b/resources/template-functions.json
index cb329d771c..332ce1ddea 100644
--- a/resources/template-functions.json
+++ b/resources/template-functions.json
@@ -12,7 +12,7 @@
"re": "def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):\n return re.sub(pattern, replacement, val)\n",
"add": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x + y)\n",
"lookup": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if len(args) == 2: # here for backwards compatibility\n if val:\n return formatter.vformat('{'+args[0].strip()+'}', [], kwargs)\n else:\n return formatter.vformat('{'+args[1].strip()+'}', [], kwargs)\n if (len(args) % 2) != 1:\n raise ValueError(_('lookup requires either 2 or an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return formatter.vformat('{' + args[i].strip() + '}', [], kwargs)\n if re.search(args[i], val):\n return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)\n i += 2\n",
- "template": "def evaluate(self, formatter, kwargs, mi, locals, template):\n template = template.replace('[[', '{').replace(']]', '}')\n return formatter.safe_format(template, kwargs, 'TEMPLATE', mi)\n",
+ "template": "def evaluate(self, formatter, kwargs, mi, locals, template):\n template = template.replace('[[', '{').replace(']]', '}')\n return formatter.__class__().safe_format(template, kwargs, 'TEMPLATE', mi)\n",
"print": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n print args\n return None\n",
"titlecase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return titlecase(val)\n",
"test": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):\n if val:\n return value_if_set\n else:\n return value_not_set\n",
diff --git a/setup/installer/linux/freeze2.py b/setup/installer/linux/freeze2.py
index bd8463b1a7..4d5d1a63e7 100644
--- a/setup/installer/linux/freeze2.py
+++ b/setup/installer/linux/freeze2.py
@@ -360,6 +360,9 @@ class LinuxFreeze(Command):
def main():
try:
sys.argv[0] = sys.calibre_basename
+ dfv = os.environ.get('CALIBRE_DEVELOP_FROM', None)
+ if dfv and os.path.exists(dfv):
+ sys.path.insert(0, os.path.abspath(dfv))
set_default_encoding()
set_helper()
set_qt_plugin_path()
diff --git a/src/calibre/constants.py b/src/calibre/constants.py
index 76580d8db8..7b93019f2d 100644
--- a/src/calibre/constants.py
+++ b/src/calibre/constants.py
@@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
-__version__ = '0.7.43'
+__version__ = '0.7.44'
__author__ = "Kovid Goyal "
import re
diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py
index 25ffe32d87..e0367515bc 100644
--- a/src/calibre/customize/builtins.py
+++ b/src/calibre/customize/builtins.py
@@ -474,7 +474,7 @@ from calibre.devices.binatone.driver import README
from calibre.devices.hanvon.driver import N516, EB511, ALEX, AZBOOKA, THEBOOK
from calibre.devices.edge.driver import EDGE
from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS, \
- SOVOS, PICO, SUNSTECH_EB700
+ SOVOS, PICO, SUNSTECH_EB700, ARCHOS7O
from calibre.devices.sne.driver import SNE
from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN, \
GEMEI, VELOCITYMICRO, PDNOVEL_KOBO, Q600, LUMIREAD, ALURATEK_COLOR, \
@@ -581,7 +581,7 @@ plugins += [
ELONEX,
TECLAST_K3,
NEWSMY,
- PICO, SUNSTECH_EB700,
+ PICO, SUNSTECH_EB700, ARCHOS7O,
IPAPYRUS,
SOVOS,
EDGE,
diff --git a/src/calibre/debug.py b/src/calibre/debug.py
index e1c3e1809e..3a080fc57b 100644
--- a/src/calibre/debug.py
+++ b/src/calibre/debug.py
@@ -22,13 +22,15 @@ Run an embedded python interpreter.
parser.add_option('-d', '--debug-device-driver', default=False, action='store_true',
help='Debug the specified device driver.')
parser.add_option('-g', '--gui', default=False, action='store_true',
- help='Run the GUI',)
+ help='Run the GUI with debugging enabled. Debug output is '
+ 'printed to stdout and stderr.')
parser.add_option('--gui-debug', default=None,
help='Run the GUI with a debug console, logging to the'
- ' specified path',)
+ ' specified path. For internal use only, use the -g'
+ ' option to run the GUI in debug mode',)
parser.add_option('--show-gui-debug', default=None,
- help='Display the specified log file.',)
-
+ help='Display the specified log file. For internal use'
+ ' only.',)
parser.add_option('-w', '--viewer', default=False, action='store_true',
help='Run the ebook viewer',)
parser.add_option('--paths', default=False, action='store_true',
diff --git a/src/calibre/devices/eb600/driver.py b/src/calibre/devices/eb600/driver.py
index 1f723ce46a..e38f72aea5 100644
--- a/src/calibre/devices/eb600/driver.py
+++ b/src/calibre/devices/eb600/driver.py
@@ -183,9 +183,8 @@ class BOOQ(EB600):
FORMATS = ['epub', 'mobi', 'prc', 'fb2', 'pdf', 'doc', 'rtf', 'txt', 'html']
- VENDOR_NAME = 'NETRONIX'
- WINDOWS_MAIN_MEM = 'EB600'
- WINDOWS_CARD_A_MEM = 'EB600'
+ VENDOR_NAME = ['NETRONIX', '36LBOOKS']
+ WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['EB600', 'ELEQTOR']
class MENTOR(EB600):
diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py
index 2a92f46e8d..bc442f5853 100644
--- a/src/calibre/devices/interface.py
+++ b/src/calibre/devices/interface.py
@@ -35,6 +35,16 @@ class DevicePlugin(Plugin):
#: Height for thumbnails on the device
THUMBNAIL_HEIGHT = 68
+ #: Width for thumbnails on the device. Setting this will force thumbnails
+ #: to this size, not preserving aspect ratio. If it is not set, then
+ #: the aspect ratio will be preserved and the thumbnail will be no higher
+ #: than THUMBNAIL_HEIGHT
+ # THUMBNAIL_WIDTH = 68
+
+ #: 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']
diff --git a/src/calibre/devices/nook/driver.py b/src/calibre/devices/nook/driver.py
index ca05885645..39d0763735 100644
--- a/src/calibre/devices/nook/driver.py
+++ b/src/calibre/devices/nook/driver.py
@@ -89,21 +89,21 @@ class NOOK_COLOR(NOOK):
BCD = [0x216]
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'EBOOK_DISK'
- EBOOK_DIR_MAIN = 'My Files/Books'
+ EBOOK_DIR_MAIN = 'My Files'
- '''
def create_upload_path(self, path, mdata, fname, create_dirs=True):
filepath = NOOK.create_upload_path(self, path, mdata, fname,
- create_dirs=create_dirs)
- edm = self.EBOOK_DIR_MAIN.replace('/', os.sep)
- npath = os.path.join(edm, _('News')) + os.sep
- if npath in filepath:
- filepath = filepath.replace(npath, os.sep.join('My Files',
- 'Magazines')+os.sep)
- filedir = os.path.dirname(filepath)
- if create_dirs and not os.path.exists(filedir):
- os.makedirs(filedir)
+ create_dirs=False)
+ edm = self.EBOOK_DIR_MAIN
+ subdir = 'Books'
+ if mdata.tags:
+ if _('News') in mdata.tags:
+ subdir = 'Magazines'
+ filepath = filepath.replace(os.sep+edm+os.sep,
+ os.sep+edm+os.sep+subdir+os.sep)
+ filedir = os.path.dirname(filepath)
+ if create_dirs and not os.path.exists(filedir):
+ os.makedirs(filedir)
return filepath
- '''
diff --git a/src/calibre/devices/prs505/__init__.py b/src/calibre/devices/prs505/__init__.py
index 48b7d98123..1a59cb81a6 100644
--- a/src/calibre/devices/prs505/__init__.py
+++ b/src/calibre/devices/prs505/__init__.py
@@ -8,5 +8,5 @@ CACHE_XML = 'Sony Reader/database/cache.xml'
CACHE_EXT = 'Sony Reader/database/cacheExt.xml'
MEDIA_THUMBNAIL = 'database/thumbnail'
-CACHE_THUMBNAIL = 'Sony Reader/database/thumbnail'
+CACHE_THUMBNAIL = 'Sony Reader/thumbnail'
diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py
index 0f6668891a..3768b8be62 100644
--- a/src/calibre/devices/prs505/driver.py
+++ b/src/calibre/devices/prs505/driver.py
@@ -81,12 +81,19 @@ class PRS505(USBMS):
_('Set this option to have separate book covers uploaded '
'every time you connect your device. Unset this option if '
'you have so many books on the reader that performance is '
- 'unacceptable.')
+ 'unacceptable.'),
+ _('Preserve cover aspect ratio when building thumbnails') +
+ ':::' +
+ _('Set this option if you want the cover thumbnails to have '
+ 'the same aspect ratio (width to height) as the cover. '
+ 'Unset it if you want the thumbnail to be the maximum size, '
+ 'ignoring aspect ratio.')
]
EXTRA_CUSTOMIZATION_DEFAULT = [
', '.join(['series', 'tags']),
False,
- False
+ False,
+ True
]
OPT_COLLECTIONS = 0
@@ -96,7 +103,7 @@ class PRS505(USBMS):
plugboard = None
plugboard_func = None
- THUMBNAIL_HEIGHT = 200
+ THUMBNAIL_HEIGHT = 217
MAX_PATH_LEN = 201 # 250 - (max(len(CACHE_THUMBNAIL), len(MEDIA_THUMBNAIL)) +
# len('main_thumbnail.jpg') + 1)
@@ -138,6 +145,13 @@ class PRS505(USBMS):
if not write_cache(self._card_b_prefix):
self._card_b_prefix = None
self.booklist_class.rebuild_collections = self.rebuild_collections
+ # Set the thumbnail width to the theoretical max if the user has asked
+ # that we do not preserve aspect ratio
+ if not self.settings().extra_customization[3]:
+ self.THUMBNAIL_WIDTH = 168
+ # Set WANTS_UPDATED_THUMBNAILS if the user has asked that thumbnails be
+ # updated on every connect
+ self.WANTS_UPDATED_THUMBNAILS = self.settings().extra_customization[2]
def get_device_information(self, end_session=True):
return (self.gui_name, '', '', '')
diff --git a/src/calibre/devices/teclast/driver.py b/src/calibre/devices/teclast/driver.py
index f406448ad2..078e59da5b 100644
--- a/src/calibre/devices/teclast/driver.py
+++ b/src/calibre/devices/teclast/driver.py
@@ -41,6 +41,16 @@ class NEWSMY(TECLAST_K3):
WINDOWS_MAIN_MEM = 'NEWSMY'
WINDOWS_CARD_A_MEM = 'USBDISK____SD'
+class ARCHOS7O(TECLAST_K3):
+ name = 'Archos 7O device interface'
+ gui_name = 'Archos'
+ description = _('Communicate with the Archos reader.')
+
+ FORMATS = ['epub', 'mobi', 'fb2', 'rtf', 'ap', 'html', 'pdf', 'txt']
+
+ VENDOR_NAME = 'ARCHOS'
+ WINDOWS_MAIN_MEM = 'USB-MSC'
+
class PICO(NEWSMY):
name = 'Pico device interface'
gui_name = 'Pico'
diff --git a/src/calibre/ebooks/__init__.py b/src/calibre/ebooks/__init__.py
index da4d1178eb..4dc97f43ed 100644
--- a/src/calibre/ebooks/__init__.py
+++ b/src/calibre/ebooks/__init__.py
@@ -113,7 +113,7 @@ def render_html_svg_workaround(path_to_html, log, width=590, height=750):
def render_html(path_to_html, width=590, height=750, as_xhtml=True):
from PyQt4.QtWebKit import QWebPage
- from PyQt4.Qt import QEventLoop, QPalette, Qt, SIGNAL, QUrl, QSize
+ from PyQt4.Qt import QEventLoop, QPalette, Qt, QUrl, QSize
from calibre.gui2 import is_ok_to_use_qt
if not is_ok_to_use_qt(): return None
path_to_html = os.path.abspath(path_to_html)
@@ -127,8 +127,7 @@ def render_html(path_to_html, width=590, height=750, as_xhtml=True):
page.mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
loop = QEventLoop()
renderer = HTMLRenderer(page, loop)
- page.connect(page, SIGNAL('loadFinished(bool)'), renderer,
- Qt.QueuedConnection)
+ page.loadFinished.connect(renderer, type=Qt.QueuedConnection)
if as_xhtml:
page.mainFrame().setContent(open(path_to_html, 'rb').read(),
'application/xhtml+xml', QUrl.fromLocalFile(path_to_html))
@@ -136,6 +135,7 @@ def render_html(path_to_html, width=590, height=750, as_xhtml=True):
page.mainFrame().load(QUrl.fromLocalFile(path_to_html))
loop.exec_()
renderer.loop = renderer.page = None
+ page.loadFinished.disconnect()
del page
del loop
if isinstance(renderer.exception, ParserError) and as_xhtml:
diff --git a/src/calibre/ebooks/chm/reader.py b/src/calibre/ebooks/chm/reader.py
index 025e252005..04ce6d5efe 100644
--- a/src/calibre/ebooks/chm/reader.py
+++ b/src/calibre/ebooks/chm/reader.py
@@ -139,6 +139,13 @@ class CHMReader(CHMFile):
if self.hhc_path not in files and files:
self.hhc_path = files[0]
+ if self.hhc_path == '.hhc' and self.hhc_path not in files:
+ from calibre import walk
+ for x in walk(output_dir):
+ if os.path.basename(x).lower() in ('index.htm', 'index.html'):
+ self.hhc_path = os.path.relpath(x, output_dir)
+ break
+
def _reformat(self, data, htmlpath):
try:
data = xml_to_unicode(data, strip_encoding_pats=True)[0]
diff --git a/src/calibre/ebooks/comic/input.py b/src/calibre/ebooks/comic/input.py
index 04d097ac67..c9b11e31f2 100755
--- a/src/calibre/ebooks/comic/input.py
+++ b/src/calibre/ebooks/comic/input.py
@@ -53,7 +53,7 @@ def find_pages(dir, sort_on_mtime=False, verbose=False):
prints('\t'+'\n\t'.join([os.path.basename(p) for p in pages]))
return pages
-class PageProcessor(list):
+class PageProcessor(list): # {{{
'''
Contains the actual image rendering logic. See :method:`render` and
:method:`process_pages`.
@@ -111,6 +111,13 @@ class PageProcessor(list):
SCRWIDTH, SCRHEIGHT = self.opts.output_profile.comic_screen_size
+ try:
+ if self.opts.comic_image_size:
+ SCRWIDTH, SCRHEIGHT = map(int, [x.strip() for x in
+ self.opts.comic_image_size.split('x')])
+ except:
+ pass # Ignore
+
if self.opts.keep_aspect_ratio:
# Preserve the aspect ratio by adding border
aspect = float(sizex) / float(sizey)
@@ -170,6 +177,7 @@ class PageProcessor(list):
dest = dest[:-1]
os.rename(dest+'8', dest)
self.append(dest)
+# }}}
def render_pages(tasks, dest, opts, notification=lambda x, y: x):
'''
@@ -291,7 +299,11 @@ class ComicInput(InputFormatPlugin):
OptionRecommendation(name='no_process', recommended_value=False,
help=_("Apply no processing to the image")),
OptionRecommendation(name='dont_grayscale', recommended_value=False,
- help=_('Do not convert the image to grayscale (black and white)'))
+ help=_('Do not convert the image to grayscale (black and white)')),
+ OptionRecommendation(name='comic_image_size', recommended_value=None,
+ help=_('Specify the image size as widthxheight pixels. Normally,'
+ ' an image size is automatically calculated from the output '
+ 'profile, this option overrides it.')),
])
recommendations = set([
diff --git a/src/calibre/ebooks/conversion/cli.py b/src/calibre/ebooks/conversion/cli.py
index 33ae61f16a..975507e2a7 100644
--- a/src/calibre/ebooks/conversion/cli.py
+++ b/src/calibre/ebooks/conversion/cli.py
@@ -46,7 +46,8 @@ HEURISTIC_OPTIONS = ['markup_chapter_headings',
'italicize_common_cases', 'fix_indents',
'html_unwrap_factor', 'unwrap_lines',
'delete_blank_paragraphs', 'format_scene_breaks',
- 'dehyphenate', 'renumber_headings']
+ 'dehyphenate', 'renumber_headings',
+ 'replace_scene_breaks']
def print_help(parser, log):
help = parser.format_help().encode(preferred_encoding, 'replace')
diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py
index 5807ba5f8f..70b6ca657e 100644
--- a/src/calibre/ebooks/conversion/plumber.py
+++ b/src/calibre/ebooks/conversion/plumber.py
@@ -531,6 +531,11 @@ OptionRecommendation(name='format_scene_breaks',
'Replace soft scene breaks that use multiple blank lines with'
'horizontal rules.')),
+OptionRecommendation(name='replace_scene_breaks',
+ recommended_value='', level=OptionRecommendation.LOW,
+ help=_('Replace scene breaks with the specified text. By default, the '
+ 'text from the input document is used.')),
+
OptionRecommendation(name='dehyphenate',
recommended_value=True, level=OptionRecommendation.LOW,
help=_('Analyze hyphenated words throughout the document. The '
diff --git a/src/calibre/ebooks/conversion/utils.py b/src/calibre/ebooks/conversion/utils.py
index ad2214fcb5..63eca10714 100644
--- a/src/calibre/ebooks/conversion/utils.py
+++ b/src/calibre/ebooks/conversion/utils.py
@@ -24,10 +24,16 @@ class HeuristicProcessor(object):
self.chapters_no_title = 0
self.chapters_with_title = 0
self.blanks_deleted = False
+ self.blanks_between_paragraphs = False
self.linereg = re.compile('(?<=)', re.IGNORECASE|re.DOTALL)
- self.blankreg = re.compile(r'\s*(?P]*>)\s*(?P
)', re.IGNORECASE)
- self.softbreak = re.compile(r'\s*(?P]*>)\s*(?P
)', re.IGNORECASE)
- self.multi_blank = re.compile(r'(\s*]*>\s*
){2,}', re.IGNORECASE)
+ self.blankreg = re.compile(r'\s*(?P]*>)\s*(?P
)', re.IGNORECASE)
+ self.anyblank = re.compile(r'\s*(?P]*>)\s*(?P
)', re.IGNORECASE)
+ self.multi_blank = re.compile(r'(\s*]*>\s*
(\s*]*>\s*
\s*)*){2,}(?!\s*]*>\s*
(\s*]*>\s*
\s*)*){2,}', re.IGNORECASE)
+ self.line_open = "<(?Pp|div)[^>]*>\s*(<(?Pfont|span|[ibu])[^>]*>)?\s*(<(?Pfont|span|[ibu])[^>]*>)?\s*(<(?Pfont|span|[ibu])[^>]*>)?\s*"
+ self.line_close = "((?P=inner3)>)?\s*((?P=inner2)>)?\s*((?P=inner1)>)?\s*(?P=outer)>"
+ self.single_blank = re.compile(r'(\s*]*>\s*
)', re.IGNORECASE)
+ self.scene_break_open = ''
def is_pdftohtml(self, src):
return '' in src[:1000]
@@ -42,8 +48,10 @@ class HeuristicProcessor(object):
" chapters. - " + unicode(chap))
return '
'+chap+'
\n'
else:
- txt_chap = html2text(chap)
- txt_title = html2text(title)
+ delete_whitespace = re.compile('^\s*(?P.*?)\s*$')
+ delete_quotes = re.compile('\'\"')
+ txt_chap = delete_quotes.sub('', delete_whitespace.sub('\g', html2text(chap)))
+ txt_title = delete_quotes.sub('', delete_whitespace.sub('\g', html2text(title)))
self.html_preprocess_sections = self.html_preprocess_sections + 1
self.log.debug("marked " + unicode(self.html_preprocess_sections) +
" chapters & titles. - " + unicode(chap) + ", " + unicode(title))
@@ -184,19 +192,17 @@ class HeuristicProcessor(object):
# Build the Regular Expressions in pieces
init_lookahead = "(?=<(p|div))"
- chapter_line_open = "<(?Pp|div)[^>]*>\s*(<(?Pfont|span|[ibu])[^>]*>)?\s*(<(?Pfont|span|[ibu])[^>]*>)?\s*(<(?Pfont|span|[ibu])[^>]*>)?\s*"
+ chapter_line_open = self.line_open
title_line_open = "<(?Pp|div)[^>]*>\s*(<(?Pfont|span|[ibu])[^>]*>)?\s*(<(?Pfont|span|[ibu])[^>]*>)?\s*(<(?Pfont|span|[ibu])[^>]*>)?\s*"
chapter_header_open = r"(?P"
title_header_open = r"(?P"
chapter_header_close = ")\s*"
title_header_close = ")"
- chapter_line_close = "((?P=inner3)>)?\s*((?P=inner2)>)?\s*((?P=inner1)>)?\s*(?P=outer)>"
+ chapter_line_close = self.line_close
title_line_close = "((?P=inner6)>)?\s*((?P=inner5)>)?\s*((?P=inner4)>)?\s*(?P=outer2)>"
is_pdftohtml = self.is_pdftohtml(html)
if is_pdftohtml:
- chapter_line_open = "<(?Pp)[^>]*>(\s*<[ibu][^>]*>)?\s*"
- chapter_line_close = "\s*([ibu][^>]*>\s*)?(?P=outer)>"
title_line_open = "<(?Pp)[^>]*>\s*"
title_line_close = "\s*(?P=outer2)>"
@@ -371,13 +377,17 @@ class HeuristicProcessor(object):
html = re.sub(ur'\s*\s*', ' ', html)
# Delete microsoft 'smart' tags
html = re.sub('(?i)?st1:\w+>', '', html)
- # Delete self closing paragraph tags
- html = re.sub('', '', html)
+ # Re-open self closing paragraph tags
+ html = re.sub('/]*/>', '
', html)
# Get rid of empty span, bold, font, em, & italics tags
html = re.sub(r"\s*]*>\s*(]*>\s*){0,2}\s*\s*", " ", html)
- html = re.sub(r"\s*<(font|[ibu]|em)[^>]*>\s*(<(font|[ibu]|em)[^>]*>\s*(font|[ibu]|em)>\s*){0,2}\s*(font|[ibu]|em)>", " ", html)
+ html = re.sub(r"\s*<(font|[ibu]|em|strong)[^>]*>\s*(<(font|[ibu]|em|strong)[^>]*>\s*(font|[ibu]|em|strong)>\s*){0,2}\s*(font|[ibu]|em|strong)>", " ", html)
html = re.sub(r"\s*]*>\s*(]>\s*){0,2}\s*\s*", " ", html)
- html = re.sub(r"\s*<(font|[ibu]|em)[^>]*>\s*(<(font|[ibu]|em)[^>]*>\s*(font|[ibu]|em)>\s*){0,2}\s*(font|[ibu]|em)>", " ", html)
+ html = re.sub(r"\s*<(font|[ibu]|em|strong)[^>]*>\s*(<(font|[ibu]|em|strong)[^>]*>\s*(font|[ibu]|em|strong)>\s*){0,2}\s*(font|[ibu]|em|strong)>", " ", html)
+ # delete surrounding divs from empty paragraphs
+ html = re.sub('', '
', html)
+ # Empty heading tags
+ html = re.sub(r'(?i)\s*', '', html)
self.deleted_nbsps = True
return html
@@ -416,10 +426,99 @@ class HeuristicProcessor(object):
return True
return False
+ def merge_blanks(self, html, blanks_count=None):
+ base_em = .5 # Baseline is 1.5em per blank line, 1st line is .5 em css and 1em for the nbsp
+ em_per_line = 1.5 # Add another 1.5 em for each additional blank
+
+ def merge_matches(match):
+ to_merge = match.group(0)
+ lines = float(len(self.single_blank.findall(to_merge))) - 1.
+ em = base_em + (em_per_line * lines)
+ if to_merge.find('whitespace'):
+ newline = self.any_multi_blank.sub('\n
', match.group(0))
+ else:
+ newline = self.any_multi_blank.sub('\n
', match.group(0))
+ return newline
+
+ html = self.any_multi_blank.sub(merge_matches, html)
+ return html
+
+ def detect_whitespace(self, html):
+ blanks_around_headings = re.compile(r'(?P(]*>\s*
\s*){1,}\s*)?(?P\d+)[^>]*>.*?)(?P\s*(]*>\s*
\s*){1,})?', re.IGNORECASE)
+ blanks_n_nopunct = re.compile(r'(?P(]*>\s*
\s*){1,}\s*)?]*>\s*(<(span|[ibu]|em|strong|font)[^>]*>\s*)*.{1,100}?[^\W]((span|[ibu]|em|strong|font)>\s*)*
(?P\s*(]*>\s*
\s*){1,})?', re.IGNORECASE)
+
+ def merge_header_whitespace(match):
+ initblanks = match.group('initparas')
+ endblanks = match.group('initparas')
+ heading = match.group('heading')
+ top_margin = ''
+ bottom_margin = ''
+ if initblanks is not None:
+ top_margin = 'margin-top:'+str(len(self.single_blank.findall(initblanks)))+'em;'
+ if endblanks is not None:
+ bottom_margin = 'margin-bottom:'+str(len(self.single_blank.findall(initblanks)))+'em;'
+
+ if initblanks == None and endblanks == None:
+ return heading
+ else:
+ heading = re.sub('(?i)\d+)[^>]*>', '\n\n'+' style="'+top_margin+bottom_margin+'">', heading)
+ return heading
+
+ html = blanks_around_headings.sub(merge_header_whitespace, html)
+
+ def markup_whitespaces(match):
+ blanks = match.group(0)
+ blanks = self.blankreg.sub('\n
', blanks)
+ return blanks
+
+ html = blanks_n_nopunct.sub(markup_whitespaces, html)
+ if self.html_preprocess_sections > self.min_chapters:
+ html = re.sub('(?si)^.*?(?=
', html)
+ else:
+ html = self.blankreg.sub('\n
', html)
+ return html
+
+ def markup_user_break(self, replacement_break):
+ '''
+ Takes string a user supplies and wraps it in markup that will be centered with
+ appropriate margins.
and
tags are allowed. If the user specifies
+ a style with width attributes in the
tag then the appropriate margins are
+ applied to wrapping divs. This is because many ebook devices don't support margin:auto
+ All other html is converted to text.
+ '''
+ hr_open = ''
+ if re.findall('(<|>)', replacement_break):
+ if re.match('^
\d+).*', '\g', replacement_break))
+ replacement_break = re.sub('(?i)(width=\d+\%?|width:\s*\d+(\%|px|pt|em)?;?)', '', replacement_break)
+ divpercent = (100 - width) / 2
+ hr_open = re.sub('45', str(divpercent), hr_open)
+ scene_break = hr_open+replacement_break+'
'
+ else:
+ scene_break = hr_open+'
'
+ elif re.match('^
'
+ else:
+ from calibre.utils.html2text import html2text
+ replacement_break = html2text(replacement_break)
+ replacement_break = re.sub('\s', ' ', replacement_break)
+ scene_break = self.scene_break_open+replacement_break+'
'
+ else:
+ replacement_break = re.sub('\s', ' ', replacement_break)
+ scene_break = self.scene_break_open+replacement_break+'
'
+
+ return scene_break
+
def __call__(self, html):
self.log.debug("********* Heuristic processing HTML *********")
-
# Count the words in the document to estimate how many chapters to look for and whether
# other types of processing are attempted
try:
@@ -433,7 +532,7 @@ class HeuristicProcessor(object):
# Arrange line feeds and tags so the line_length and no_markup functions work correctly
html = self.arrange_htm_line_endings(html)
-
+ #self.dump(html, 'after_arrange_line_endings')
if self.cleanup_required():
###### Check Markup ######
#
@@ -453,27 +552,32 @@ class HeuristicProcessor(object):
# fix indents must run before this step, as it removes non-breaking spaces
html = self.cleanup_markup(html)
+ is_pdftohtml = self.is_pdftohtml(html)
+ if is_pdftohtml:
+ self.line_open = "<(?Pp)[^>]*>(\s*<[ibu][^>]*>)?\s*"
+ self.line_close = "\s*([ibu][^>]*>\s*)?(?P=outer)>"
+
# ADE doesn't render
, change to empty paragraphs
#html = re.sub('
]*>', u'\u00a0
', html)
# Determine whether the document uses interleaved blank lines
- blanks_between_paragraphs = self.analyze_blanks(html)
+ self.blanks_between_paragraphs = self.analyze_blanks(html)
- #self.dump(html, 'before_chapter_markup')
# detect chapters/sections to match xpath or splitting logic
if getattr(self.extra_opts, 'markup_chapter_headings', False):
- html = self.markup_chapters(html, self.totalwords, blanks_between_paragraphs)
+ html = self.markup_chapters(html, self.totalwords, self.blanks_between_paragraphs)
+ #self.dump(html, 'after_chapter_markup')
if getattr(self.extra_opts, 'italicize_common_cases', False):
html = self.markup_italicis(html)
# If more than 40% of the lines are empty paragraphs and the user has enabled delete
# blank paragraphs then delete blank lines to clean up spacing
- if blanks_between_paragraphs and getattr(self.extra_opts, 'delete_blank_paragraphs', False):
+ if self.blanks_between_paragraphs and getattr(self.extra_opts, 'delete_blank_paragraphs', False):
self.log.debug("deleting blank lines")
self.blanks_deleted = True
- html = self.multi_blank.sub('\n
', html)
+ html = self.multi_blank.sub('\n
', html)
html = self.blankreg.sub('', html)
# Determine line ending type
@@ -514,7 +618,7 @@ class HeuristicProcessor(object):
if self.html_preprocess_sections < self.min_chapters and getattr(self.extra_opts, 'markup_chapter_headings', False):
self.log.debug("Looking for more split points based on punctuation,"
" currently have " + unicode(self.html_preprocess_sections))
- chapdetect3 = re.compile(r'<(?P(p|div)[^>]*)>\s*(?P(]*>)?\s*(?!([*#•]+\s*)+)(<[ibu][^>]*>){0,2}\s*(]*>)?\s*(<[ibu][^>]*>){0,2}\s*(]*>)?\s*.?(?=[a-z#\-*\s]+<)([a-z#-*]+\s*){1,5}\s*\s*()?([ibu]>){0,2}\s*()?\s*([ibu]>){0,2}\s*()?\s*(p|div)>)', re.IGNORECASE)
+ chapdetect3 = re.compile(r'<(?P(p|div)[^>]*)>\s*(?P(]*>)?\s*(?!([\W]+\s*)+)(<[ibu][^>]*>){0,2}\s*(]*>)?\s*(<[ibu][^>]*>){0,2}\s*(]*>)?\s*.?(?=[a-z#\-*\s]+<)([a-z#-*]+\s*){1,5}\s*\s*()?([ibu]>){0,2}\s*()?\s*([ibu]>){0,2}\s*()?\s*(p|div)>)', re.IGNORECASE)
html = chapdetect3.sub(self.chapter_break, html)
if getattr(self.extra_opts, 'renumber_headings', False):
@@ -524,15 +628,32 @@ class HeuristicProcessor(object):
doubleheading = re.compile(r'(?P]*>.+?\s*(<(?!h\d)[^>]*>\s*)*)[^>]*>.+?)', re.IGNORECASE)
html = doubleheading.sub('\g'+'\n'+'
', html)
+ # If scene break formatting is enabled, find all blank paragraphs that definitely aren't scenebreaks,
+ # style it with the 'whitespace' class. All remaining blank lines are styled as softbreaks.
+ # Multiple sequential blank paragraphs are merged with appropriate margins
+ # If non-blank scene breaks exist they are center aligned and styled with appropriate margins.
if getattr(self.extra_opts, 'format_scene_breaks', False):
- # Center separator lines
- html = re.sub(u'<(?Pp|div)[^>]*>\s*(<(?Pfont|span|[ibu])[^>]*>)?\s*(<(?Pfont|span|[ibu])[^>]*>)?\s*(<(?Pfont|span|[ibu])[^>]*>)?\s*(?P([*#•=✦]+\s*)+)\s*((?P=inner3)>)?\s*((?P=inner2)>)?\s*((?P=inner1)>)?\s*(?P=outer)>', '' + '\g' + '
', html)
- if not self.blanks_deleted:
- html = self.multi_blank.sub('\n
', html)
- html = re.sub(']*>\s*
', '
', html)
+ html = self.detect_whitespace(html)
+ html = self.detect_soft_breaks(html)
+ blanks_count = len(self.any_multi_blank.findall(html))
+ if blanks_count >= 1:
+ html = self.merge_blanks(html, blanks_count)
+ scene_break_regex = self.line_open+'(?![\w\'\"])(?P((?P((?!\s)\W))\s*(?P=break_char)?)+)\s*'+self.line_close
+ scene_break = re.compile(r'%s' % scene_break_regex, re.IGNORECASE|re.UNICODE)
+ # If the user has enabled scene break replacement, then either softbreaks
+ # or 'hard' scene breaks are replaced, depending on which is in use
+ # Otherwise separator lines are centered, use a bit larger margin in this case
+ replacement_break = getattr(self.extra_opts, 'replace_scene_breaks', None)
+ if replacement_break:
+ replacement_break = self.markup_user_break(replacement_break)
+ if len(scene_break.findall(html)) >= 1:
+ html = scene_break.sub(replacement_break, html)
+ else:
+ html = re.sub(']*>\s*
', replacement_break, html)
+ else:
+ html = scene_break.sub(self.scene_break_open+'\g'+'', html)
if self.deleted_nbsps:
- # put back non-breaking spaces in empty paragraphs to preserve original formatting
- html = self.blankreg.sub('\n'+r'\g'+u'\u00a0'+r'\g', html)
- html = self.softbreak.sub('\n'+r'\g'+u'\u00a0'+r'\g', html)
+ # put back non-breaking spaces in empty paragraphs so they render correctly
+ html = self.anyblank.sub('\n'+r'\g'+u'\u00a0'+r'\g', html)
return html
diff --git a/src/calibre/ebooks/epub/input.py b/src/calibre/ebooks/epub/input.py
index ec2004d81c..e22ed27371 100644
--- a/src/calibre/ebooks/epub/input.py
+++ b/src/calibre/ebooks/epub/input.py
@@ -175,6 +175,19 @@ class EPUBInput(InputFormatPlugin):
raise ValueError(
'EPUB files with DTBook markup are not supported')
+ for x in list(opf.iterspine()):
+ ref = x.get('idref', None)
+ if ref is None:
+ x.getparent().remove(x)
+ continue
+ for y in opf.itermanifest():
+ if y.get('id', None) == ref and y.get('media-type', None) in \
+ ('application/vnd.adobe-page-template+xml',):
+ p = x.getparent()
+ if p is not None:
+ p.remove(x)
+ break
+
with open('content.opf', 'wb') as nopf:
nopf.write(opf.render())
diff --git a/src/calibre/ebooks/fb2/fb2ml.py b/src/calibre/ebooks/fb2/fb2ml.py
index 515bdee9df..6af058da7b 100644
--- a/src/calibre/ebooks/fb2/fb2ml.py
+++ b/src/calibre/ebooks/fb2/fb2ml.py
@@ -71,19 +71,28 @@ class FB2MLizer(object):
return u'' + output
def clean_text(self, text):
+ # Condense empty paragraphs into a line break.
+ text = re.sub(r'(?miu)(\s*
\s*){3,}', '
', text)
+ # Remove empty paragraphs.
text = re.sub(r'(?miu)\s*
', '', text)
+ # Clean up pargraph endings.
text = re.sub(r'(?miu)\s*', '', text)
+ # Put paragraphs following a paragraph on a separate line.
text = re.sub(r'(?miu)\s*', '
\n\n', text)
+ # Remove empty title elements.
text = re.sub(r'(?miu)
\s*', '', text)
text = re.sub(r'(?miu)\s+', '', text)
+ # Remove empty sections.
text = re.sub(r'(?miu)', '', text)
+ # Clean up sections start and ends.
text = re.sub(r'(?miu)\s*', '\n', text)
text = re.sub(r'(?miu)\s*', '\n\n', text)
text = re.sub(r'(?miu)\s*', '\n', text)
text = re.sub(r'(?miu)\s*', '\n', text)
- text = re.sub(r'(?miu)\n\n', text)
+ # Put sectnions followed by sections on a separate line.
+ text = re.sub(r'(?miu)\s*\n\n', text)
if self.opts.insert_blank_line:
text = re.sub(r'(?miu)', '', text)
@@ -338,6 +347,11 @@ class FB2MLizer(object):
tags = []
# First tag in tree
tag = barename(elem_tree.tag)
+ # Number of blank lines above tag
+ try:
+ ems = int(round((float(style.marginTop) / style.fontSize) - 1))
+ except:
+ ems = 0
# Convert TOC entries to s and add s
if self.opts.sectionize == 'toc':
@@ -370,7 +384,9 @@ class FB2MLizer(object):
fb2_out.append('')
self.section_level += 1
- # Process the XHTML tag if it needs to be converted to an FB2 tag.
+ # Process the XHTML tag and styles. Converted to an FB2 tag.
+ # Use individual if statement not if else. There can be
+ # only one XHTML tag but it can have multiple styles.
if tag == 'img':
if elem_tree.attrib.get('src', None):
# Only write the image tag if it is in the manifest.
@@ -381,7 +397,11 @@ class FB2MLizer(object):
fb2_out += p_txt
tags += p_tag
fb2_out.append('' % self.image_hrefs[page.abshref(elem_tree.attrib['src'])])
- elif tag == 'br':
+ if tag in ('br', 'hr') or ems:
+ if ems < 1:
+ multiplier = 1
+ else:
+ multiplier = ems
if self.in_p:
closed_tags = []
open_tags = tag_stack+tags
@@ -391,52 +411,38 @@ class FB2MLizer(object):
closed_tags.append(t)
if t == 'p':
break
- fb2_out.append('')
+ fb2_out.append('' * multiplier)
closed_tags.reverse()
for t in closed_tags:
fb2_out.append('<%s>' % t)
else:
- fb2_out.append('')
- elif tag in ('div', 'li', 'p'):
+ fb2_out.append('' * multiplier)
+ if tag in ('div', 'li', 'p'):
p_text, added_p = self.close_open_p(tag_stack+tags)
fb2_out += p_text
if added_p:
tags.append('p')
- elif tag == 'b':
+ if tag == 'b' or style['font-weight'] in ('bold', 'bolder'):
s_out, s_tags = self.handle_simple_tag('strong', tag_stack+tags)
fb2_out += s_out
tags += s_tags
- elif tag == 'i':
+ if tag == 'i' or style['font-style'] == 'italic':
s_out, s_tags = self.handle_simple_tag('emphasis', tag_stack+tags)
fb2_out += s_out
tags += s_tags
- elif tag in ('del', 'strike'):
+ if tag in ('del', 'strike') or style['text-decoration'] == 'line-through':
s_out, s_tags = self.handle_simple_tag('strikethrough', tag_stack+tags)
fb2_out += s_out
tags += s_tags
- elif tag == 'sub':
+ if tag == 'sub':
s_out, s_tags = self.handle_simple_tag('sub', tag_stack+tags)
fb2_out += s_out
tags += s_tags
- elif tag == 'sup':
+ if tag == 'sup':
s_out, s_tags = self.handle_simple_tag('sup', tag_stack+tags)
fb2_out += s_out
tags += s_tags
- # Processes style information.
- if style['font-style'] == 'italic':
- s_out, s_tags = self.handle_simple_tag('emphasis', tag_stack+tags)
- fb2_out += s_out
- tags += s_tags
- elif style['font-weight'] in ('bold', 'bolder'):
- s_out, s_tags = self.handle_simple_tag('strong', tag_stack+tags)
- fb2_out += s_out
- tags += s_tags
- elif style['text-decoration'] == 'line-through':
- s_out, s_tags = self.handle_simple_tag('strikethrough', tag_stack+tags)
- fb2_out += s_out
- tags += s_tags
-
# Process element text.
if hasattr(elem_tree, 'text') and elem_tree.text:
if not self.in_p:
diff --git a/src/calibre/ebooks/lit/input.py b/src/calibre/ebooks/lit/input.py
index ff901c3715..9ccbba543f 100644
--- a/src/calibre/ebooks/lit/input.py
+++ b/src/calibre/ebooks/lit/input.py
@@ -38,13 +38,16 @@ class LITInput(InputFormatPlugin):
if len(body) == 1 and body[0].tag == XHTML('pre'):
pre = body[0]
from calibre.ebooks.txt.processor import convert_basic, preserve_spaces, \
- separate_paragraphs_single_line
+ separate_paragraphs_single_line
+ from calibre.ebooks.chardet import xml_to_unicode
from lxml import etree
import copy
html = separate_paragraphs_single_line(pre.text)
html = preserve_spaces(html)
html = convert_basic(html).replace('',
''%XHTML_NS)
+ html = xml_to_unicode(html, strip_encoding_pats=True,
+ resolve_entities=True)[0]
root = etree.fromstring(html)
body = XPath('//h:body')(root)
pre.tag = XHTML('div')
diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py
index e1712f3668..f19b89eb88 100644
--- a/src/calibre/ebooks/metadata/epub.py
+++ b/src/calibre/ebooks/metadata/epub.py
@@ -14,7 +14,8 @@ from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup
from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.metadata.opf2 import OPF
from calibre.ptempfile import TemporaryDirectory, PersistentTemporaryFile
-from calibre import CurrentDir
+from calibre import CurrentDir, walk
+from calibre.constants import isosx
class EPubException(Exception):
pass
@@ -159,6 +160,13 @@ def get_cover(opf, opf_path, stream, reader=None):
with TemporaryDirectory('_epub_meta') as tdir:
with CurrentDir(tdir):
zf.extractall()
+ if isosx:
+ # On OS X trying to render an HTML cover which uses embedded
+ # fonts more than once in the same process causes a crash in Qt
+ # so be safe and remove the fonts.
+ for f in walk('.'):
+ if os.path.splitext(f)[1].lower() in ('.ttf', '.otf'):
+ os.remove(f)
opf_path = opf_path.replace('/', os.sep)
cpage = os.path.join(tdir, os.path.dirname(opf_path), cpage)
if not os.path.exists(cpage):
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index 62d57f2251..dfb902b5b9 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -422,6 +422,33 @@ class MetadataField(object):
elem = obj.create_metadata_element(self.name, is_dc=self.is_dc)
obj.set_text(elem, self.renderer(val))
+class TitleSortField(MetadataField):
+
+ def __get__(self, obj, type=None):
+ c = self.__real_get__(obj, type)
+ if c is None:
+ matches = obj.title_path(obj.metadata)
+ if matches:
+ for match in matches:
+ ans = match.get('{%s}file-as'%obj.NAMESPACES['opf'], None)
+ if not ans:
+ ans = match.get('file-as', None)
+ if ans:
+ c = ans
+ if not c:
+ c = self.none_is
+ else:
+ c = c.strip()
+ return c
+
+ def __set__(self, obj, val):
+ MetadataField.__set__(self, obj, val)
+ matches = obj.title_path(obj.metadata)
+ if matches:
+ for match in matches:
+ for attr in list(match.attrib):
+ if attr.endswith('file-as'):
+ del match.attrib[attr]
def serialize_user_metadata(metadata_elem, all_user_metadata, tail='\n'+(' '*8)):
from calibre.utils.config import to_json
@@ -490,6 +517,7 @@ class OPF(object): # {{{
rights = MetadataField('rights')
series = MetadataField('series', is_dc=False)
series_index = MetadataField('series_index', is_dc=False, formatter=float, none_is=1)
+ title_sort = TitleSortField('title_sort', is_dc=False)
rating = MetadataField('rating', is_dc=False, formatter=int)
pubdate = MetadataField('date', formatter=parse_date,
renderer=isoformat)
@@ -776,30 +804,6 @@ class OPF(object): # {{{
return property(fget=fget, fset=fset)
- @dynamic_property
- def title_sort(self):
-
- def fget(self):
- matches = self.title_path(self.metadata)
- if matches:
- for match in matches:
- ans = match.get('{%s}file-as'%self.NAMESPACES['opf'], None)
- if not ans:
- ans = match.get('file-as', None)
- if ans:
- return ans
-
- def fset(self, val):
- matches = self.title_path(self.metadata)
- if matches:
- for key in matches[0].attrib:
- if key.endswith('file-as'):
- matches[0].attrib.pop(key)
- matches[0].set('{%s}file-as'%self.NAMESPACES['opf'], unicode(val))
-
- return property(fget=fget, fset=fset)
-
-
@dynamic_property
def tags(self):
@@ -1129,8 +1133,6 @@ class OPFCreator(Metadata):
metadata = M.metadata()
a = metadata.append
role = {}
- if self.title_sort:
- role = {'file-as':self.title_sort}
a(DC_ELEM('title', self.title if self.title else _('Unknown'),
opf_attrs=role))
for i, author in enumerate(self.authors):
@@ -1165,6 +1167,8 @@ class OPFCreator(Metadata):
a(CAL_ELEM('calibre:series', self.series))
if self.series_index is not None:
a(CAL_ELEM('calibre:series_index', self.format_series_index()))
+ if self.title_sort:
+ a(CAL_ELEM('calibre:title_sort', self.title_sort))
if self.rating is not None:
a(CAL_ELEM('calibre:rating', str(self.rating)))
if self.timestamp is not None:
@@ -1320,7 +1324,6 @@ def test_m2o():
mi.author_sort = 'author sort'
mi.pubdate = nowf()
mi.language = 'en'
- mi.category = 'test'
mi.comments = 'what a fun book\n\n'
mi.publisher = 'publisher'
mi.isbn = 'boooo'
@@ -1335,11 +1338,11 @@ def test_m2o():
opf = metadata_to_opf(mi)
print opf
newmi = MetaInformation(OPF(StringIO(opf)))
- for attr in ('author_sort', 'title_sort', 'comments', 'category',
+ for attr in ('author_sort', 'title_sort', 'comments',
'publisher', 'series', 'series_index', 'rating',
'isbn', 'tags', 'cover_data', 'application_id',
'language', 'cover',
- 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc',
+ 'book_producer', 'timestamp',
'pubdate', 'rights', 'publication_type'):
o, n = getattr(mi, attr), getattr(newmi, attr)
if o != n and o.strip() != n.strip():
@@ -1441,4 +1444,6 @@ def test_user_metadata():
print opf.render()
if __name__ == '__main__':
- test_user_metadata()
+ #test_user_metadata()
+ #test_m2o()
+ test()
diff --git a/src/calibre/ebooks/metadata/sources/base.py b/src/calibre/ebooks/metadata/sources/base.py
new file mode 100644
index 0000000000..89ad8a7956
--- /dev/null
+++ b/src/calibre/ebooks/metadata/sources/base.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+
+__license__ = 'GPL v3'
+__copyright__ = '2011, Kovid Goyal '
+__docformat__ = 'restructuredtext en'
+
+import re
+
+from calibre.customize import Plugin
+
+class Source(Plugin):
+
+ type = _('Metadata source')
+ author = 'Kovid Goyal'
+
+ supported_platforms = ['windows', 'osx', 'linux']
+
+ result_of_identify_is_complete = True
+
+ def get_author_tokens(self, authors):
+ 'Take a list of authors and return a list of tokens useful for a '
+ 'AND search query'
+ # Leave ' in there for Irish names
+ pat = re.compile(r'[-,:;+!@#$%^&*(){}.`~"\s\[\]/]')
+ for au in authors:
+ for tok in au.split():
+ yield pat.sub('', tok)
+
+ 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]
+
+ def identify(self, log, result_queue, abort, title=None, authors=None, identifiers={}):
+ '''
+ Identify a book by its title/author/isbn/etc.
+
+ :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...'}
+ :return: None if no errors occurred, otherwise a unicode representation
+ of the error suitable for showing to the user
+
+ '''
+ return None
+
diff --git a/src/calibre/ebooks/metadata/sources/google.py b/src/calibre/ebooks/metadata/sources/google.py
new file mode 100644
index 0000000000..d9efb65ae0
--- /dev/null
+++ b/src/calibre/ebooks/metadata/sources/google.py
@@ -0,0 +1,215 @@
+#!/usr/bin/env python
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+
+__license__ = 'GPL v3'
+__copyright__ = '2011, Kovid Goyal '
+__docformat__ = 'restructuredtext en'
+
+import time
+from urllib import urlencode
+from functools import partial
+from threading import Thread
+
+from lxml import etree
+
+from calibre.ebooks.metadata.sources import Source
+from calibre.ebooks.metadata.book.base import Metadata
+from calibre.utils.date import parse_date, utcnow
+from calibre import browser, as_unicode
+
+NAMESPACES = {
+ 'openSearch':'http://a9.com/-/spec/opensearchrss/1.0/',
+ 'atom' : 'http://www.w3.org/2005/Atom',
+ 'dc': 'http://purl.org/dc/terms'
+ }
+XPath = partial(etree.XPath, namespaces=NAMESPACES)
+
+total_results = XPath('//openSearch:totalResults')
+start_index = XPath('//openSearch:startIndex')
+items_per_page = XPath('//openSearch:itemsPerPage')
+entry = XPath('//atom:entry')
+entry_id = XPath('descendant::atom:id')
+creator = XPath('descendant::dc:creator')
+identifier = XPath('descendant::dc:identifier')
+title = XPath('descendant::dc:title')
+date = XPath('descendant::dc:date')
+publisher = XPath('descendant::dc:publisher')
+subject = XPath('descendant::dc:subject')
+description = XPath('descendant::dc:description')
+language = XPath('descendant::dc:language')
+
+
+
+def to_metadata(browser, log, entry_):
+
+ def get_text(extra, x):
+ try:
+ ans = x(extra)
+ if ans:
+ ans = ans[0].text
+ if ans and ans.strip():
+ return ans.strip()
+ except:
+ log.exception('Programming error:')
+ return None
+
+
+ id_url = entry_id(entry_)[0].text
+ title_ = ': '.join([x.text for x in title(entry_)]).strip()
+ authors = [x.text.strip() for x in creator(entry_) if x.text]
+ if not authors:
+ authors = [_('Unknown')]
+ if not id_url or not title:
+ # Silently discard this entry
+ return None
+
+ mi = Metadata(title_, authors)
+ try:
+ raw = browser.open_novisit(id_url).read()
+ feed = etree.fromstring(raw)
+ extra = entry(feed)[0]
+ except:
+ log.exception('Failed to get additional details for', mi.title)
+ return mi
+
+ mi.comments = get_text(extra, description)
+ #mi.language = get_text(extra, language)
+ mi.publisher = get_text(extra, publisher)
+
+ # Author sort
+ for x in creator(extra):
+ for key, val in x.attrib.items():
+ if key.endswith('file-as') and val and val.strip():
+ mi.author_sort = val
+ break
+ # ISBN
+ isbns = []
+ for x in identifier(extra):
+ t = str(x.text).strip()
+ if t[:5].upper() in ('ISBN:', 'LCCN:', 'OCLC:'):
+ if t[:5].upper() == 'ISBN:':
+ isbns.append(t[5:])
+ if isbns:
+ mi.isbn = sorted(isbns, key=len)[-1]
+
+ # Tags
+ try:
+ btags = [x.text for x in subject(extra) if x.text]
+ tags = []
+ for t in btags:
+ tags.extend([y.strip() for y in t.split('/')])
+ tags = list(sorted(list(set(tags))))
+ except:
+ log.exception('Failed to parse tags:')
+ tags = []
+ if tags:
+ mi.tags = [x.replace(',', ';') for x in tags]
+
+ # pubdate
+ pubdate = get_text(extra, date)
+ if pubdate:
+ try:
+ default = utcnow().replace(day=15)
+ mi.pubdate = parse_date(pubdate, assume_utc=True, default=default)
+ except:
+ log.exception('Failed to parse pubdate')
+
+
+ return mi
+
+class Worker(Thread):
+
+ def __init__(self, log, entries, abort, result_queue):
+ self.browser, self.log, self.entries = browser(), log, entries
+ self.abort, self.result_queue = abort, result_queue
+ Thread.__init__(self)
+ self.daemon = True
+
+ def run(self):
+ for i in self.entries:
+ try:
+ ans = to_metadata(self.browser, self.log, i)
+ if isinstance(ans, Metadata):
+ self.result_queue.put(ans)
+ except:
+ self.log.exception(
+ 'Failed to get metadata for identify entry:',
+ etree.tostring(i))
+ if self.abort.is_set():
+ break
+
+
+class GoogleBooks(Source):
+
+ name = 'Google Books'
+
+ def create_query(self, log, title=None, authors=None, identifiers={},
+ start_index=1):
+ BASE_URL = 'http://books.google.com/books/feeds/volumes?'
+ isbn = identifiers.get('isbn', None)
+ q = ''
+ if isbn is not None:
+ q += 'isbn:'+isbn
+ elif title or authors:
+ def build_term(prefix, parts):
+ return ' '.join('in'+prefix + ':' + x for x in parts)
+ if title is not None:
+ q += build_term('title', title.split())
+ if authors:
+ q += ('+' if q else '')+build_term('author',
+ self.get_author_tokens(authors))
+
+ if isinstance(q, unicode):
+ q = q.encode('utf-8')
+ if not q:
+ return None
+ return BASE_URL+urlencode({
+ 'q':q,
+ 'max-results':20,
+ 'start-index':start_index,
+ 'min-viewability':'none',
+ })
+
+
+ def identify(self, log, result_queue, abort, title=None, authors=None, identifiers={}):
+ query = self.create_query(log, title=title, authors=authors,
+ identifiers=identifiers)
+ try:
+ raw = browser().open_novisit(query).read()
+ except Exception, e:
+ log.exception('Failed to make identify query: %r'%query)
+ return as_unicode(e)
+
+ try:
+ parser = etree.XMLParser(recover=True, no_network=True)
+ feed = etree.fromstring(raw, parser=parser)
+ entries = entry(feed)
+ except Exception, e:
+ log.exception('Failed to parse identify results')
+ return as_unicode(e)
+
+
+ groups = self.split_jobs(entries, 5) # At most 5 threads
+ if not groups:
+ return
+ workers = [Worker(log, entries, abort, result_queue) for entries in
+ groups]
+
+ if abort.is_set():
+ return
+
+ for worker in workers: worker.start()
+
+ has_alive_worker = True
+ while has_alive_worker and not abort.is_set():
+ has_alive_worker = False
+ for worker in workers:
+ if worker.is_alive():
+ has_alive_worker = True
+ time.sleep(0.1)
+
+ return None
+
+
+
+
diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py
index 2f397006a1..9576ccb637 100644
--- a/src/calibre/ebooks/mobi/reader.py
+++ b/src/calibre/ebooks/mobi/reader.py
@@ -103,6 +103,8 @@ class EXTHHeader(object):
pass
elif id == 108:
pass # Producer
+ elif id == 113:
+ pass # ASIN or UUID
#else:
# print 'unhandled metadata record', id, repr(content)
@@ -488,7 +490,7 @@ class MobiReader(object):
def remove_random_bytes(self, html):
- return re.sub('\x14|\x15|\x19|\x1c|\x1d|\xef|\x12|\x13|\xec|\x08',
+ return re.sub('\x14|\x15|\x19|\x1c|\x1d|\xef|\x12|\x13|\xec|\x08|\x01|\x02|\x03|\x04|\x05|\x06|\x07',
'', html)
def ensure_unit(self, raw, unit='px'):
diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py
index 2a71ecd43b..abba173d69 100644
--- a/src/calibre/ebooks/mobi/writer.py
+++ b/src/calibre/ebooks/mobi/writer.py
@@ -1547,6 +1547,31 @@ class MobiWriter(object):
rights = 'Unknown'
exth.write(pack('>II', EXTH_CODES['rights'], len(rights) + 8))
exth.write(rights)
+ nrecs += 1
+
+ # Write UUID as ASIN
+ uuid = None
+ from calibre.ebooks.oeb.base import OPF
+ for x in oeb.metadata['identifier']:
+ if x.get(OPF('scheme'), None).lower() == 'uuid' or unicode(x).startswith('urn:uuid:'):
+ uuid = unicode(x).split(':')[-1]
+ break
+ if uuid is None:
+ from uuid import uuid4
+ uuid = str(uuid4())
+
+ if isinstance(uuid, unicode):
+ uuid = uuid.encode('utf-8')
+ exth.write(pack('>II', 113, len(uuid) + 8))
+ exth.write(uuid)
+ nrecs += 1
+
+ # Write cdetype
+ if not self.opts.mobi_periodical:
+ data = 'EBOK'
+ exth.write(pack('>II', 501, len(data)+8))
+ exth.write(data)
+ nrecs += 1
# Add a publication date entry
if oeb.metadata['date'] != [] :
diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py
index 40b82514c1..849d161228 100644
--- a/src/calibre/ebooks/oeb/stylizer.py
+++ b/src/calibre/ebooks/oeb/stylizer.py
@@ -633,7 +633,7 @@ class Style(object):
def lineHeight(self):
if self._lineHeight is None:
result = None
- parent = self._getparent()
+ parent = self._get_parent()
if 'line-height' in self._style:
lineh = self._style['line-height']
if lineh == 'normal':
diff --git a/src/calibre/ebooks/pml/pmlconverter.py b/src/calibre/ebooks/pml/pmlconverter.py
index 20d8c7186b..7d1e74e3f4 100644
--- a/src/calibre/ebooks/pml/pmlconverter.py
+++ b/src/calibre/ebooks/pml/pmlconverter.py
@@ -603,7 +603,7 @@ class PML_HTMLizer(object):
if empty:
empty_count += 1
- if empty_count == 3:
+ if empty_count == 2:
output.append('
')
else:
empty_count = 0
diff --git a/src/calibre/ebooks/pml/pmlml.py b/src/calibre/ebooks/pml/pmlml.py
index ceb7f36124..4b2f924c7d 100644
--- a/src/calibre/ebooks/pml/pmlml.py
+++ b/src/calibre/ebooks/pml/pmlml.py
@@ -10,6 +10,8 @@ Transform OEB content into PML markup
import re
+from lxml import etree
+
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace
from calibre.ebooks.oeb.stylizer import Stylizer
from calibre.ebooks.pdb.ereader import image_name
@@ -64,8 +66,8 @@ SEPARATE_TAGS = [
'h4',
'h5',
'h6',
- 'p',
- 'div',
+ 'hr',
+ 'img',
'li',
'tr',
]
@@ -84,14 +86,18 @@ class PMLMLizer(object):
# This is used for adding \CX tags chapter markers. This is separate
# from the optional inline toc.
self.toc = {}
- for item in oeb_book.toc:
+ self.create_flat_toc(self.oeb_book.toc)
+
+ return self.pmlmlize_spine()
+
+ def create_flat_toc(self, nodes, level=0):
+ for item in nodes:
href, mid, id = item.href.partition('#')
self.get_anchor_id(href, id)
if not self.toc.get(href, None):
self.toc[href] = {}
- self.toc[href][id] = item.title
-
- return self.pmlmlize_spine()
+ self.toc[href][id] = (item.title, level)
+ self.create_flat_toc(item.nodes, level + 1)
def pmlmlize_spine(self):
self.image_hrefs = {}
@@ -122,9 +128,12 @@ class PMLMLizer(object):
text = [u'']
for item in self.oeb_book.spine:
self.log.debug('Converting %s to PML markup...' % item.href)
- stylizer = Stylizer(item.data, item.href, self.oeb_book, self.opts, self.opts.output_profile)
+ content = unicode(etree.tostring(item.data, encoding=unicode))
+ content = self.prepare_text(content)
+ content = etree.fromstring(content)
+ stylizer = Stylizer(content, item.href, self.oeb_book, self.opts, self.opts.output_profile)
text.append(self.add_page_anchor(item))
- text += self.dump_text(item.data.find(XHTML('body')), stylizer, item)
+ text += self.dump_text(content.find(XHTML('body')), stylizer, item)
return ''.join(text)
def add_page_anchor(self, page):
@@ -147,6 +156,21 @@ class PMLMLizer(object):
text = text.replace('\r', ' ')
return text
+ def prepare_string_for_pml(self, text):
+ text = self.remove_newlines(text)
+ # Replace \ with \\ so \ in the text is not interperted as
+ # a pml code.
+ text = text.replace('\\', '\\\\')
+ # Replace sequences of \\c \\c with pml sequences denoting
+ # empty lines.
+ text = text.replace('\\\\c \\\\c', '\\c \n\\c\n')
+ return text
+
+ def prepare_text(self, text):
+ # Replace empty paragraphs with \c pml codes used to denote emtpy lines.
+ text = re.sub(ur'(?<=)\s*]*>[\xc2\xa0\s]*
', '\\c\n\\c', text)
+ return text
+
def clean_text(self, text):
# Remove excessive \p tags
text = re.sub(r'\\p\s*\\p', '', text)
@@ -172,15 +196,18 @@ class PMLMLizer(object):
# Remove excessive spaces
text = re.sub('[ ]{2,}', ' ', text)
+ # Condense excessive \c empty line sequences.
+ text = re.sub('(\\c\s*\\c\s*){2,}', '\\c \n\\c\n', text)
+
# Remove excessive newlines.
text = re.sub('\n[ ]+\n', '\n\n', text)
if self.opts.remove_paragraph_spacing:
text = re.sub('\n{2,}', '\n', text)
- text = re.sub('(?imu)^(?P.+)$', lambda mo: mo.group('text') if re.search(r'\\[XxCm]', mo.group('text')) else ' %s' % mo.group('text'), text)
+ # Only indent lines that don't have special formatting
+ text = re.sub('(?imu)^(?P.+)$', lambda mo: mo.group('text') if re.search(r'\\[XxCmrctTp]', mo.group('text')) else ' %s' % mo.group('text'), text)
else:
text = re.sub('\n{3,}', '\n\n', text)
-
return text
def dump_text(self, elem, stylizer, page, tag_stack=[]):
@@ -203,7 +230,7 @@ class PMLMLizer(object):
tags.append('block')
# Process tags that need special processing and that do not have inner
- # text. Usually these require an argument
+ # text. Usually these require an argument.
if tag in IMAGE_TAGS:
if elem.attrib.get('src', None):
if page.abshref(elem.attrib['src']) not in self.image_hrefs.keys():
@@ -212,7 +239,7 @@ class PMLMLizer(object):
else:
self.image_hrefs[page.abshref(elem.attrib['src'])] = image_name('%s.png' % len(self.image_hrefs.keys()), self.image_hrefs.keys()).strip('\x00')
text.append('\\m="%s"' % self.image_hrefs[page.abshref(elem.attrib['src'])])
- if tag == 'hr':
+ elif tag == 'hr':
w = '\\w'
width = elem.get('width')
if width:
@@ -222,21 +249,27 @@ class PMLMLizer(object):
else:
w += '="50%"'
text.append(w)
+ elif tag == 'br':
+ text.append('\n\\c \n\\c\n')
+
+ # TOC markers.
toc_name = elem.attrib.get('name', None)
toc_id = elem.attrib.get('id', None)
if (toc_id or toc_name) and tag not in ('h1', 'h2','h3','h4','h5','h6',):
toc_page = page.href
if self.toc.get(toc_page, None):
for toc_x in (toc_name, toc_id):
- toc_title = self.toc[toc_page].get(toc_x, None)
+ toc_title, toc_depth = self.toc[toc_page].get(toc_x, (None, 0))
if toc_title:
- text.append('\\C0="%s"' % toc_title)
+ toc_depth = max(min(toc_depth, 4), 0)
+ text.append('\\C%s="%s"' % (toc_depth, toc_title))
# Process style information that needs holds a single tag
# Commented out because every page in an OEB book starts with this style
- #if style['page-break-before'] == 'always':
- # text.append('\\p')
+ if style['page-break-before'] == 'always':
+ text.append('\\p')
+ # Process basic PML tags.
pml_tag = TAG_MAP.get(tag, None)
if pml_tag and pml_tag not in tag_stack+tags:
text.append('\\%s' % pml_tag)
@@ -270,34 +303,60 @@ class PMLMLizer(object):
if style_tag and style_tag not in tag_stack+tags:
text.append('\\%s' % style_tag)
tags.append(style_tag)
- # margin
- # Proccess tags that contain text.
+ # margin left
+ try:
+ mms = int(float(style['margin-left']) * 100 / style.height)
+ if mms:
+ text.append('\\T="%s%%"' % mms)
+ except:
+ pass
+
+ # Soft scene breaks.
+ try:
+ ems = int(round((float(style.marginTop) / style.fontSize) - 1))
+ if ems >= 1:
+ text.append('\n\\c \n\\c\n')
+ except:
+ pass
+
+ # Proccess text within this tag.
if hasattr(elem, 'text') and elem.text:
- text.append(self.remove_newlines(elem.text))
+ text.append(self.prepare_string_for_pml(elem.text))
+ # Process inner tags
for item in elem:
text += self.dump_text(item, stylizer, page, tag_stack+tags)
+ # Close opened tags.
tags.reverse()
text += self.close_tags(tags)
- if tag in SEPARATE_TAGS:
- text.append('\n\n')
+ #if tag in SEPARATE_TAGS:
+ # text.append('\n\n')
- #if style['page-break-after'] == 'always':
- # text.append('\\p')
+ if style['page-break-after'] == 'always':
+ text.append('\\p')
+ # Process text after this tag but not within another.
if hasattr(elem, 'tail') and elem.tail:
- text.append(self.remove_newlines(elem.tail))
+ text.append(self.prepare_string_for_pml(elem.tail))
return text
def close_tags(self, tags):
text = []
for tag in tags:
+ # block isn't a real tag we just use
+ # it to determine when we need to start
+ # a new text block.
if tag == 'block':
text.append('\n\n')
else:
- text.append('\\%s' % tag)
+ # closing \c and \r need to be placed
+ # on the next line per PML spec.
+ if tag in ('c', 'r'):
+ text.append('\n\\%s' % tag)
+ else:
+ text.append('\\%s' % tag)
return text
diff --git a/src/calibre/ebooks/rtf/input.py b/src/calibre/ebooks/rtf/input.py
index ca6f2c7b95..52f6feb071 100644
--- a/src/calibre/ebooks/rtf/input.py
+++ b/src/calibre/ebooks/rtf/input.py
@@ -83,6 +83,7 @@ class RTFInput(InputFormatPlugin):
os.mkdir(debug_dir)
debug_dir = 'rtfdebug'
run_lev = 4
+ self.log('Running RTFParser in debug mode')
except:
pass
parser = ParseRtf(
@@ -230,22 +231,6 @@ class RTFInput(InputFormatPlugin):
with open('styles.css', 'ab') as f:
f.write(css)
- # def preprocess(self, fname):
- # self.log('\tPreprocessing to convert unicode characters')
- # try:
- # data = open(fname, 'rb').read()
- # from calibre.ebooks.rtf.preprocess import RtfTokenizer, RtfTokenParser
- # tokenizer = RtfTokenizer(data)
- # tokens = RtfTokenParser(tokenizer.tokens)
- # data = tokens.toRTF()
- # fname = 'preprocessed.rtf'
- # with open(fname, 'wb') as f:
- # f.write(data)
- # except:
- # self.log.exception(
- # 'Failed to preprocess RTF to convert unicode sequences, ignoring...')
- # return fname
-
def convert_borders(self, doc):
border_styles = []
style_map = {}
@@ -280,8 +265,6 @@ class RTFInput(InputFormatPlugin):
self.opts = options
self.log = log
self.log('Converting RTF to XML...')
- #Name of the preprocesssed RTF file
- # fname = self.preprocess(stream.name)
try:
xml = self.generate_xml(stream.name)
except RtfInvalidCodeException, e:
@@ -335,3 +318,4 @@ class RTFInput(InputFormatPlugin):
opf.render(open('metadata.opf', 'wb'))
return os.path.abspath('metadata.opf')
+
diff --git a/src/calibre/ebooks/rtf/rtfml.py b/src/calibre/ebooks/rtf/rtfml.py
index 1fb14eb06f..f739207018 100644
--- a/src/calibre/ebooks/rtf/rtfml.py
+++ b/src/calibre/ebooks/rtf/rtfml.py
@@ -24,14 +24,15 @@ from calibre.utils.magick.draw import save_cover_data_to, identify_data
TAGS = {
'b': '\\b',
'del': '\\deleted',
- 'h1': '\\b \\par \\pard \\hyphpar',
- 'h2': '\\b \\par \\pard \\hyphpar',
- 'h3': '\\b \\par \\pard \\hyphpar',
- 'h4': '\\b \\par \\pard \\hyphpar',
- 'h5': '\\b \\par \\pard \\hyphpar',
- 'h6': '\\b \\par \\pard \\hyphpar',
- 'li': '\\par \\pard \\hyphpar \t',
- 'p': '\\par \\pard \\hyphpar \t',
+ 'h1': '\\s1 \\afs32',
+ 'h2': '\\s2 \\afs28',
+ 'h3': '\\s3 \\afs28',
+ 'h4': '\\s4 \\afs23',
+ 'h5': '\\s5 \\afs23',
+ 'h6': '\\s6 \\afs21',
+ 'i': '\\i',
+ 'li': '\t',
+ 'p': '\t',
'sub': '\\sub',
'sup': '\\super',
'u': '\\ul',
@@ -39,15 +40,9 @@ TAGS = {
SINGLE_TAGS = {
'br': '\n{\\line }\n',
- 'div': '\n{\\line }\n',
-}
-
-SINGLE_TAGS_END = {
- 'div': '\n{\\line }\n',
}
STYLES = [
- ('display', {'block': '\\par \\pard \\hyphpar'}),
('font-weight', {'bold': '\\b', 'bolder': '\\b'}),
('font-style', {'italic': '\\i'}),
('text-align', {'center': '\\qc', 'left': '\\ql', 'right': '\\qr'}),
@@ -55,6 +50,7 @@ STYLES = [
]
BLOCK_TAGS = [
+ 'div',
'p',
'h1',
'h2',
@@ -112,14 +108,16 @@ class RTFMLizer(object):
stylizer = Stylizer(item.data, item.href, self.oeb_book,
self.opts, self.opts.output_profile)
output += self.dump_text(item.data.find(XHTML('body')), stylizer)
- output += '{\\page } '
+ output += '{\\page }'
for item in self.oeb_book.spine:
self.log.debug('Converting %s to RTF markup...' % item.href)
content = unicode(etree.tostring(item.data, encoding=unicode))
content = self.remove_newlines(content)
+ content = self.remove_tabs(content)
content = etree.fromstring(content)
stylizer = Stylizer(content, item.href, self.oeb_book, self.opts, self.opts.output_profile)
output += self.dump_text(content.find(XHTML('body')), stylizer)
+ output += '{\\page }'
output += self.footer()
output = self.insert_images(output)
output = self.clean_text(output)
@@ -134,8 +132,23 @@ class RTFMLizer(object):
return text
+ def remove_tabs(self, text):
+ self.log.debug('\Replace tabs with space for processing...')
+ text = text.replace('\t', ' ')
+
+ return text
+
def header(self):
- return u'{\\rtf1{\\info{\\title %s}{\\author %s}}\\ansi\\ansicpg1252\\deff0\\deflang1033' % (self.oeb_book.metadata.title[0].value, authors_to_string([x.value for x in self.oeb_book.metadata.creator]))
+ header = u'{\\rtf1{\\info{\\title %s}{\\author %s}}\\ansi\\ansicpg1252\\deff0\\deflang1033\n' % (self.oeb_book.metadata.title[0].value, authors_to_string([x.value for x in self.oeb_book.metadata.creator]))
+ return header + \
+ '{\\fonttbl{\\f0\\froman\\fprq2\\fcharset128 Times New Roman;}{\\f1\\froman\\fprq2\\fcharset128 Times New Roman;}{\\f2\\fswiss\\fprq2\\fcharset128 Arial;}{\\f3\\fnil\\fprq2\\fcharset128 Arial;}{\\f4\\fnil\\fprq2\\fcharset128 MS Mincho;}{\\f5\\fnil\\fprq2\\fcharset128 Tahoma;}{\\f6\\fnil\\fprq0\\fcharset128 Tahoma;}}\n' \
+ '{\\stylesheet{\\ql \\li0\\ri0\\nowidctlpar\\wrapdefault\\faauto\\rin0\\lin0\\itap0 \\rtlch\\fcs1 \\af25\\afs24\\alang1033 \\ltrch\\fcs0 \\fs24\\lang1033\\langfe255\\cgrid\\langnp1033\\langfenp255 \\snext0 Normal;}\n' \
+ '{\\s1\\ql \\li0\\ri0\\sb240\\sa120\\keepn\\nowidctlpar\\wrapdefault\\faauto\\outlinelevel0\\rin0\\lin0\\itap0 \\rtlch\\fcs1 \\ab\\af0\\afs32\\alang1033 \\ltrch\\fcs0 \\b\\fs32\\lang1033\\langfe255\\loch\\f1\\hich\\af1\\dbch\\af26\\cgrid\\langnp1033\\langfenp255 \\sbasedon15 \\snext16 \\slink21 heading 1;}\n' \
+ '{\\s2\\ql \\li0\\ri0\\sb240\\sa120\\keepn\\nowidctlpar\\wrapdefault\\faauto\\outlinelevel1\\rin0\\lin0\\itap0 \\rtlch\\fcs1 \\ab\\ai\\af0\\afs28\\alang1033 \\ltrch\\fcs0 \\b\\i\\fs28\\lang1033\\langfe255\\loch\\f1\\hich\\af1\\dbch\\af26\\cgrid\\langnp1033\\langfenp255 \\sbasedon15 \\snext16 \\slink22 heading 2;}\n' \
+ '{\\s3\\ql \\li0\\ri0\\sb240\\sa120\\keepn\\nowidctlpar\\wrapdefault\\faauto\\outlinelevel2\\rin0\\lin0\\itap0 \\rtlch\\fcs1 \\ab\\af0\\afs28\\alang1033 \\ltrch\\fcs0 \\b\\fs28\\lang1033\\langfe255\\loch\\f1\\hich\\af1\\dbch\\af26\\cgrid\\langnp1033\\langfenp255 \\sbasedon15 \\snext16 \\slink23 heading 3;}\n' \
+ '{\\s4\\ql \\li0\\ri0\\sb240\\sa120\\keepn\\nowidctlpar\\wrapdefault\\faauto\\outlinelevel3\\rin0\\lin0\\itap0 \\rtlch\\fcs1 \\ab\\ai\\af0\\afs23\\alang1033 \\ltrch\\fcs0\\b\\i\\fs23\\lang1033\\langfe255\\loch\\f1\\hich\\af1\\dbch\\af26\\cgrid\\langnp1033\\langfenp255 \\sbasedon15 \\snext16 \\slink24 heading 4;}\n' \
+ '{\\s5\\ql \\li0\\ri0\\sb240\\sa120\\keepn\\nowidctlpar\\wrapdefault\\faauto\\outlinelevel4\\rin0\\lin0\\itap0 \\rtlch\\fcs1 \\ab\\af0\\afs23\\alang1033 \\ltrch\\fcs0 \\b\\fs23\\lang1033\\langfe255\\loch\\f1\\hich\\af1\\dbch\\af26\\cgrid\\langnp1033\\langfenp255 \\sbasedon15 \\snext16 \\slink25 heading 5;}\n' \
+ '{\\s6\\ql \\li0\\ri0\\sb240\\sa120\\keepn\\nowidctlpar\\wrapdefault\\faauto\\outlinelevel5\\rin0\\lin0\\itap0 \\rtlch\\fcs1 \\ab\\af0\\afs21\\alang1033 \\ltrch\\fcs0 \\b\\fs21\\lang1033\\langfe255\\loch\\f1\\hich\\af1\\dbch\\af26\\cgrid\\langnp1033\\langfenp255 \\sbasedon15 \\snext16 \\slink26 heading 6;}}\n'
def footer(self):
return ' }'
@@ -170,19 +183,16 @@ class RTFMLizer(object):
return (hex_string, width, height)
def clean_text(self, text):
- # Remove excess spaces at beginning and end of lines
- text = re.sub('(?m)^[ ]+', '', text)
- text = re.sub('(?m)[ ]+$', '', text)
-
# Remove excessive newlines
- #text = re.sub('%s{1,1}' % os.linesep, '%s%s' % (os.linesep, os.linesep), text)
text = re.sub('%s{3,}' % os.linesep, '%s%s' % (os.linesep, os.linesep), text)
# Remove excessive spaces
text = re.sub('[ ]{2,}', ' ', text)
+ text = re.sub('\t{2,}', '\t', text)
+ text = re.sub('\t ', '\t', text)
+ # Remove excessive line breaks
text = re.sub(r'(\{\\line \}\s*){3,}', r'{\\line }{\\line }', text)
- #text = re.compile(r'(\{\\line \}\s*)+(?P}*)\s*\{\\par').sub(lambda mo: r'%s{\\par' % mo.group('brackets'), text)
# Remove non-breaking spaces
text = text.replace(u'\xa0', ' ')
@@ -222,7 +232,7 @@ class RTFMLizer(object):
block_start = ''
block_end = ''
if 'block' not in tag_stack:
- block_start = '{\\par \\pard \\hyphpar '
+ block_start = '{\\par\\pard\\hyphpar '
block_end = '}'
text += '%s SPECIAL_IMAGE-%s-REPLACE_ME %s' % (block_start, src, block_end)
@@ -245,7 +255,7 @@ class RTFMLizer(object):
tag_stack.append(style_tag)
# Proccess tags that contain text.
- if hasattr(elem, 'text') and elem.text != None and elem.text.strip() != '':
+ if hasattr(elem, 'text') and elem.text:
text += txt2rtf(elem.text)
for item in elem:
@@ -254,16 +264,15 @@ class RTFMLizer(object):
for i in range(0, tag_count):
end_tag = tag_stack.pop()
if end_tag != 'block':
- text += u'}'
+ if tag in BLOCK_TAGS:
+ text += u'\\par\\pard\\plain\\hyphpar}'
+ else:
+ text += u'}'
- single_tag_end = SINGLE_TAGS_END.get(tag, None)
- if single_tag_end:
- text += single_tag_end
-
- if hasattr(elem, 'tail') and elem.tail != None and elem.tail.strip() != '':
+ if hasattr(elem, 'tail') and elem.tail:
if 'block' in tag_stack:
text += '%s' % txt2rtf(elem.tail)
else:
- text += '{\\par \\pard \\hyphpar %s}' % txt2rtf(elem.tail)
+ text += '{\\par\\pard\\hyphpar %s}' % txt2rtf(elem.tail)
return text
diff --git a/src/calibre/ebooks/rtf2xml/ParseRtf.py b/src/calibre/ebooks/rtf2xml/ParseRtf.py
index d673836210..831183f0dd 100755
--- a/src/calibre/ebooks/rtf2xml/ParseRtf.py
+++ b/src/calibre/ebooks/rtf2xml/ParseRtf.py
@@ -238,6 +238,8 @@ class ParseRtf:
bug_handler = RtfInvalidCodeException,
)
enc = 'cp' + encode_obj.get_codepage()
+ if enc == 'cp10000':
+ enc = 'mac_roman'
msg = 'Exception in token processing'
if check_encoding_obj.check_encoding(self.__file, enc):
file_name = self.__file if isinstance(self.__file, str) \
diff --git a/src/calibre/ebooks/rtf2xml/colors.py b/src/calibre/ebooks/rtf2xml/colors.py
index d81b293bbf..eba03547c8 100755
--- a/src/calibre/ebooks/rtf2xml/colors.py
+++ b/src/calibre/ebooks/rtf2xml/colors.py
@@ -15,8 +15,10 @@
# #
# #
#########################################################################
-import sys, os, tempfile, re
+import sys, os, tempfile, re
+
from calibre.ebooks.rtf2xml import copy
+
class Colors:
"""
Change lines with color info from color numbers to the actual color names.
@@ -40,8 +42,10 @@ class Colors:
self.__file = in_file
self.__copy = copy
self.__bug_handler = bug_handler
+ self.__line = 0
self.__write_to = tempfile.mktemp()
self.__run_level = run_level
+
def __initiate_values(self):
"""
Initiate all values.
@@ -61,6 +65,7 @@ class Colors:
self.__color_num = 1
self.__line_color_exp = re.compile(r'bdr-color_:(\d+)')
# cw 3:
- msg = 'no value in self.__color_dict for key %s\n' % num
- raise self.__bug_hanlder, msg
- if hex_num == None:
+ if hex_num is None:
hex_num = '0'
+ if self.__run_level > 5:
+ msg = 'no value in self.__color_dict' \
+ 'for key %s at line %d\n' % (num, self.__line)
+ raise self.__bug_handler, msg
return hex_num
+
def __do_nothing_func(self, line):
"""
Bad RTF will have text in the color table
"""
pass
+
def convert_colors(self):
"""
Requires:
@@ -226,20 +238,16 @@ class Colors:
info, and substitute the number with the hex number.
"""
self.__initiate_values()
- read_obj = open(self.__file, 'r')
- self.__write_obj = open(self.__write_to, 'w')
- line_to_read = 1
- while line_to_read:
- line_to_read = read_obj.readline()
- line = line_to_read
- self.__token_info = line[:16]
- action = self.__state_dict.get(self.__state)
- if action == None:
- sys.stderr.write('no no matching state in module fonts.py\n')
- sys.stderr.write(self.__state + '\n')
- action(line)
- read_obj.close()
- self.__write_obj.close()
+ with open(self.__file, 'r') as read_obj:
+ with open(self.__write_to, 'w') as self.__write_obj:
+ for line in read_obj:
+ self.__line+=1
+ self.__token_info = line[:16]
+ action = self.__state_dict.get(self.__state)
+ if action is None:
+ sys.stderr.write('no matching state in module fonts.py\n')
+ sys.stderr.write(self.__state + '\n')
+ action(line)
copy_obj = copy.Copy(bug_handler = self.__bug_handler)
if self.__copy:
copy_obj.copy_file(self.__write_to, "color.data")
diff --git a/src/calibre/ebooks/rtf2xml/convert_to_tags.py b/src/calibre/ebooks/rtf2xml/convert_to_tags.py
index 6927537474..1abc672f85 100755
--- a/src/calibre/ebooks/rtf2xml/convert_to_tags.py
+++ b/src/calibre/ebooks/rtf2xml/convert_to_tags.py
@@ -33,13 +33,13 @@ class ConvertToTags:
self.__copy = copy
self.__dtd_path = dtd_path
self.__no_dtd = no_dtd
- if encoding != 'mac_roman':
- self.__encoding = 'cp' + encoding
- else:
+ self.__encoding = 'cp' + encoding
+ if encoding == 'mac_roman':
self.__encoding = 'mac_roman'
self.__indent = indent
self.__run_level = run_level
self.__write_to = tempfile.mktemp()
+ self.__convert_utf = False
def __initiate_values(self):
"""
@@ -213,7 +213,8 @@ class ConvertToTags:
if not check_encoding_obj.check_encoding(self.__file, verbose=False):
self.__write_obj.write('')
elif not check_encoding_obj.check_encoding(self.__file, self.__encoding):
- self.__write_obj.write('' % self.__encoding)
+ self.__write_obj.write('')
+ self.__convert_utf = True
else:
self.__write_obj.write('')
sys.stderr.write('Bad RTF encoding, revert to US-ASCII chars and'
@@ -253,15 +254,28 @@ class ConvertToTags:
an empty tag function.
"""
self.__initiate_values()
- self.__write_obj = open(self.__write_to, 'w')
- self.__write_dec()
- with open(self.__file, 'r') as read_obj:
- for line in read_obj:
- self.__token_info = line[:16]
- action = self.__state_dict.get(self.__token_info)
- if action is not None:
- action(line)
+ with open(self.__write_to, 'w') as self.__write_obj:
+ self.__write_dec()
+ with open(self.__file, 'r') as read_obj:
+ for line in read_obj:
+ self.__token_info = line[:16]
+ action = self.__state_dict.get(self.__token_info)
+ if action is not None:
+ action(line)
self.__write_obj.close()
+ #convert all encodings to UTF8 to avoid unsupported encodings in lxml
+ if self.__convert_utf:
+ copy_obj = copy.Copy(bug_handler = self.__bug_handler)
+ copy_obj.rename(self.__write_to, self.__file)
+ with open(self.__file, 'r') as read_obj:
+ with open(self.__write_to, 'w') as write_obj:
+ file = read_obj.read()
+ try:
+ file = file.decode(self.__encoding)
+ write_obj.write(file.encode('utf-8'))
+ except:
+ sys.stderr.write('Conversion to UTF-8 is not possible,'
+ ' encoding should be very carefully checked')
copy_obj = copy.Copy(bug_handler = self.__bug_handler)
if self.__copy:
copy_obj.copy_file(self.__write_to, "convert_to_tags.data")
diff --git a/src/calibre/ebooks/rtf2xml/default_encoding.py b/src/calibre/ebooks/rtf2xml/default_encoding.py
index 3ddfbcd321..c0a43db800 100755
--- a/src/calibre/ebooks/rtf2xml/default_encoding.py
+++ b/src/calibre/ebooks/rtf2xml/default_encoding.py
@@ -75,12 +75,16 @@ class DefaultEncoding:
self._encoding()
self.__datafetched = True
code_page = 'ansicpg' + self.__code_page
+ if self.__code_page == '10000':
+ self.__code_page = 'mac_roman'
return self.__platform, code_page, self.__default_num
def get_codepage(self):
if not self.__datafetched:
self._encoding()
self.__datafetched = True
+ if self.__code_page == '10000':
+ self.__code_page = 'mac_roman'
return self.__code_page
def get_platform(self):
diff --git a/src/calibre/ebooks/rtf2xml/fonts.py b/src/calibre/ebooks/rtf2xml/fonts.py
index b85717ce48..45ed3c1957 100755
--- a/src/calibre/ebooks/rtf2xml/fonts.py
+++ b/src/calibre/ebooks/rtf2xml/fonts.py
@@ -16,7 +16,9 @@
# #
#########################################################################
import sys, os, tempfile
+
from calibre.ebooks.rtf2xml import copy
+
class Fonts:
"""
Change lines with font info from font numbers to the actual font names.
@@ -45,6 +47,7 @@ class Fonts:
self.__default_font_num = default_font_num
self.__write_to = tempfile.mktemp()
self.__run_level = run_level
+
def __initiate_values(self):
"""
Initiate all values.
@@ -67,6 +70,7 @@ class Fonts:
self.__font_table = {}
# individual font written
self.__wrote_ind_font = 0
+
def __default_func(self, line):
"""
Requires:
@@ -79,6 +83,7 @@ class Fonts:
if self.__token_info == 'miTimes0\n' )
+ 'Times0\n')
+
def __after_font_table_func(self, line):
"""
Required:
@@ -169,7 +177,7 @@ class Fonts:
if self.__token_info == 'cw 3:
msg = 'no value for %s in self.__font_table\n' % font_num
raise self.__bug_handler, msg
@@ -182,6 +190,7 @@ class Fonts:
)
else:
self.__write_obj.write(line)
+
def convert_fonts(self):
"""
Required:
@@ -197,20 +206,15 @@ class Fonts:
info. Substitute a font name for a font number.
"""
self.__initiate_values()
- read_obj = open(self.__file, 'r')
- self.__write_obj = open(self.__write_to, 'w')
- line_to_read = 1
- while line_to_read:
- line_to_read = read_obj.readline()
- line = line_to_read
- self.__token_info = line[:16]
- action = self.__state_dict.get(self.__state)
- if action == None:
- sys.stderr.write('no no matching state in module fonts.py\n')
- sys.stderr.write(self.__state + '\n')
- action(line)
- read_obj.close()
- self.__write_obj.close()
+ with open(self.__file, 'r') as read_obj:
+ with open(self.__write_to, 'w') as self.__write_obj:
+ for line in read_obj:
+ self.__token_info = line[:16]
+ action = self.__state_dict.get(self.__state)
+ if action is None:
+ sys.stderr.write('no matching state in module fonts.py\n' \
+ + self.__state + '\n')
+ action(line)
default_font_name = self.__font_table.get(self.__default_font_num)
if not default_font_name:
default_font_name = 'Not Defined'
diff --git a/src/calibre/ebooks/rtf2xml/get_char_map.py b/src/calibre/ebooks/rtf2xml/get_char_map.py
index fb3ef28b4f..5944d1920d 100755
--- a/src/calibre/ebooks/rtf2xml/get_char_map.py
+++ b/src/calibre/ebooks/rtf2xml/get_char_map.py
@@ -43,7 +43,7 @@ class GetCharMap:
def get_char_map(self, map):
if map == 'ansicpg0':
map = 'ansicpg1250'
- if map in ('ansicpg10000', '10000'):
+ if map == 'ansicpg10000':
map = 'mac_roman'
found_map = False
map_dict = {}
diff --git a/src/calibre/ebooks/rtf2xml/tokenize.py b/src/calibre/ebooks/rtf2xml/tokenize.py
index 20438a2e66..59c2cab082 100755
--- a/src/calibre/ebooks/rtf2xml/tokenize.py
+++ b/src/calibre/ebooks/rtf2xml/tokenize.py
@@ -126,12 +126,6 @@ class Tokenize:
tokens = re.split(self.__splitexp, input_file)
#remove empty tokens and \n
return filter(lambda x: len(x) > 0 and x != '\n', tokens)
- #input_file = re.sub(self.__utf_exp, self.__from_ms_to_utf8, input_file)
- # line = re.sub( self.__neg_utf_exp, self.__neg_unicode_func, line)
- # this is for older RTF
- #line = re.sub(self.__par_exp, '\\par ', line)
- #return filter(lambda x: len(x) > 0, \
- #(self.__remove_line.sub('', x) for x in tokens))
def __compile_expressions(self):
SIMPLE_RPL = {
@@ -160,7 +154,7 @@ class Tokenize:
}
self.__replace_spchar = MReplace(SIMPLE_RPL)
#add ;? in case of char following \u
- self.__ms_hex_exp = re.compile(r"\\\'([0-9a-fA-F]{2})") #r"\\\'(..)"
+ self.__ms_hex_exp = re.compile(r"\\\'([0-9a-fA-F]{2})")
self.__utf_exp = re.compile(r"\\u(-?\d{3,6}) ?")
self.__bin_exp = re.compile(r"(?:\\bin(-?\d{0,10})[\n ]+)[01\n]+")
#manage upr/ud situations
@@ -172,14 +166,21 @@ class Tokenize:
self.__splitexp = re.compile(r"(\\[{}]|\n|\\[^\s\\{}&]+(?:[ \t\r\f\v])?)")
#this is for old RTF
self.__par_exp = re.compile(r'\\\n+')
- # self.__par_exp = re.compile(r'\\$')
+ #handle cw using a digit as argument and without space as delimiter
+ self.__cwdigit_exp = re.compile(r"(\\[a-zA-Z]+[\-0-9]+)([^0-9 \\]+)")
#self.__bin_exp = re.compile(r"\\bin(-?\d{1,8}) {0,1}")
#self.__utf_exp = re.compile(r"^\\u(-?\d{3,6})")
#self.__splitexp = re.compile(r"(\\[\\{}]|{|}|\n|\\[^\s\\{}&]+(?:\s)?)")
#self.__remove_line = re.compile(r'\n+')
- #self.__mixed_exp = re.compile(r"(\\[a-zA-Z]+\d+)(\D+)")
##self.num_exp = re.compile(r"(\*|:|[a-zA-Z]+)(.*)")
+ def __correct_spliting(self, token):
+ match_obj = re.search(self.__cwdigit_exp, token)
+ if match_obj is None:
+ return token
+ else:
+ return '%s\n%s' % (match_obj.group(1), match_obj.group(2))
+
def tokenize(self):
"""Main class for handling other methods. Reads the file \
, uses method self.sub_reg to make basic substitutions,\
@@ -187,7 +188,7 @@ class Tokenize:
#read
with open(self.__file, 'r') as read_obj:
input_file = read_obj.read()
-
+
#process simple replacements and split giving us a correct list
#remove '' and \n in the process
tokens = self.__sub_reg_split(input_file)
@@ -195,7 +196,9 @@ class Tokenize:
tokens = map(self.__unicode_process, tokens)
#remove empty items created by removing \uc
tokens = filter(lambda x: len(x) > 0, tokens)
-
+ #handles bothersome cases
+ tokens = map(self.__correct_spliting, tokens)
+
#write
with open(self.__write_to, 'wb') as write_obj:
write_obj.write('\n'.join(tokens))
@@ -203,11 +206,9 @@ class Tokenize:
copy_obj = copy.Copy(bug_handler = self.__bug_handler)
if self.__copy:
copy_obj.copy_file(self.__write_to, "tokenize.data")
- # if self.__out_file:
- # self.__file = self.__out_file
copy_obj.rename(self.__write_to, self.__file)
os.remove(self.__write_to)
-
+
#self.__special_tokens = [ '_', '~', "'", '{', '}' ]
# import sys
@@ -223,4 +224,4 @@ class Tokenize:
# if __name__ == '__main__':
- # sys.exit(main())
\ No newline at end of file
+ # sys.exit(main())
diff --git a/src/calibre/ebooks/txt/txtml.py b/src/calibre/ebooks/txt/txtml.py
index 00992a8612..c2ee3f37c5 100644
--- a/src/calibre/ebooks/txt/txtml.py
+++ b/src/calibre/ebooks/txt/txtml.py
@@ -67,10 +67,11 @@ class TXTMLizer(object):
output.append(self.get_toc())
for item in self.oeb_book.spine:
self.log.debug('Converting %s to TXT...' % item.href)
- stylizer = Stylizer(item.data, item.href, self.oeb_book, self.opts, self.opts.output_profile)
- content = unicode(etree.tostring(item.data.find(XHTML('body')), encoding=unicode))
+ content = unicode(etree.tostring(item.data, encoding=unicode))
content = self.remove_newlines(content)
- output += self.dump_text(etree.fromstring(content), stylizer, item)
+ content = etree.fromstring(content)
+ stylizer = Stylizer(content, item.href, self.oeb_book, self.opts, self.opts.output_profile)
+ output += self.dump_text(content.find(XHTML('body')), stylizer, item)
output += '\n\n\n\n\n\n'
output = u''.join(output)
output = u'\n'.join(l.rstrip() for l in output.splitlines())
@@ -218,10 +219,17 @@ class TXTMLizer(object):
if tag in SPACE_TAGS:
text.append(u' ')
-
- # Scene breaks.
+
+ # Hard scene breaks.
if tag == 'hr':
text.append('\n\n* * *\n\n')
+ # Soft scene breaks.
+ try:
+ ems = int(round((float(style.marginTop) / style.fontSize) - 1))
+ if ems >= 1:
+ text.append('\n' * ems)
+ except:
+ pass
# Process tags that contain text.
if hasattr(elem, 'text') and elem.text:
diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py
index fb3e627789..b32568f8fd 100644
--- a/src/calibre/gui2/actions/device.py
+++ b/src/calibre/gui2/actions/device.py
@@ -74,23 +74,29 @@ class ShareConnMenu(QMenu): # {{{
opts = email_config().parse()
if opts.accounts:
self.email_to_menu = QMenu(_('Email to')+'...', self)
+ ac = self.addMenu(self.email_to_menu)
+ self.email_actions.append(ac)
+ self.email_to_and_delete_menu = QMenu(
+ _('Email to and delete from library')+'...', self)
keys = sorted(opts.accounts.keys())
for account in keys:
formats, auto, default = opts.accounts[account]
dest = 'mail:'+account+';'+formats
action1 = DeviceAction(dest, False, False, I('mail.png'),
- _('Email to')+' '+account)
+ account)
action2 = DeviceAction(dest, True, False, I('mail.png'),
- _('Email to')+' '+account+ _(' and delete from library'))
- map(self.email_to_menu.addAction, (action1, action2))
+ account + ' ' + _('(delete from library)'))
+ self.email_to_menu.addAction(action1)
+ self.email_to_and_delete_menu.addAction(action2)
map(self.memory.append, (action1, action2))
if default:
- map(self.addAction, (action1, action2))
- map(self.email_actions.append, (action1, action2))
- self.email_to_menu.addSeparator()
+ ac = DeviceAction(dest, False, False,
+ I('mail.png'), _('Email to') + ' ' +account)
+ self.addAction(ac)
+ self.email_actions.append(ac)
action1.a_s.connect(sync_menu.action_triggered)
action2.a_s.connect(sync_menu.action_triggered)
- ac = self.addMenu(self.email_to_menu)
+ ac = self.addMenu(self.email_to_and_delete_menu)
self.email_actions.append(ac)
else:
ac = self.addAction(_('Setup email based sharing of books'))
diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py
index f50251e700..6c2cfb8126 100644
--- a/src/calibre/gui2/actions/edit_metadata.py
+++ b/src/calibre/gui2/actions/edit_metadata.py
@@ -160,6 +160,7 @@ class EditMetadataAction(InterfaceAction):
break
changed.add(d.id)
+ self.gui.library_view.model().refresh_ids(list(d.books_to_refresh))
if d.row_delta == 0:
break
current_row += d.row_delta
diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py
new file mode 100644
index 0000000000..f589b30679
--- /dev/null
+++ b/src/calibre/gui2/complete.py
@@ -0,0 +1,359 @@
+#!/usr/bin/env python
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+
+__license__ = 'GPL v3'
+__copyright__ = '2011, Kovid Goyal '
+__docformat__ = 'restructuredtext en'
+
+
+from PyQt4.Qt import QLineEdit, QListView, QAbstractListModel, Qt, QTimer, \
+ QApplication, QPoint, QItemDelegate, QStyleOptionViewItem, \
+ QStyle, QEvent, pyqtSignal
+
+from calibre.utils.icu import sort_key, lower
+from calibre.gui2 import NONE
+from calibre.gui2.widgets import EnComboBox
+
+class CompleterItemDelegate(QItemDelegate): # {{{
+
+ ''' Renders the current item as thought it were selected '''
+
+ def __init__(self, view):
+ self.view = view
+ QItemDelegate.__init__(self, view)
+
+ def paint(self, p, opt, idx):
+ opt = QStyleOptionViewItem(opt)
+ opt.showDecorationSelected = True
+ if self.view.currentIndex() == idx:
+ opt.state |= QStyle.State_HasFocus
+ QItemDelegate.paint(self, p, opt, idx)
+
+# }}}
+
+class CompleteWindow(QListView): # {{{
+
+ '''
+ The completion popup. For keyboard and mouse handling see
+ :meth:`eventFilter`.
+ '''
+
+ #: This signal is emitted when the user selects one of the listed
+ #: completions, by mouse or keyboard
+ completion_selected = pyqtSignal(object)
+
+ def __init__(self, widget, model):
+ self.widget = widget
+ QListView.__init__(self)
+ self.setVisible(False)
+ self.setParent(None, Qt.Popup)
+ self.setAlternatingRowColors(True)
+ self.setFocusPolicy(Qt.NoFocus)
+ self._d = CompleterItemDelegate(self)
+ self.setItemDelegate(self._d)
+ self.setModel(model)
+ self.setFocusProxy(widget)
+ self.installEventFilter(self)
+ self.clicked.connect(self.do_selected)
+ self.entered.connect(self.do_entered)
+ self.setMouseTracking(True)
+
+ def do_entered(self, idx):
+ if idx.isValid():
+ self.setCurrentIndex(idx)
+
+ def do_selected(self, idx=None):
+ idx = self.currentIndex() if idx is None else idx
+ if idx.isValid():
+ data = unicode(self.model().data(idx, Qt.DisplayRole))
+ self.completion_selected.emit(data)
+ self.hide()
+
+ def eventFilter(self, o, e):
+ if o is not self:
+ return False
+ if e.type() == e.KeyPress:
+ key = e.key()
+ if key in (Qt.Key_Escape, Qt.Key_Backtab) or \
+ (key == Qt.Key_F4 and (e.modifiers() & Qt.AltModifier)):
+ self.hide()
+ return True
+ elif key in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab):
+ if key == Qt.Key_Tab and not self.currentIndex().isValid():
+ if self.model().rowCount() > 0:
+ self.setCurrentIndex(self.model().index(0))
+ self.do_selected()
+ return True
+ elif key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp,
+ Qt.Key_PageDown):
+ return False
+ # Send key event to associated line edit
+ self.widget.eat_focus_out = False
+ try:
+ self.widget.event(e)
+ finally:
+ self.widget.eat_focus_out = True
+ if not self.widget.hasFocus():
+ # Line edit lost focus
+ self.hide()
+ if e.isAccepted():
+ # Line edit consumed event
+ return True
+ elif e.type() == e.MouseButtonPress:
+ # Hide popup if user clicks outside it, otherwise
+ # pass event to popup
+ if not self.underMouse():
+ self.hide()
+ return True
+ elif e.type() in (e.InputMethod, e.ShortcutOverride):
+ QApplication.sendEvent(self.widget, e)
+
+ return False # Do not filter this event
+
+# }}}
+
+class CompleteModel(QAbstractListModel):
+
+ def __init__(self, parent=None):
+ QAbstractListModel.__init__(self, parent)
+ self.sep = ','
+ self.space_before_sep = False
+ self.items = []
+ self.lowered_items = []
+ self.matches = []
+
+ def set_items(self, items):
+ items = [unicode(x.strip()) for x in items]
+ self.items = list(sorted(items, key=lambda x: sort_key(x)))
+ self.lowered_items = [lower(x) for x in self.items]
+ self.matches = []
+ self.reset()
+
+ def rowCount(self, *args):
+ return len(self.matches)
+
+ def data(self, index, role):
+ if role == Qt.DisplayRole:
+ r = index.row()
+ try:
+ return self.matches[r]
+ except IndexError:
+ pass
+ return NONE
+
+ def get_matches(self, prefix):
+ '''
+ Return all matches that (case insensitively) start with prefix
+ '''
+ prefix = lower(prefix)
+ ans = []
+ if prefix:
+ for i, test in enumerate(self.lowered_items):
+ if test.startswith(prefix):
+ ans.append(self.items[i])
+ return ans
+
+ def update_matches(self, matches):
+ self.matches = matches
+ self.reset()
+
+class MultiCompleteLineEdit(QLineEdit):
+ '''
+ A line edit that completes on multiple items separated by a
+ separator. Use the :meth:`update_items_cache` to set the list of
+ all possible completions. Separator can be controlled with the
+ :meth:`set_separator` and :meth:`set_space_before_sep` methods.
+
+ A call to self.set_separator(None) will allow this widget to be used
+ to complete non multiple fields as well.
+ '''
+
+ def __init__(self, parent=None):
+ self.eat_focus_out = True
+ self.max_visible_items = 7
+ self.current_prefix = None
+ QLineEdit.__init__(self, parent)
+
+ self._model = CompleteModel(parent=self)
+ self.complete_window = CompleteWindow(self, self._model)
+ self.textEdited.connect(self.text_edited)
+ self.complete_window.completion_selected.connect(self.completion_selected)
+ self.installEventFilter(self)
+
+ # Interface {{{
+ def update_items_cache(self, complete_items):
+ self.all_items = complete_items
+
+ def set_separator(self, sep):
+ self.sep = sep
+
+ def set_space_before_sep(self, space_before):
+ self.space_before_sep = space_before
+
+ # }}}
+
+ def eventFilter(self, o, e):
+ if self.eat_focus_out and o is self and e.type() == QEvent.FocusOut:
+ if self.complete_window.isVisible():
+ return True # Filter this event since the cw is visible
+ return QLineEdit.eventFilter(self, o, e)
+
+ def hide_completion_window(self):
+ self.complete_window.hide()
+
+
+ def text_edited(self, *args):
+ self.update_completions()
+
+ def update_completions(self):
+ ' Update the list of completions '
+ if not self.complete_window.isVisible() and not self.hasFocus():
+ return
+ cpos = self.cursorPosition()
+ text = unicode(self.text())
+ prefix = text[:cpos]
+ self.current_prefix = prefix
+ complete_prefix = prefix.lstrip()
+ if self.sep:
+ complete_prefix = prefix = prefix.split(self.sep)[-1].lstrip()
+
+ matches = self._model.get_matches(complete_prefix)
+ self.update_complete_window(matches)
+
+ def get_completed_text(self, text):
+ '''
+ Get completed text from current cursor position and the completion
+ text
+ '''
+ if self.sep is None:
+ return -1, text
+ else:
+ cursor_pos = self.cursorPosition()
+ before_text = unicode(self.text())[:cursor_pos]
+ after_text = unicode(self.text())[cursor_pos:]
+ after_parts = after_text.split(self.sep)
+ if len(after_parts) < 3 and not after_parts[-1].strip():
+ after_text = u''
+ prefix_len = len(before_text.split(self.sep)[-1].lstrip())
+ return prefix_len, \
+ before_text[:cursor_pos - prefix_len] + text + after_text
+
+ def completion_selected(self, text):
+ prefix_len, ctext = self.get_completed_text(text)
+ if self.sep is None:
+ self.setText(ctext)
+ self.setCursorPosition(len(ctext))
+ else:
+ cursor_pos = self.cursorPosition()
+ self.setText(ctext)
+ self.setCursorPosition(cursor_pos - prefix_len + len(text))
+
+ def update_complete_window(self, matches):
+ self._model.update_matches(matches)
+ if matches:
+ self.show_complete_window()
+ else:
+ self.complete_window.hide()
+
+
+ def position_complete_window(self):
+ popup = self.complete_window
+ screen = QApplication.desktop().availableGeometry(self)
+ h = (popup.sizeHintForRow(0) * min(self.max_visible_items,
+ popup.model().rowCount()) + 3) + 3
+ hsb = popup.horizontalScrollBar()
+ if hsb and hsb.isVisible():
+ h += hsb.sizeHint().height()
+
+ rh = self.height()
+ pos = self.mapToGlobal(QPoint(0, self.height() - 2))
+ w = self.width()
+
+ if w > screen.width():
+ w = screen.width()
+ if (pos.x() + w) > (screen.x() + screen.width()):
+ pos.setX(screen.x() + screen.width() - w)
+ if (pos.x() < screen.x()):
+ pos.setX(screen.x())
+
+ top = pos.y() - rh - screen.top() + 2
+ bottom = screen.bottom() - pos.y()
+ h = max(h, popup.minimumHeight())
+ if h > bottom:
+ h = min(max(top, bottom), h)
+ if top > bottom:
+ pos.setY(pos.y() - h - rh + 2)
+
+ popup.setGeometry(pos.x(), pos.y(), w, h)
+
+
+ def show_complete_window(self):
+ self.position_complete_window()
+ self.complete_window.show()
+
+ def moveEvent(self, ev):
+ ret = QLineEdit.moveEvent(self, ev)
+ QTimer.singleShot(0, self.position_complete_window)
+ return ret
+
+ def resizeEvent(self, ev):
+ ret = QLineEdit.resizeEvent(self, ev)
+ QTimer.singleShot(0, self.position_complete_window)
+ return ret
+
+
+ @dynamic_property
+ def all_items(self):
+ def fget(self):
+ return self._model.items
+ def fset(self, items):
+ self._model.set_items(items)
+ return property(fget=fget, fset=fset)
+
+ @dynamic_property
+ def sep(self):
+ def fget(self):
+ return self._model.sep
+ def fset(self, val):
+ self._model.sep = val
+ return property(fget=fget, fset=fset)
+
+ @dynamic_property
+ def space_before_sep(self):
+ def fget(self):
+ return self._model.space_before_sep
+ def fset(self, val):
+ self._model.space_before_sep = val
+ return property(fget=fget, fset=fset)
+
+class MultiCompleteComboBox(EnComboBox):
+
+ def __init__(self, *args):
+ EnComboBox.__init__(self, *args)
+ self.setLineEdit(MultiCompleteLineEdit(self))
+ # Needed to allow changing the case of an existing item
+ # otherwise on focus out, the text is changed to the
+ # item that matches case insensitively
+ c = self.lineEdit().completer()
+ c.setCaseSensitivity(Qt.CaseSensitive)
+
+ def update_items_cache(self, complete_items):
+ self.lineEdit().update_items_cache(complete_items)
+
+ def set_separator(self, sep):
+ self.lineEdit().set_separator(sep)
+
+ def set_space_before_sep(self, space_before):
+ self.lineEdit().set_space_before_sep(space_before)
+
+
+
+if __name__ == '__main__':
+ from PyQt4.Qt import QDialog, QVBoxLayout
+ app = QApplication([])
+ d = QDialog()
+ d.setLayout(QVBoxLayout())
+ le = MultiCompleteLineEdit(d)
+ d.layout().addWidget(le)
+ le.all_items = ['one', 'otwo', 'othree', 'ooone', 'ootwo', 'oothree']
+ d.exec_()
diff --git a/src/calibre/gui2/convert/comic_input.py b/src/calibre/gui2/convert/comic_input.py
index fe86f133d1..f7f8023c0e 100644
--- a/src/calibre/gui2/convert/comic_input.py
+++ b/src/calibre/gui2/convert/comic_input.py
@@ -22,7 +22,7 @@ class PluginWidget(Widget, Ui_Form):
['colors', 'dont_normalize', 'keep_aspect_ratio', 'right2left',
'despeckle', 'no_sort', 'no_process', 'landscape',
'dont_sharpen', 'disable_trim', 'wide', 'output_format',
- 'dont_grayscale']
+ 'dont_grayscale', 'comic_image_size']
)
self.db, self.book_id = db, book_id
for x in get_option('output_format').option.choices:
diff --git a/src/calibre/gui2/convert/comic_input.ui b/src/calibre/gui2/convert/comic_input.ui
index c3b363d7e9..52c0ad2bb5 100644
--- a/src/calibre/gui2/convert/comic_input.ui
+++ b/src/calibre/gui2/convert/comic_input.ui
@@ -7,7 +7,7 @@
0
0
599
- 345
+ 398
@@ -37,70 +37,70 @@
- -
+
-
Disable &normalize
- -
+
-
Keep &aspect ratio
- -
+
-
Disable &Sharpening
- -
+
-
Disable &Trimming
- -
+
-
&Wide
- -
+
-
&Landscape
- -
+
-
&Right to left
- -
+
-
Don't so&rt
- -
+
-
De&speckle
- -
+
-
Qt::Vertical
@@ -120,7 +120,7 @@
- -
+
-
&Output format:
@@ -130,7 +130,7 @@
- -
+
-
-
@@ -140,6 +140,19 @@
+ -
+
+
+ Override image &size:
+
+
+ opt_comic_image_size
+
+
+
+ -
+
+
diff --git a/src/calibre/gui2/convert/heuristics.py b/src/calibre/gui2/convert/heuristics.py
index e788888257..5e7e4aa506 100644
--- a/src/calibre/gui2/convert/heuristics.py
+++ b/src/calibre/gui2/convert/heuristics.py
@@ -6,6 +6,7 @@ __docformat__ = 'restructuredtext en'
from PyQt4.Qt import Qt
+from calibre.gui2 import gprefs
from calibre.gui2.convert.heuristics_ui import Ui_Form
from calibre.gui2.convert import Widget
@@ -21,17 +22,38 @@ class HeuristicsWidget(Widget, Ui_Form):
['enable_heuristics', 'markup_chapter_headings',
'italicize_common_cases', 'fix_indents',
'html_unwrap_factor', 'unwrap_lines',
- 'delete_blank_paragraphs', 'format_scene_breaks',
+ 'delete_blank_paragraphs',
+ 'format_scene_breaks', 'replace_scene_breaks',
'dehyphenate', 'renumber_headings']
)
self.db, self.book_id = db, book_id
+ self.rssb_defaults = [u'', u'
', u'∗ ∗ ∗', u'• • •', u'♦ ♦ ♦',
+ u'† †', u'‡ ‡ ‡', u'∞ ∞ ∞', u'¤ ¤ ¤', u'§']
self.initialize_options(get_option, get_help, db, book_id)
+ self.load_histories()
+
self.opt_enable_heuristics.stateChanged.connect(self.enable_heuristics)
self.opt_unwrap_lines.stateChanged.connect(self.enable_unwrap)
self.enable_heuristics(self.opt_enable_heuristics.checkState())
+ def restore_defaults(self, get_option):
+ Widget.restore_defaults(self, get_option)
+
+ self.save_histories()
+ rssb_hist = gprefs['replace_scene_breaks_history']
+ for x in self.rssb_defaults:
+ if x in rssb_hist:
+ del rssb_hist[rssb_hist.index(x)]
+ gprefs['replace_scene_breaks_history'] = self.rssb_defaults + gprefs['replace_scene_breaks_history']
+ self.load_histories()
+
+ def commit_options(self, save_defaults=False):
+ self.save_histories()
+
+ return Widget.commit_options(self, save_defaults)
+
def break_cycles(self):
Widget.break_cycles(self)
@@ -45,6 +67,33 @@ class HeuristicsWidget(Widget, Ui_Form):
if val is None and g is self.opt_html_unwrap_factor:
g.setValue(0.0)
return True
+ if not val and g is self.opt_replace_scene_breaks:
+ g.lineEdit().setText('')
+ return True
+
+ def load_histories(self):
+ self.opt_replace_scene_breaks.clear()
+ self.opt_replace_scene_breaks.lineEdit().setText('')
+
+ val = unicode(self.opt_replace_scene_breaks.currentText())
+ rssb_hist = gprefs.get('replace_scene_breaks_history', self.rssb_defaults)
+ if val in rssb_hist:
+ del rssb_hist[rssb_hist.index(val)]
+ rssb_hist.insert(0, val)
+ for v in rssb_hist:
+ # Ensure we don't have duplicate items.
+ if self.opt_replace_scene_breaks.findText(v) == -1:
+ self.opt_replace_scene_breaks.addItem(v)
+ self.opt_replace_scene_breaks.setCurrentIndex(0)
+
+ def save_histories(self):
+ rssb_history = []
+ history_pats = [unicode(self.opt_replace_scene_breaks.lineEdit().text())] + [unicode(self.opt_replace_scene_breaks.itemText(i)) for i in xrange(self.opt_replace_scene_breaks.count())]
+ for p in history_pats[:10]:
+ # Ensure we don't have duplicate items.
+ if p not in rssb_history:
+ rssb_history.append(p)
+ gprefs['replace_scene_breaks_history'] = rssb_history
def enable_heuristics(self, state):
state = state == Qt.Checked
diff --git a/src/calibre/gui2/convert/heuristics.ui b/src/calibre/gui2/convert/heuristics.ui
index 6863fcf8e6..46d62061af 100644
--- a/src/calibre/gui2/convert/heuristics.ui
+++ b/src/calibre/gui2/convert/heuristics.ui
@@ -150,6 +150,45 @@
+ -
+
+
+ QLayout::SetDefaultConstraint
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Replace soft scene &breaks:
+
+
+ opt_replace_scene_breaks
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ true
+
+
+ QComboBox::InsertAtTop
+
+
+
+
+
-
diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py
index 23cac74cf8..81274f25a8 100644
--- a/src/calibre/gui2/convert/metadata.py
+++ b/src/calibre/gui2/convert/metadata.py
@@ -70,9 +70,6 @@ class MetadataWidget(Widget, Ui_Form):
def initialize_metadata_options(self):
self.initialize_combos()
self.author.editTextChanged.connect(self.deduce_author_sort)
- self.author.set_separator('&')
- self.author.set_space_before_sep(True)
- self.author.update_items_cache(self.db.all_author_names())
mi = self.db.get_metadata(self.book_id, index_is_id=True)
self.title.setText(mi.title)
@@ -109,6 +106,9 @@ class MetadataWidget(Widget, Ui_Form):
def initalize_authors(self):
all_authors = self.db.all_authors()
all_authors.sort(key=lambda x : sort_key(x[1]))
+ self.author.set_separator('&')
+ self.author.set_space_before_sep(True)
+ self.author.update_items_cache(self.db.all_author_names())
for i in all_authors:
id, name = i
@@ -124,6 +124,8 @@ class MetadataWidget(Widget, Ui_Form):
def initialize_series(self):
all_series = self.db.all_series()
all_series.sort(key=lambda x : sort_key(x[1]))
+ self.series.set_separator(None)
+ self.series.update_items_cache([x[1] for x in all_series])
for i in all_series:
id, name = i
@@ -133,6 +135,8 @@ class MetadataWidget(Widget, Ui_Form):
def initialize_publisher(self):
all_publishers = self.db.all_publishers()
all_publishers.sort(key=lambda x : sort_key(x[1]))
+ self.publisher.set_separator(None)
+ self.publisher.update_items_cache([x[1] for x in all_publishers])
for i in all_publishers:
id, name = i
diff --git a/src/calibre/gui2/convert/metadata.ui b/src/calibre/gui2/convert/metadata.ui
index 61c27594c4..95ccac6890 100644
--- a/src/calibre/gui2/convert/metadata.ui
+++ b/src/calibre/gui2/convert/metadata.ui
@@ -190,7 +190,7 @@
-
-
+
Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.
@@ -213,7 +213,7 @@
-
-
+
10
@@ -248,14 +248,14 @@
-
-
+
true
-
-
+
true
@@ -277,19 +277,14 @@
- EnComboBox
+ MultiCompleteComboBox
QComboBox
-
+
- CompleteComboBox
- QComboBox
-
-
-
- CompleteLineEdit
+ MultiCompleteLineEdit
QLineEdit
-
+
ImageView
diff --git a/src/calibre/gui2/convert/structure_detection.ui b/src/calibre/gui2/convert/structure_detection.ui
index ef0677a67c..f80e6f8182 100644
--- a/src/calibre/gui2/convert/structure_detection.ui
+++ b/src/calibre/gui2/convert/structure_detection.ui
@@ -48,10 +48,10 @@
- -
+
-
- -
+
-
Qt::Vertical
@@ -77,6 +77,16 @@
+ -
+
+
+ The header and footer removal options have been replaced by the Search & Replace options. Click the Search & Replace category in the bar to the left to use these options. Leave the replace field blank and enter your header/footer removal regexps into the search field.
+
+
+ true
+
+
+
diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py
index 360a5bcd0a..eae6dc79c3 100644
--- a/src/calibre/gui2/custom_column_widgets.py
+++ b/src/calibre/gui2/custom_column_widgets.py
@@ -14,7 +14,7 @@ from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \
QPushButton
from calibre.utils.date import qt_to_dt, now
-from calibre.gui2.widgets import CompleteLineEdit, EnComboBox
+from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
from calibre.gui2.comments_editor import Editor as CommentsEditor
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
from calibre.utils.config import tweaks
@@ -44,8 +44,10 @@ class Base(object):
val = self.gui_val
val = self.normalize_ui_val(val)
if val != self.initial_val:
- self.db.set_custom(book_id, val, num=self.col_id, notify=notify,
- commit=False)
+ return self.db.set_custom(book_id, val, num=self.col_id,
+ notify=notify, commit=False, allow_case_change=True)
+ else:
+ return set()
def normalize_db_val(self, val):
return val
@@ -228,10 +230,12 @@ class Text(Base):
values = self.all_values = list(self.db.all_custom(num=self.col_id))
values.sort(key=sort_key)
if self.col_metadata['is_multiple']:
- w = CompleteLineEdit(parent, values)
+ w = MultiCompleteLineEdit(parent)
+ w.update_items_cache(values)
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
else:
- w = EnComboBox(parent)
+ w = MultiCompleteComboBox(parent)
+ w.set_separator(None)
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
w.setMinimumContentsLength(25)
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w]
@@ -240,9 +244,10 @@ class Text(Base):
val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
self.initial_val = val
val = self.normalize_db_val(val)
+ self.widgets[1].update_items_cache(self.all_values)
+
if self.col_metadata['is_multiple']:
self.setter(val)
- self.widgets[1].update_items_cache(self.all_values)
else:
idx = None
for i, c in enumerate(self.all_values):
@@ -276,7 +281,7 @@ class Series(Base):
def setup_ui(self, parent):
values = self.all_values = list(self.db.all_custom(num=self.col_id))
values.sort(key=sort_key)
- w = EnComboBox(parent)
+ w = MultiCompleteComboBox(parent)
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
w.setMinimumContentsLength(25)
self.name_widget = w
@@ -305,6 +310,7 @@ class Series(Base):
if c == val:
idx = i
self.name_widget.addItem(c)
+ self.name_widget.update_items_cache(self.all_values)
self.name_widget.setEditText('')
if idx is not None:
self.widgets[1].setCurrentIndex(idx)
@@ -326,8 +332,10 @@ class Series(Base):
num=self.col_id)
else:
s_index = None
- self.db.set_custom(book_id, val, extra=s_index,
- num=self.col_id, notify=notify, commit=False)
+ return self.db.set_custom(book_id, val, extra=s_index, num=self.col_id,
+ notify=notify, commit=False, allow_case_change=True)
+ else:
+ return set()
class Enumeration(Base):
@@ -670,7 +678,7 @@ class BulkDateTime(BulkBase):
class BulkSeries(BulkBase):
def setup_ui(self, parent):
- self.make_widgets(parent, EnComboBox)
+ self.make_widgets(parent, MultiCompleteComboBox)
values = self.all_values = list(self.db.all_custom(num=self.col_id))
values.sort(key=sort_key)
self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon)
@@ -705,6 +713,8 @@ class BulkSeries(BulkBase):
def initialize(self, book_id):
self.idx_widget.setChecked(False)
+ self.main_widget.set_separator(None)
+ self.main_widget.update_items_cache(self.all_values)
for c in self.all_values:
self.main_widget.addItem(c)
self.main_widget.setEditText('')
@@ -795,7 +805,8 @@ class RemoveTags(QWidget):
layout.setSpacing(5)
layout.setContentsMargins(0, 0, 0, 0)
- self.tags_box = CompleteLineEdit(parent, values)
+ self.tags_box = MultiCompleteLineEdit(parent)
+ self.tags_box.update_items_cache(values)
layout.addWidget(self.tags_box, stretch=3)
self.checkbox = QCheckBox(_('Remove all tags'), parent)
layout.addWidget(self.checkbox)
@@ -816,7 +827,7 @@ class BulkText(BulkBase):
values = self.all_values = list(self.db.all_custom(num=self.col_id))
values.sort(key=sort_key)
if self.col_metadata['is_multiple']:
- self.make_widgets(parent, CompleteLineEdit,
+ self.make_widgets(parent, MultiCompleteLineEdit,
extra_label_text=_('tags to add'))
self.main_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
self.adding_widget = self.main_widget
@@ -829,16 +840,16 @@ class BulkText(BulkBase):
w.tags_box.textChanged.connect(self.a_c_checkbox_changed)
w.checkbox.stateChanged.connect(self.a_c_checkbox_changed)
else:
- self.make_widgets(parent, EnComboBox)
+ self.make_widgets(parent, MultiCompleteComboBox)
+ self.main_widget.set_separator(None)
self.main_widget.setSizeAdjustPolicy(
self.main_widget.AdjustToMinimumContentsLengthWithIcon)
self.main_widget.setMinimumContentsLength(25)
self.ignore_change_signals = False
def initialize(self, book_ids):
- if self.col_metadata['is_multiple']:
- self.main_widget.update_items_cache(self.all_values)
- else:
+ self.main_widget.update_items_cache(self.all_values)
+ if not self.col_metadata['is_multiple']:
val = self.get_initial_value(book_ids)
self.initial_val = val = self.normalize_db_val(val)
idx = None
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index 1cf0fa5d67..8efa7f154c 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -687,7 +687,7 @@ class DeviceMixin(object): # {{{
except:
pass
if not self.device_error_dialog.isVisible():
- self.device_error_dialog.setDetailedText(job.details)
+ self.device_error_dialog.set_details(job.details)
self.device_error_dialog.show()
# Device connected {{{
@@ -838,9 +838,10 @@ class DeviceMixin(object): # {{{
format_count[f] = 1
for f in self.device_manager.device.settings().format_map:
if f in format_count.keys():
- formats.append((f, _('%i of %i Books' % (format_count[f], len(rows))), True if f in aval_out_formats else False))
+ formats.append((f, _('%i of %i Books') % (format_count[f],
+ len(rows)), True if f in aval_out_formats else False))
elif f in aval_out_formats:
- formats.append((f, _('0 of %i Books' % len(rows)), True))
+ formats.append((f, _('0 of %i Books') % len(rows), True))
d = ChooseFormatDeviceDialog(self, _('Choose format to send to device'), formats)
if d.exec_() != QDialog.Accepted:
return
@@ -871,6 +872,16 @@ class DeviceMixin(object): # {{{
self.send_by_mail(to, fmts, delete)
def cover_to_thumbnail(self, data):
+ if self.device_manager.device and \
+ hasattr(self.device_manager.device, 'THUMBNAIL_WIDTH'):
+ try:
+ return thumbnail(data,
+ self.device_manager.device.THUMBNAIL_WIDTH,
+ self.device_manager.device.THUMBNAIL_HEIGHT,
+ preserve_aspect_ratio=False)
+ except:
+ pass
+ return
ht = self.device_manager.device.THUMBNAIL_HEIGHT \
if self.device_manager else DevicePlugin.THUMBNAIL_HEIGHT
try:
@@ -1272,6 +1283,8 @@ class DeviceMixin(object): # {{{
x = x.lower() if x else ''
return string_pat.sub('', x)
+ update_metadata = prefs['manage_device_metadata'] == 'on_connect'
+
# Force a reset if the caches are not initialized
if reset or not hasattr(self, 'db_book_title_cache'):
# Build a cache (map) of the library, so the search isn't On**2
@@ -1284,8 +1297,13 @@ class DeviceMixin(object): # {{{
except:
return False
+ get_covers = False
+ if update_metadata and self.device_manager.is_device_connected:
+ if self.device_manager.device.WANTS_UPDATED_THUMBNAILS:
+ get_covers = True
+
for id in db.data.iterallids():
- mi = db.get_metadata(id, index_is_id=True)
+ mi = db.get_metadata(id, index_is_id=True, get_cover=get_covers)
title = clean_string(mi.title)
if title not in db_book_title_cache:
db_book_title_cache[title] = \
@@ -1311,7 +1329,6 @@ class DeviceMixin(object): # {{{
# the application_id to the db_id of the matching book. This value
# will be used by books_on_device to indicate matches.
- update_metadata = prefs['manage_device_metadata'] == 'on_connect'
for booklist in booklists:
for book in booklist:
book.in_library = None
@@ -1382,6 +1399,12 @@ class DeviceMixin(object): # {{{
if update_metadata:
if self.device_manager.is_device_connected:
+ if self.device_manager.device.WANTS_UPDATED_THUMBNAILS:
+ for blist in booklists:
+ for book in blist:
+ if book.cover and os.access(book.cover, os.R_OK):
+ book.thumbnail = \
+ self.cover_to_thumbnail(open(book.cover, 'rb').read())
plugboards = self.library_view.model().db.prefs.get('plugboards', {})
self.device_manager.sync_booklists(
Dispatcher(self.metadata_synced), booklists,
diff --git a/src/calibre/gui2/dialogs/add_empty_book.py b/src/calibre/gui2/dialogs/add_empty_book.py
index b8339f95f5..9e5fb07308 100644
--- a/src/calibre/gui2/dialogs/add_empty_book.py
+++ b/src/calibre/gui2/dialogs/add_empty_book.py
@@ -7,8 +7,8 @@ __license__ = 'GPL v3'
from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \
QApplication, QSpinBox, QToolButton, QIcon
from calibre.ebooks.metadata import authors_to_string, string_to_authors
-from calibre.gui2.widgets import CompleteComboBox
from calibre.utils.icu import sort_key
+from calibre.gui2.complete import MultiCompleteComboBox
class AddEmptyBookDialog(QDialog):
@@ -32,7 +32,7 @@ class AddEmptyBookDialog(QDialog):
self.author_label = QLabel(_('Set the author of the new books to:'))
self._layout.addWidget(self.author_label, 2, 0, 1, 2)
- self.authors_combo = CompleteComboBox(self)
+ self.authors_combo = MultiCompleteComboBox(self)
self.authors_combo.setSizeAdjustPolicy(
self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
self.authors_combo.setEditable(True)
diff --git a/src/calibre/gui2/dialogs/check_library.py b/src/calibre/gui2/dialogs/check_library.py
index b6b15d8be8..f6688b0a9e 100644
--- a/src/calibre/gui2/dialogs/check_library.py
+++ b/src/calibre/gui2/dialogs/check_library.py
@@ -7,7 +7,7 @@ import os, shutil
from PyQt4.Qt import QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, \
QPushButton, QDialogButtonBox, QApplication, QTreeWidgetItem, \
- QLineEdit, Qt, QProgressBar, QSize, QTimer
+ QLineEdit, Qt, QProgressBar, QSize, QTimer, QIcon, QTextEdit
from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.library.check_library import CheckLibrary, CHECKS
@@ -16,7 +16,7 @@ from calibre import prints, as_unicode
from calibre.ptempfile import PersistentTemporaryFile
from calibre.library.sqlite import DBThread, OperationalError
-class DBCheck(QDialog):
+class DBCheck(QDialog): # {{{
def __init__(self, parent, db):
QDialog.__init__(self, parent)
@@ -134,7 +134,7 @@ class DBCheck(QDialog):
def reject(self):
self.rejected = True
QDialog.reject(self)
-
+# }}}
class Item(QTreeWidgetItem):
pass
@@ -146,9 +146,70 @@ class CheckLibraryDialog(QDialog):
self.db = db
self.setWindowTitle(_('Check Library -- Problems Found'))
+ self.setWindowIcon(QIcon(I('debug.png')))
- self._layout = QVBoxLayout(self)
- self.setLayout(self._layout)
+ self._tl = QHBoxLayout()
+ self._layout = QVBoxLayout()
+ self.setLayout(self._tl)
+ self._tl.addLayout(self._layout)
+ self.helpw = QTextEdit(self)
+ self._tl.addWidget(self.helpw)
+ self.helpw.setReadOnly(True)
+ self.helpw.setText(_('''\
+ Help
+
+ calibre stores the list of your books and their metadata in a
+ database. The actual book files and covers are stored as normal
+ files in the calibre library folder. The database contains a list of the files
+ and covers belonging to each book entry. This tool checks that the
+ actual files in the library folder on your computer match the
+ information in the database.
+
+ The result of each type of check is shown to the left. The various
+ checks are:
+
+
+ - Invalid titles: These are files and folders appearing
+ in the library where books titles should, but that do not have the
+ correct form to be a book title.
+ - Extra titles: These are extra files in your calibre
+ library that appear to be correctly-formed titles, but have no corresponding
+ entries in the database
+ - Invalid authors: These are files appearing
+ in the library where only author folders should be.
+ - Extra authors: These are folders in the
+ calibre library that appear to be authors but that do not have entries
+ in the database
+ - Missing book formats: These are book formats that are in
+ the database but have no corresponding format file in the book's folder.
+
- Extra book formats: These are book format files found in
+ the book's folder but not in the database.
+
- Unknown files in books: These are extra files in the
+ folder of each book that do not correspond to a known format or cover
+ file.
+ - Missing cover files: These represent books that are marked
+ in the database as having covers but the actual cover files are
+ missing.
+ - Cover files not in database: These are books that have
+ cover files but are marked as not having covers in the database.
+ - Folder raising exception: These represent folders in the
+ calibre library that could not be processed/understood by this
+ tool.
+
+
+ There are two kinds of automatic fixes possible: Delete
+ marked and Fix marked.
+ Delete marked is used to remove extra files/folders/covers that
+ have no entries in the database. Check the box next to the item you want
+ to delete. Use with caution.
+ Fix marked is applicable only to covers (the two lines marked
+ 'fixable'). In the case of missing cover files, checking the fixable
+ box and pushing this button will remove the cover mark from the
+ database for all the files in that category. In the case of extra
+ cover files, checking the fixable box and pushing this button will
+ add the cover mark to the database for all the files in that
+ category.
+ '''))
self.log = QTreeWidget(self)
self.log.itemChanged.connect(self.item_changed)
@@ -199,7 +260,7 @@ class CheckLibraryDialog(QDialog):
self._layout.addLayout(h)
self._layout.addWidget(self.bbox)
- self.resize(750, 500)
+ self.resize(950, 500)
self.bbox.setEnabled(True)
def do_exec(self):
@@ -347,5 +408,6 @@ class CheckLibraryDialog(QDialog):
if __name__ == '__main__':
app = QApplication([])
- d = CheckLibraryDialog()
+ from calibre.library import db
+ d = CheckLibraryDialog(None, db())
d.exec_()
diff --git a/src/calibre/gui2/dialogs/message_box.py b/src/calibre/gui2/dialogs/message_box.py
index 565fb147fc..9d586ce28d 100644
--- a/src/calibre/gui2/dialogs/message_box.py
+++ b/src/calibre/gui2/dialogs/message_box.py
@@ -45,14 +45,12 @@ class MessageBox(QDialog, Ui_Dialog):
self.ctc_button.clicked.connect(self.copy_to_clipboard)
- if det_msg:
- self.show_det_msg = _('Show &details')
- self.hide_det_msg = _('Hide &details')
- self.det_msg_toggle = self.bb.addButton(self.show_det_msg, self.bb.ActionRole)
- self.det_msg_toggle.clicked.connect(self.toggle_det_msg)
- self.det_msg_toggle.setToolTip(
- _('Show detailed information about this error'))
-
+ self.show_det_msg = _('Show &details')
+ self.hide_det_msg = _('Hide &details')
+ self.det_msg_toggle = self.bb.addButton(self.show_det_msg, self.bb.ActionRole)
+ self.det_msg_toggle.clicked.connect(self.toggle_det_msg)
+ self.det_msg_toggle.setToolTip(
+ _('Show detailed information about this error'))
self.copy_action = QAction(self)
self.addAction(self.copy_action)
@@ -66,10 +64,14 @@ class MessageBox(QDialog, Ui_Dialog):
else:
self.bb.button(self.bb.Ok).setDefault(True)
+ if not det_msg:
+ self.det_msg_toggle.setVisible(False)
+
self.do_resize()
+
def toggle_det_msg(self, *args):
- vis = self.det_msg.isVisible()
+ vis = unicode(self.det_msg_toggle.text()) == self.hide_det_msg
self.det_msg_toggle.setText(self.show_det_msg if vis else
self.hide_det_msg)
self.det_msg.setVisible(not vis)
@@ -100,6 +102,15 @@ class MessageBox(QDialog, Ui_Dialog):
self.bb.button(self.bb.Ok).setFocus(Qt.OtherFocusReason)
return ret
+ def set_details(self, msg):
+ if not msg:
+ msg = ''
+ self.det_msg.setPlainText(msg)
+ self.det_msg_toggle.setText(self.show_det_msg)
+ self.det_msg_toggle.setVisible(bool(msg))
+ self.det_msg.setVisible(False)
+ self.do_resize()
+
if __name__ == '__main__':
app = QApplication([])
from calibre.gui2 import question_dialog
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index 533a344de5..e355144544 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -11,7 +11,7 @@ from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
from calibre.gui2.dialogs.tag_editor import TagEditor
-from calibre.ebooks.metadata import string_to_authors, authors_to_string
+from calibre.ebooks.metadata import string_to_authors, authors_to_string, title_sort
from calibre.ebooks.metadata.book.base import composite_formatter
from calibre.ebooks.metadata.meta import get_metadata
from calibre.gui2.custom_column_widgets import populate_metadata_page
@@ -134,7 +134,7 @@ class MyBlockingBusy(QDialog): # {{{
do_autonumber, do_remove_format, remove_format, do_swap_ta, \
do_remove_conv, do_auto_author, series, do_series_restart, \
series_start_value, do_title_case, cover_action, clear_series, \
- pubdate, adddate = self.args
+ pubdate, adddate, do_title_sort = self.args
# first loop: do author and title. These will commit at the end of each
@@ -159,6 +159,9 @@ class MyBlockingBusy(QDialog): # {{{
if do_title_case and not title_set:
title = self.db.title(id, index_is_id=True)
self.db.set_title(id, titlecase(title), notify=False)
+ if do_title_sort:
+ title = self.db.title(id, index_is_id=True)
+ self.db.set_title_sort(id, title_sort(title), notify=False)
if au:
self.db.set_authors(id, string_to_authors(au), notify=False)
if cover_action == 'remove':
@@ -360,11 +363,11 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
if (f in ['author_sort'] or
(fm[f]['datatype'] in ['text', 'series', 'enumeration']
and fm[f].get('search_terms', None)
- and f not in ['formats', 'ondevice', 'sort']) or
+ and f not in ['formats', 'ondevice']) or
fm[f]['datatype'] in ['int', 'float', 'bool'] ):
self.all_fields.append(f)
self.writable_fields.append(f)
- if f in ['sort'] or fm[f]['datatype'] == 'composite':
+ if fm[f]['datatype'] == 'composite':
self.all_fields.append(f)
self.all_fields.sort()
self.all_fields.insert(1, '{template}')
@@ -437,7 +440,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.replace_func.addItems(sorted(self.s_r_functions.keys()))
self.search_mode.currentIndexChanged[int].connect(self.s_r_search_mode_changed)
self.search_field.currentIndexChanged[int].connect(self.s_r_search_field_changed)
- self.destination_field.currentIndexChanged[str].connect(self.s_r_destination_field_changed)
+ self.destination_field.currentIndexChanged[int].connect(self.s_r_destination_field_changed)
self.replace_mode.currentIndexChanged[int].connect(self.s_r_paint_results)
self.replace_func.currentIndexChanged[str].connect(self.s_r_paint_results)
@@ -469,6 +472,16 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.query_field.currentIndexChanged[str].connect(self.s_r_query_change)
self.query_field.setCurrentIndex(0)
+ def s_r_sf_itemdata(self, idx):
+ if idx is None:
+ idx = self.search_field.currentIndex()
+ return unicode(self.search_field.itemData(idx).toString())
+
+ def s_r_df_itemdata(self, idx):
+ if idx is None:
+ idx = self.destination_field.currentIndex()
+ return unicode(self.destination_field.itemData(idx).toString())
+
def s_r_get_field(self, mi, field):
if field:
if field == '{template}':
@@ -508,7 +521,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
for i in range(0, self.s_r_number_of_books):
w = getattr(self, 'book_%d_text'%(i+1))
mi = self.db.get_metadata(self.ids[i], index_is_id=True)
- src = unicode(self.search_field.currentText())
+ src = self.s_r_sf_itemdata(idx)
t = self.s_r_get_field(mi, src)
if len(t) > 1:
t = t[self.starting_from.value()-1:
@@ -518,13 +531,13 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
if self.search_mode.currentIndex() == 0:
self.destination_field.setCurrentIndex(idx)
else:
- self.s_r_destination_field_changed(self.destination_field.currentText())
+ self.s_r_destination_field_changed(self.destination_field.currentIndex())
self.s_r_paint_results(None)
- def s_r_destination_field_changed(self, txt):
- txt = unicode(txt)
+ def s_r_destination_field_changed(self, idx):
+ txt = self.s_r_df_itemdata(idx)
if not txt:
- txt = unicode(self.search_field.currentText())
+ txt = self.s_r_sf_itemdata(None)
if txt and txt in self.writable_fields:
self.destination_field_fm = self.db.metadata_for_field(txt)
self.s_r_paint_results(None)
@@ -533,8 +546,9 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.search_field.clear()
self.destination_field.clear()
if val == 0:
- self.search_field.addItems(self.writable_fields)
- self.destination_field.addItems(self.writable_fields)
+ for f in self.writable_fields:
+ self.search_field.addItem(f if f != 'sort' else 'title_sort', f)
+ self.destination_field.addItem(f if f != 'sort' else 'title_sort', f)
self.destination_field.setCurrentIndex(0)
self.destination_field.setVisible(False)
self.destination_field_label.setVisible(False)
@@ -544,8 +558,14 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.comma_separated.setVisible(False)
self.s_r_heading.setText(''+self.main_heading + self.character_heading)
else:
- self.search_field.addItems(self.all_fields)
- self.destination_field.addItems(self.writable_fields)
+ self.search_field.blockSignals(True)
+ self.destination_field.blockSignals(True)
+ for f in self.all_fields:
+ self.search_field.addItem(f if f != 'sort' else 'title_sort', f)
+ for f in self.writable_fields:
+ self.destination_field.addItem(f if f != 'sort' else 'title_sort', f)
+ self.search_field.blockSignals(False)
+ self.destination_field.blockSignals(False)
self.destination_field.setVisible(True)
self.destination_field_label.setVisible(True)
self.replace_mode.setVisible(True)
@@ -575,7 +595,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
return rfunc(rtext)
def s_r_do_regexp(self, mi):
- src_field = unicode(self.search_field.currentText())
+ src_field = self.s_r_sf_itemdata(None)
src = self.s_r_get_field(mi, src_field)
result = []
rfunc = self.s_r_functions[unicode(self.replace_func.currentText())]
@@ -587,10 +607,10 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
return result
def s_r_do_destination(self, mi, val):
- src = unicode(self.search_field.currentText())
+ src = self.s_r_sf_itemdata(None)
if src == '':
return ''
- dest = unicode(self.destination_field.currentText())
+ dest = self.s_r_df_itemdata(None)
if dest == '':
if self.db.metadata_for_field(src)['datatype'] == 'composite':
raise Exception(_('You must specify a destination when source is a composite field'))
@@ -680,10 +700,10 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
break
def do_search_replace(self, id):
- source = unicode(self.search_field.currentText())
+ source = self.s_r_sf_itemdata(None)
if not source or not self.s_r_obj:
return
- dest = unicode(self.destination_field.currentText())
+ dest = self.s_r_df_itemdata(None)
if not dest:
dest = source
dfm = self.db.field_metadata[dest]
@@ -717,6 +737,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
else:
if dest == 'comments':
setter = self.db.set_comment
+ elif dest == 'sort':
+ setter = self.db.set_title_sort
else:
setter = getattr(self.db, 'set_'+dest)
if dest in ['title', 'authors']:
@@ -764,6 +786,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
def initialize_series(self):
all_series = self.db.all_series()
all_series.sort(key=lambda x : sort_key(x[1]))
+ self.series.set_separator(None)
+ self.series.update_items_cache([x[1] for x in all_series])
for i in all_series:
id, name = i
@@ -773,6 +797,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
def initialize_publisher(self):
all_publishers = self.db.all_publishers()
all_publishers.sort(key=lambda x : sort_key(x[1]))
+ self.publisher.set_separator(None)
+ self.publisher.update_items_cache([x[1] for x in all_publishers])
for i in all_publishers:
id, name = i
@@ -840,6 +866,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
do_remove_conv = self.remove_conversion_settings.isChecked()
do_auto_author = self.auto_author_sort.isChecked()
do_title_case = self.change_title_to_title_case.isChecked()
+ do_title_sort = self.update_title_sort.isChecked()
pubdate = adddate = None
if self.apply_pubdate.isChecked():
pubdate = qt_to_dt(self.pubdate.date())
@@ -858,7 +885,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
do_autonumber, do_remove_format, remove_format, do_swap_ta,
do_remove_conv, do_auto_author, series, do_series_restart,
series_start_value, do_title_case, cover_action, clear_series,
- pubdate, adddate)
+ pubdate, adddate, do_title_sort)
bb = MyBlockingBusy(_('Applying changes to %d books.\nPhase {0} {1}%%.')
%len(self.ids), args, self.db, self.ids,
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui
index b0f2c144fc..2ab37bcbc6 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.ui
+++ b/src/calibre/gui2/dialogs/metadata_bulk.ui
@@ -76,7 +76,7 @@
-
-
+
true
@@ -175,7 +175,7 @@
-
-
+
true
@@ -195,7 +195,7 @@
-
-
+
Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.
@@ -229,7 +229,7 @@
-
-
+
Comma separated list of tags to remove from the books.
@@ -262,7 +262,7 @@
-
-
+
0
@@ -489,6 +489,16 @@ title and author are swapped before the title case is set
+ -
+
+
+ Update title sort based on the current title. This will be applied only after other changes to title.
+
+
+ Update &title sort
+
+
+
-
@@ -1072,19 +1082,14 @@ not multiple and the destination field is multiple
- EnComboBox
+ MultiCompleteComboBox
QComboBox
-
+
- CompleteComboBox
- QComboBox
-
-
-
- CompleteLineEdit
+ MultiCompleteLineEdit
QLineEdit
-
+
HistoryLineEdit
diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py
index 7a8e4ea8d0..aec8c4fd60 100644
--- a/src/calibre/gui2/dialogs/metadata_single.py
+++ b/src/calibre/gui2/dialogs/metadata_single.py
@@ -429,10 +429,12 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
old_extensions.add(ext)
for ext in new_extensions:
self.db.add_format(self.row, ext, open(paths[ext], 'rb'), notify=False)
- db_extensions = set([f.lower() for f in self.db.formats(self.row).split(',')])
+ dbfmts = self.db.formats(self.row)
+ db_extensions = set([f.lower() for f in (dbfmts.split(',') if dbfmts
+ else [])])
extensions = new_extensions.union(old_extensions)
for ext in db_extensions:
- if ext not in extensions:
+ if ext not in extensions and ext in self.original_formats:
self.db.remove_format(self.row, ext, notify=False)
def show_format(self, item, *args):
@@ -576,6 +578,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.orig_date = qt_to_dt(self.date.date())
exts = self.db.formats(row)
+ self.original_formats = []
if exts:
exts = exts.split(',')
for ext in exts:
@@ -586,6 +589,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
if size is None:
continue
Format(self.formats, ext, size, timestamp=timestamp)
+ self.original_formats.append(ext.lower())
self.initialize_combos()
@@ -618,6 +622,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.original_author = unicode(self.authors.text()).strip()
self.original_title = unicode(self.title.text()).strip()
+ self.books_to_refresh = set()
self.show()
@@ -735,6 +740,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.series.setSizeAdjustPolicy(self.series.AdjustToContentsOnFirstShow)
all_series = self.db.all_series()
all_series.sort(key=lambda x : sort_key(x[1]))
+ self.series.set_separator(None)
+ self.series.update_items_cache([x[1] for x in all_series])
series_id = self.db.series_id(self.row)
idx, c = None, 0
for i in all_series:
@@ -752,6 +759,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
def initialize_publisher(self):
all_publishers = self.db.all_publishers()
all_publishers.sort(key=lambda x : sort_key(x[1]))
+ self.publisher.set_separator(None)
+ self.publisher.update_items_cache([x[1] for x in all_publishers])
publisher_id = self.db.publisher_id(self.row)
idx, c = None, 0
for i in all_publishers:
@@ -771,7 +780,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
_('You have changed the tags. In order to use the tags'
' editor, you must either discard or apply these '
'changes. Apply changes?'), show_copy_button=False):
- self.apply_tags(commit=True, notify=True)
+ self.books_to_refresh |= self.apply_tags(commit=True, notify=True,
+ allow_case_change=True)
self.original_tags = unicode(self.tags.text())
else:
self.tags.setText(self.original_tags)
@@ -878,9 +888,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
break
def apply_tags(self, commit=False, notify=False):
- self.db.set_tags(self.id, [x.strip() for x in
- unicode(self.tags.text()).split(',')],
- notify=notify, commit=commit)
+ return self.db.set_tags(self.id, [x.strip() for x in
+ unicode(self.tags.text()).split(',')],
+ notify=notify, commit=commit, allow_case_change=True)
def next_triggered(self, row_delta, *args):
self.row_delta = row_delta
@@ -899,7 +909,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.db.set_title_sort(self.id, ts, notify=False, commit=False)
au = unicode(self.authors.text()).strip()
if au and au != self.original_author:
- self.db.set_authors(self.id, string_to_authors(au), notify=False)
+ self.books_to_refresh |= self.db.set_authors(self.id,
+ string_to_authors(au),
+ notify=False,
+ allow_case_change=True)
aus = unicode(self.author_sort.text()).strip()
if aus:
self.db.set_author_sort(self.id, aus, notify=False, commit=False)
@@ -909,13 +922,13 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
notify=False, commit=False)
self.db.set_rating(self.id, 2*self.rating.value(), notify=False,
commit=False)
- self.apply_tags()
- self.db.set_publisher(self.id,
- unicode(self.publisher.currentText()).strip(),
- notify=False, commit=False)
- self.db.set_series(self.id,
+ self.books_to_refresh |= self.apply_tags()
+ self.books_to_refresh |= self.db.set_publisher(self.id,
+ unicode(self.publisher.currentText()).strip(),
+ notify=False, commit=False, allow_case_change=True)
+ self.books_to_refresh |= self.db.set_series(self.id,
unicode(self.series.currentText()).strip(), notify=False,
- commit=False)
+ commit=False, allow_case_change=True)
self.db.set_series_index(self.id, self.series_index.value(),
notify=False, commit=False)
self.db.set_comment(self.id,
@@ -936,7 +949,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
else:
self.db.remove_cover(self.id)
for w in getattr(self, 'custom_column_widgets', []):
- w.commit(self.id)
+ self.books_to_refresh |= w.commit(self.id)
self.db.commit()
except IOError, err:
if err.errno == 13: # Permission denied
diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui
index 23efc45399..5bcf268aaa 100644
--- a/src/calibre/gui2/dialogs/metadata_single.ui
+++ b/src/calibre/gui2/dialogs/metadata_single.ui
@@ -240,7 +240,7 @@ Using this button to create author sort will change author sort from red to gree
-
-
+
true
@@ -313,7 +313,7 @@ If the box is colored green, then text matches the individual author's sort stri
-
-
+
true
@@ -335,7 +335,7 @@ If the box is colored green, then text matches the individual author's sort stri
-
-
-
+
Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.
@@ -379,7 +379,7 @@ If the box is colored green, then text matches the individual author's sort stri
5
-
-
+
List of known series. You can add new series.
@@ -837,19 +837,14 @@ If the box is colored green, then text matches the individual author's sort stri
- EnComboBox
- QComboBox
-
-
-
- CompleteLineEdit
+ MultiCompleteLineEdit
QLineEdit
-
+
- CompleteComboBox
+ MultiCompleteComboBox
QComboBox
-
+
FormatList
diff --git a/src/calibre/gui2/dialogs/restore_library.py b/src/calibre/gui2/dialogs/restore_library.py
index dd1befc11b..a57d6c86c1 100644
--- a/src/calibre/gui2/dialogs/restore_library.py
+++ b/src/calibre/gui2/dialogs/restore_library.py
@@ -21,7 +21,7 @@ class DBRestore(QDialog):
self.l = QVBoxLayout()
self.setLayout(self.l)
self.l1 = QLabel(''+_('Restoring database from backups, do not'
- ' interrupt, this will happen in two stages')+'...')
+ ' interrupt, this will happen in three stages')+'...')
self.setWindowTitle(_('Restoring database'))
self.l.addWidget(self.l1)
self.pb = QProgressBar(self)
@@ -104,7 +104,7 @@ def restore_database(db, parent=None):
else:
if r.errors_occurred:
warning_dialog(parent, _('Success'),
- _('Restoring the database succeeded with some warnings',
+ _('Restoring the database succeeded with some warnings'
' click Show details to see the details.'),
det_msg=r.report, show=True)
else:
diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py
index ab3fd3ec4e..9c91446f3c 100644
--- a/src/calibre/gui2/dialogs/search.py
+++ b/src/calibre/gui2/dialogs/search.py
@@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal '
import re, copy
-from PyQt4.Qt import QDialog, QDialogButtonBox, QCompleter, Qt
+from PyQt4.Qt import QDialog, QDialogButtonBox
from calibre.gui2.dialogs.search_ui import Ui_Dialog
from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH
@@ -29,20 +29,18 @@ class SearchDialog(QDialog, Ui_Dialog):
name = name.strip().replace('|', ',')
self.authors_box.addItem(name)
self.authors_box.setEditText('')
- self.authors_box.completer().setCompletionMode(QCompleter.PopupCompletion)
- self.authors_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
self.authors_box.set_separator('&')
self.authors_box.set_space_before_sep(True)
self.authors_box.update_items_cache(db.all_author_names())
all_series = db.all_series()
all_series.sort(key=lambda x : sort_key(x[1]))
+ self.series_box.set_separator(None)
+ self.series_box.update_items_cache([x[1] for x in all_series])
for i in all_series:
id, name = i
self.series_box.addItem(name)
self.series_box.setEditText('')
- self.series_box.completer().setCompletionMode(QCompleter.PopupCompletion)
- self.series_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
all_tags = db.all_tags()
self.tags_box.update_items_cache(all_tags)
diff --git a/src/calibre/gui2/dialogs/search.ui b/src/calibre/gui2/dialogs/search.ui
index 1d013a1e9f..eb6fffdb60 100644
--- a/src/calibre/gui2/dialogs/search.ui
+++ b/src/calibre/gui2/dialogs/search.ui
@@ -265,21 +265,21 @@
-
-
+
Enter an author's name. Only one author can be used.
-
-
+
Enter a series name, without an index. Only one series name can be used.
-
-
+
Enter tags separated by spaces
@@ -355,19 +355,14 @@
- EnComboBox
- QComboBox
-
-
-
- CompleteLineEdit
+ MultiCompleteLineEdit
QLineEdit
-
+
- CompleteComboBox
+ MultiCompleteComboBox
QComboBox
-
+
diff --git a/src/calibre/gui2/email.py b/src/calibre/gui2/email.py
index 6b2ed81413..426747e044 100644
--- a/src/calibre/gui2/email.py
+++ b/src/calibre/gui2/email.py
@@ -264,8 +264,9 @@ class EmailMixin(object): # {{{
if _auto_ids != []:
for id in _auto_ids:
if specific_format == None:
- formats = [f.lower() for f in self.library_view.model().db.formats(id, index_is_id=True).split(',')]
- formats = formats if formats != None else []
+ dbfmts = self.library_view.model().db.formats(id, index_is_id=True)
+ formats = [f.lower() for f in (dbfmts.split(',') if fmts else
+ [])]
if list(set(formats).intersection(available_input_formats())) != [] and list(set(fmts).intersection(available_output_formats())) != []:
auto.append(id)
else:
diff --git a/src/calibre/gui2/filename_pattern.ui b/src/calibre/gui2/filename_pattern.ui
index e2367c8ceb..68b3108e06 100644
--- a/src/calibre/gui2/filename_pattern.ui
+++ b/src/calibre/gui2/filename_pattern.ui
@@ -17,13 +17,10 @@
-
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
-<html><head><meta name="qrichtext" content="1" /><style type="text/css">
-p, li { white-space: pre-wrap; }
-</style></head><body style=" font-family:'Candara'; font-size:10pt; font-weight:400; font-style:normal;">
-<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Set a regular expression pattern to use when trying to guess ebook metadata from filenames. </p>
-<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A <a href="http://docs.python.org/lib/re-syntax.html"><span style=" text-decoration: underline; color:#0000ff;">reference</span></a> on the syntax of regular expressions is available.</p>
-<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Use the <span style=" font-weight:600;">Test</span> functionality below to test your regular expression on a few sample filenames. The group names for the various metadata entries are documented in tooltips.</p></body></html>
+ <div style="font-size:10pt;">
+<p>Set a regular expression pattern to use when trying to guess ebook metadata from filenames. </p>
+<p>A <a href="http://calibre-ebook.com/user_manual/regexp.html">tutorial</a> on using regular expressions is available.</p>
+<p>Use the <b>Test</b> functionality below to test your regular expression on a few sample filenames (remember to include the file extension). The group names for the various metadata entries are documented in tooltips.</p></div>
Qt::RichText
@@ -104,8 +101,8 @@ p, li { white-space: pre-wrap; }
0
0
- 277
- 276
+ 305
+ 263
diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py
index ae9d6e2f71..fed2e42470 100644
--- a/src/calibre/gui2/library/delegates.py
+++ b/src/calibre/gui2/library/delegates.py
@@ -12,11 +12,11 @@ from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \
QPainterPath, QLinearGradient, QBrush, \
QPen, QStyle, QPainter, QStyleOptionViewItemV4, \
QIcon, QDoubleSpinBox, QVariant, QSpinBox, \
- QStyledItemDelegate, QCompleter, \
- QComboBox, QTextDocument
+ QStyledItemDelegate, QComboBox, QTextDocument
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
-from calibre.gui2.widgets import EnLineEdit, CompleteLineEdit
+from calibre.gui2.widgets import EnLineEdit
+from calibre.gui2.complete import MultiCompleteLineEdit
from calibre.utils.date import now, format_date
from calibre.utils.config import tweaks
from calibre.utils.formatter import validation_formatter
@@ -151,38 +151,15 @@ class TextDelegate(QStyledItemDelegate): # {{{
self.auto_complete_function = f
def createEditor(self, parent, option, index):
- editor = EnLineEdit(parent)
if self.auto_complete_function:
+ editor = MultiCompleteLineEdit(parent)
+ editor.set_separator(None)
complete_items = [i[1] for i in self.auto_complete_function()]
- completer = QCompleter(complete_items, self)
- completer.setCaseSensitivity(Qt.CaseInsensitive)
- completer.setCompletionMode(QCompleter.PopupCompletion)
- editor.setCompleter(completer)
- return editor
-#}}}
-
-class TagsDelegate(QStyledItemDelegate): # {{{
- def __init__(self, parent):
- QStyledItemDelegate.__init__(self, parent)
- self.db = None
-
- def set_database(self, db):
- self.db = db
-
- def createEditor(self, parent, option, index):
- if self.db:
- col = index.model().column_map[index.column()]
- if not index.model().is_custom_column(col):
- editor = CompleteLineEdit(parent, self.db.all_tags())
- else:
- editor = CompleteLineEdit(parent,
- sorted(list(self.db.all_custom(label=self.db.field_metadata.key_to_label(col))),
- key=sort_key))
- return editor
+ editor.update_items_cache(complete_items)
else:
editor = EnLineEdit(parent)
return editor
-# }}}
+#}}}
class CompleteDelegate(QStyledItemDelegate): # {{{
def __init__(self, parent, sep, items_func_name, space_before_sep=False):
@@ -197,13 +174,15 @@ class CompleteDelegate(QStyledItemDelegate): # {{{
def createEditor(self, parent, option, index):
if self.db and hasattr(self.db, self.items_func_name):
col = index.model().column_map[index.column()]
+ editor = MultiCompleteLineEdit(parent)
+ editor.set_separator(self.sep)
+ editor.set_space_before_sep(self.space_before_sep)
if not index.model().is_custom_column(col):
- editor = CompleteLineEdit(parent, getattr(self.db, self.items_func_name)(),
- self.sep, self.space_before_sep)
+ all_items = getattr(self.db, self.items_func_name)()
else:
- editor = CompleteLineEdit(parent,
- sorted(list(self.db.all_custom(label=self.db.field_metadata.key_to_label(col))),
- key=sort_key), self.sep, self.space_before_sep)
+ all_items = list(self.db.all_custom(
+ label=self.db.field_metadata.key_to_label(col)))
+ editor.update_items_cache(all_items)
else:
editor = EnLineEdit(parent)
return editor
@@ -273,13 +252,11 @@ class CcTextDelegate(QStyledItemDelegate): # {{{
editor.setRange(-100., float(sys.maxint))
editor.setDecimals(2)
else:
- editor = EnLineEdit(parent)
+ editor = MultiCompleteLineEdit(parent)
+ editor.set_separator(None)
complete_items = sorted(list(m.db.all_custom(label=m.db.field_metadata.key_to_label(col))),
key=sort_key)
- completer = QCompleter(complete_items, self)
- completer.setCaseSensitivity(Qt.CaseInsensitive)
- completer.setCompletionMode(QCompleter.PopupCompletion)
- editor.setCompleter(completer)
+ editor.update_items_cache(complete_items)
return editor
# }}}
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 31b8cf46bf..0b6991665b 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -800,9 +800,10 @@ class BooksModel(QAbstractTableModel): # {{{
return True
id = self.db.id(row)
- self.db.set_custom(id, val, extra=s_index,
+ books_to_refresh = set([id])
+ books_to_refresh |= self.db.set_custom(id, val, extra=s_index,
label=label, num=None, append=False, notify=True)
- self.refresh_ids([id], current_row=row)
+ self.refresh_ids(list(books_to_refresh), current_row=row)
return True
def setData(self, index, value, role):
@@ -819,6 +820,7 @@ class BooksModel(QAbstractTableModel): # {{{
value.toDate() if column in ('timestamp', 'pubdate') else \
unicode(value.toString())
id = self.db.id(row)
+ books_to_refresh = set([id])
if column == 'rating':
val = 0 if val < 0 else 5 if val > 5 else val
val *= 2
@@ -826,7 +828,8 @@ class BooksModel(QAbstractTableModel): # {{{
elif column == 'series':
val = val.strip()
if not val:
- self.db.set_series(id, val)
+ books_to_refresh |= self.db.set_series(id, val,
+ allow_case_change=True)
self.db.set_series_index(id, 1.0)
else:
pat = re.compile(r'\[([.0-9]+)\]')
@@ -840,7 +843,8 @@ class BooksModel(QAbstractTableModel): # {{{
if ni != 1:
self.db.set_series_index(id, ni)
if val:
- self.db.set_series(id, val)
+ books_to_refresh |= self.db.set_series(id, val,
+ allow_case_change=True)
elif column == 'timestamp':
if val.isNull() or not val.isValid():
return False
@@ -850,8 +854,9 @@ class BooksModel(QAbstractTableModel): # {{{
return False
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
else:
- self.db.set(row, column, val)
- self.refresh_ids([id], row)
+ books_to_refresh |= self.db.set(row, column, val,
+ allow_case_change=True)
+ self.refresh_ids(list(books_to_refresh), row)
self.dataChanged.emit(index, index)
return True
diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py
index 590a8be3bb..f9058fc333 100644
--- a/src/calibre/gui2/metadata/basic_widgets.py
+++ b/src/calibre/gui2/metadata/basic_widgets.py
@@ -12,8 +12,8 @@ from PyQt4.Qt import Qt, QDateEdit, QDate, \
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \
QPushButton, QSpinBox, QLineEdit
-from calibre.gui2.widgets import EnLineEdit, CompleteComboBox, \
- EnComboBox, FormatList, ImageView, CompleteLineEdit
+from calibre.gui2.widgets import EnLineEdit, FormatList, ImageView
+from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
from calibre.utils.icu import sort_key
from calibre.utils.config import tweaks, prefs
from calibre.ebooks.metadata import title_sort, authors_to_string, \
@@ -149,14 +149,15 @@ class TitleSortEdit(TitleEdit):
# }}}
# Authors {{{
-class AuthorsEdit(CompleteComboBox):
+class AuthorsEdit(MultiCompleteComboBox):
TOOLTIP = ''
LABEL = _('&Author(s):')
def __init__(self, parent):
self.dialog = parent
- CompleteComboBox.__init__(self, parent)
+ self.books_to_refresh = set([])
+ MultiCompleteComboBox.__init__(self, parent)
self.setToolTip(self.TOOLTIP)
self.setWhatsThis(self.TOOLTIP)
self.setEditable(True)
@@ -166,6 +167,7 @@ class AuthorsEdit(CompleteComboBox):
return _('Unknown')
def initialize(self, db, id_):
+ self.books_to_refresh = set([])
all_authors = db.all_authors()
all_authors.sort(key=lambda x : sort_key(x[1]))
for i in all_authors:
@@ -185,7 +187,8 @@ class AuthorsEdit(CompleteComboBox):
def commit(self, db, id_):
authors = self.current_val
- db.set_authors(id_, authors, notify=False)
+ self.books_to_refresh |= db.set_authors(id_, authors, notify=False,
+ allow_case_change=True)
return True
@dynamic_property
@@ -283,19 +286,21 @@ class AuthorSortEdit(EnLineEdit):
# }}}
# Series {{{
-class SeriesEdit(EnComboBox):
+class SeriesEdit(MultiCompleteComboBox):
TOOLTIP = _('List of known series. You can add new series.')
LABEL = _('&Series:')
def __init__(self, parent):
- EnComboBox.__init__(self, parent)
+ MultiCompleteComboBox.__init__(self, parent)
+ self.set_separator(None)
self.dialog = parent
self.setSizeAdjustPolicy(
self.AdjustToMinimumContentsLengthWithIcon)
self.setToolTip(self.TOOLTIP)
self.setWhatsThis(self.TOOLTIP)
self.setEditable(True)
+ self.books_to_refresh = set([])
@dynamic_property
def current_val(self):
@@ -312,8 +317,10 @@ class SeriesEdit(EnComboBox):
return property(fget=fget, fset=fset)
def initialize(self, db, id_):
+ self.books_to_refresh = set([])
all_series = db.all_series()
all_series.sort(key=lambda x : sort_key(x[1]))
+ self.update_items_cache([x[1] for x in all_series])
series_id = db.series_id(id_, index_is_id=True)
idx, c = None, 0
for i in all_series:
@@ -330,7 +337,8 @@ class SeriesEdit(EnComboBox):
def commit(self, db, id_):
series = self.current_val
- db.set_series(id_, series, notify=False, commit=True)
+ self.books_to_refresh |= db.set_series(id_, series, notify=False,
+ commit=True, allow_case_change=True)
return True
class SeriesIndexEdit(QDoubleSpinBox):
@@ -472,6 +480,7 @@ class FormatsManager(QWidget): # {{{
def initialize(self, db, id_):
self.changed = False
exts = db.formats(id_, index_is_id=True)
+ self.original_val = set([])
if exts:
exts = exts.split(',')
for ext in exts:
@@ -482,6 +491,7 @@ class FormatsManager(QWidget): # {{{
if size is None:
continue
Format(self.formats, ext, size, timestamp=timestamp)
+ self.original_val.add(ext.lower())
def commit(self, db, id_):
if not self.changed:
@@ -500,11 +510,12 @@ class FormatsManager(QWidget): # {{{
for ext in new_extensions:
db.add_format(id_, ext, open(paths[ext], 'rb'), notify=False,
index_is_id=True)
- db_extensions = set([f.lower() for f in db.formats(id_,
- index_is_id=True).split(',')])
+ dbfmts = db.formats(id_, index_is_id=True)
+ db_extensions = set([f.lower() for f in (dbfmts.split(',') if dbfmts
+ else [])])
extensions = new_extensions.union(old_extensions)
for ext in db_extensions:
- if ext not in extensions:
+ if ext not in extensions and ext in self.original_val:
db.remove_format(id_, ext, notify=False, index_is_id=True)
self.changed = False
@@ -811,14 +822,15 @@ class RatingEdit(QSpinBox): # {{{
# }}}
-class TagsEdit(CompleteLineEdit): # {{{
+class TagsEdit(MultiCompleteLineEdit): # {{{
LABEL = _('Ta&gs:')
TOOLTIP = '
'+_('Tags categorize the book. This is particularly '
'useful while searching.
They can be any words'
'or phrases, separated by commas.')
def __init__(self, parent):
- CompleteLineEdit.__init__(self, parent)
+ MultiCompleteLineEdit.__init__(self, parent)
+ self.books_to_refresh = set([])
self.setToolTip(self.TOOLTIP)
self.setWhatsThis(self.TOOLTIP)
@@ -833,10 +845,11 @@ class TagsEdit(CompleteLineEdit): # {{{
return property(fget=fget, fset=fset)
def initialize(self, db, id_):
+ self.books_to_refresh = set([])
tags = db.tags(id_, index_is_id=True)
tags = tags.split(',') if tags else []
self.current_val = tags
- self.update_items_cache(db.all_tags())
+ self.all_items = db.all_tags()
self.original_val = self.current_val
@property
@@ -857,11 +870,13 @@ class TagsEdit(CompleteLineEdit): # {{{
d = TagEditor(self, db, id_)
if d.exec_() == TagEditor.Accepted:
self.current_val = d.tags
- self.update_items_cache(db.all_tags())
+ self.all_items = db.all_tags()
def commit(self, db, id_):
- db.set_tags(id_, self.current_val, notify=False, commit=False)
+ self.books_to_refresh |= db.set_tags(
+ id_, self.current_val, notify=False, commit=False,
+ allow_case_change=True)
return True
# }}}
@@ -907,13 +922,15 @@ class ISBNEdit(QLineEdit): # {{{
# }}}
-class PublisherEdit(EnComboBox): # {{{
+class PublisherEdit(MultiCompleteComboBox): # {{{
LABEL = _('&Publisher:')
def __init__(self, parent):
- EnComboBox.__init__(self, parent)
+ MultiCompleteComboBox.__init__(self, parent)
+ self.set_separator(None)
self.setSizeAdjustPolicy(
self.AdjustToMinimumContentsLengthWithIcon)
+ self.books_to_refresh = set([])
@dynamic_property
def current_val(self):
@@ -930,8 +947,10 @@ class PublisherEdit(EnComboBox): # {{{
return property(fget=fget, fset=fset)
def initialize(self, db, id_):
+ self.books_to_refresh = set([])
all_publishers = db.all_publishers()
all_publishers.sort(key=lambda x : sort_key(x[1]))
+ self.update_items_cache([x[1] for x in all_publishers])
publisher_id = db.publisher_id(id_, index_is_id=True)
idx, c = None, 0
for i in all_publishers:
@@ -946,7 +965,8 @@ class PublisherEdit(EnComboBox): # {{{
self.setCurrentIndex(idx)
def commit(self, db, id_):
- db.set_publisher(id_, self.current_val, notify=False, commit=False)
+ self.books_to_refresh |= db.set_publisher(id_, self.current_val,
+ notify=False, commit=False, allow_case_change=True)
return True
# }}}
diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py
index 3b163d84f7..1be954155c 100644
--- a/src/calibre/gui2/metadata/single.py
+++ b/src/calibre/gui2/metadata/single.py
@@ -31,6 +31,8 @@ class MetadataSingleDialogBase(ResizableDialog):
def __init__(self, db, parent=None):
self.db = db
self.changed = set([])
+ self.books_to_refresh = set([])
+ self.rows_to_refresh = set([])
ResizableDialog.__init__(self, parent)
def setupUi(self, *args): # {{{
@@ -192,6 +194,7 @@ class MetadataSingleDialogBase(ResizableDialog):
def __call__(self, id_):
self.book_id = id_
+ self.books_to_refresh = set([])
for widget in self.basic_metadata_widgets:
widget.initialize(self.db, id_)
for widget in self.custom_metadata_widgets:
@@ -295,6 +298,8 @@ class MetadataSingleDialogBase(ResizableDialog):
try:
if not widget.commit(self.db, self.book_id):
return False
+ self.books_to_refresh |= getattr(widget, 'books_to_refresh',
+ set([]))
except IOError, err:
if err.errno == 13: # Permission denied
import traceback
@@ -306,9 +311,13 @@ class MetadataSingleDialogBase(ResizableDialog):
return False
raise
for widget in getattr(self, 'custom_metadata_widgets', []):
- widget.commit(self.book_id)
+ self.books_to_refresh |= widget.commit(self.book_id)
self.db.commit()
+ rows = self.db.refresh_ids(list(self.books_to_refresh))
+ if rows:
+ self.rows_to_refresh |= set(rows)
+
return True
def accept(self):
@@ -330,12 +339,14 @@ class MetadataSingleDialogBase(ResizableDialog):
self.current_row = current_row
if view_slot is not None:
self.view_format.connect(view_slot)
- self.do_one()
+ self.do_one(apply_changes=False)
ret = self.exec_()
self.break_cycles()
return ret
- def do_one(self, delta=0):
+ def do_one(self, delta=0, apply_changes=True):
+ if apply_changes:
+ self.apply_changes()
self.current_row += delta
prev = next_ = None
if self.current_row > 0:
@@ -353,6 +364,7 @@ class MetadataSingleDialogBase(ResizableDialog):
self.prev_button.setVisible(prev is not None)
self(self.db.id(self.row_list[self.current_row]))
+
def break_cycles(self):
# Break any reference cycles that could prevent python
# from garbage collecting this dialog
@@ -618,7 +630,7 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{
def edit_metadata(db, row_list, current_row, parent=None, view_slot=None):
d = MetadataSingleDialog(db, parent)
d.start(row_list, current_row, view_slot=view_slot)
- return d.changed
+ return d.changed, d.rows_to_refresh
if __name__ == '__main__':
from PyQt4.Qt import QApplication
diff --git a/src/calibre/gui2/preferences/adding.py b/src/calibre/gui2/preferences/adding.py
index 7a27ed8f2d..e919d53b64 100644
--- a/src/calibre/gui2/preferences/adding.py
+++ b/src/calibre/gui2/preferences/adding.py
@@ -7,7 +7,8 @@ __docformat__ = 'restructuredtext en'
-from calibre.gui2.preferences import ConfigWidgetBase, test_widget
+from calibre.gui2.preferences import ConfigWidgetBase, test_widget, \
+ CommaSeparatedList
from calibre.gui2.preferences.adding_ui import Ui_Form
from calibre.utils.config import prefs
from calibre.gui2.widgets import FilenamePattern
@@ -22,6 +23,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('read_file_metadata', prefs)
r('swap_author_names', prefs)
r('add_formats_to_existing', prefs)
+ r('new_book_tags', prefs, setting=CommaSeparatedList)
self.filename_pattern = FilenamePattern(self)
self.metadata_box.layout().insertWidget(0, self.filename_pattern)
diff --git a/src/calibre/gui2/preferences/adding.ui b/src/calibre/gui2/preferences/adding.ui
index 062c45e1ad..75e6c466f0 100644
--- a/src/calibre/gui2/preferences/adding.ui
+++ b/src/calibre/gui2/preferences/adding.ui
@@ -6,7 +6,7 @@
0
0
- 1010
+ 750
339
@@ -27,10 +27,37 @@
-
- Read metadata from &file contents rather than file name
+ Read &metadata from &file contents rather than file name
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Swap the firstname and lastname of the author. This affects only metadata read from file names.
+
+
+ &Swap author firstname and lastname
+
+
+
+
+
-
@@ -44,7 +71,24 @@ Title match ignores leading indefinite articles ("the", "a",
- -
+
-
+
+
+ &Tags to apply when adding a book:
+
+
+ opt_new_book_tags
+
+
+
+ -
+
+
+ A comma-separated list of tags that will be applied to books added to the library
+
+
+
+ -
&Configure metadata from file name
@@ -66,16 +110,6 @@ Title match ignores leading indefinite articles ("the", "a",
- -
-
-
- Swap the firstname and lastname of the author. This affects only metadata read from file names.
-
-
- &Swap author firstname and lastname
-
-
-
diff --git a/src/calibre/gui2/preferences/behavior.py b/src/calibre/gui2/preferences/behavior.py
index 169a2b76fe..aeee6e5064 100644
--- a/src/calibre/gui2/preferences/behavior.py
+++ b/src/calibre/gui2/preferences/behavior.py
@@ -9,8 +9,7 @@ import re
from PyQt4.Qt import Qt, QVariant, QListWidgetItem
-from calibre.gui2.preferences import ConfigWidgetBase, test_widget, \
- CommaSeparatedList
+from calibre.gui2.preferences import ConfigWidgetBase, test_widget
from calibre.gui2.preferences.behavior_ui import Ui_Form
from calibre.gui2 import config, info_dialog, dynamic
from calibre.utils.config import prefs
@@ -49,7 +48,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
restrictions = sorted(saved_searches().names(), key=sort_key)
choices = [('', '')] + [(x, x) for x in restrictions]
r('gui_restriction', db.prefs, choices=choices)
- r('new_book_tags', prefs, setting=CommaSeparatedList)
self.reset_confirmation_button.clicked.connect(self.reset_confirmation_dialogs)
self.input_up_button.clicked.connect(self.up_input)
diff --git a/src/calibre/gui2/preferences/behavior.ui b/src/calibre/gui2/preferences/behavior.ui
index 8332212235..0f35d28cd5 100644
--- a/src/calibre/gui2/preferences/behavior.ui
+++ b/src/calibre/gui2/preferences/behavior.ui
@@ -164,20 +164,6 @@
- -
-
-
- A comma-separated list of tags that will be applied to books added to the library
-
-
-
- -
-
-
- Tags to apply when adding a book:
-
-
-
-
diff --git a/src/calibre/gui2/preferences/plugins.py b/src/calibre/gui2/preferences/plugins.py
index 1edd4fe5f9..8f77a03c24 100644
--- a/src/calibre/gui2/preferences/plugins.py
+++ b/src/calibre/gui2/preferences/plugins.py
@@ -266,7 +266,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
def add_plugin(self):
path = choose_files(self, 'add a plugin dialog', _('Add plugin'),
- filters=[(_('Plugins'), ['zip'])], all_files=False,
+ filters=[(_('Plugins') + ' (*.zip)', ['zip'])], all_files=False,
select_only_single_file=True)
if not path:
return
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index 677ebac083..041f0a715e 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -1041,23 +1041,28 @@ class TagsModel(QAbstractItemModel): # {{{
def tokens(self):
ans = []
+ # Tags can be in the news and the tags categories. However, because of
+ # the desire to use two different icons (tags and news), the nodes are
+ # not shared, which can lead to the possibility of searching twice for
+ # the same tag. The tags_seen set helps us prevent that
tags_seen = set()
+ # Tag nodes are in their own category and possibly in user categories.
+ # They will be 'checked' in both places, but we want to put the node
+ # into the search string only once. The nodes_seen set helps us do that
+ nodes_seen = set()
row_index = -1
for i, key in enumerate(self.row_map):
if self.hidden_categories and self.categories[i] in self.hidden_categories:
continue
row_index += 1
- if key.startswith('@'):
- # User category, so skip it. The tag will be marked in its real category
- continue
category_item = self.root_item.children[row_index]
for tag_item in category_item.child_tags():
tag = tag_item.tag
if tag.state != TAG_SEARCH_STATES['clear']:
prefix = ' not ' if tag.state == TAG_SEARCH_STATES['mark_minus'] \
else ''
- category = key if key != 'news' else 'tag'
+ category = tag.category if key != 'news' else 'tag'
if tag.name and tag.name[0] == u'\u2605': # char is a star. Assume rating
ans.append('%s%s:%s'%(prefix, category, len(tag.name)))
else:
@@ -1065,6 +1070,9 @@ class TagsModel(QAbstractItemModel): # {{{
if tag.name in tags_seen:
continue
tags_seen.add(tag.name)
+ if tag in nodes_seen:
+ continue
+ nodes_seen.add(tag)
ans.append('%s%s:"=%s"'%(prefix, category, tag.name))
return ans
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index dd4509acea..e818e6a3c0 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -420,7 +420,8 @@ class ResultCache(SearchQueryParser): # {{{
return candidates - res
return res
- def get_matches(self, location, query, allow_recursion=True, candidates=None):
+ def get_matches(self, location, query, candidates=None,
+ allow_recursion=True):
matches = set([])
if candidates is None:
candidates = self.universal_set()
@@ -434,8 +435,8 @@ class ResultCache(SearchQueryParser): # {{{
if isinstance(location, list):
if allow_recursion:
for loc in location:
- matches |= self.get_matches(loc, query, candidates,
- allow_recursion=False)
+ matches |= self.get_matches(loc, query,
+ candidates=candidates, allow_recursion=False)
return matches
raise ParseException(query, len(query), 'Recursive query group detected', self)
diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py
index b9c151309c..23127035d2 100644
--- a/src/calibre/library/catalog.py
+++ b/src/calibre/library/catalog.py
@@ -232,6 +232,7 @@ class BIBTEX(CatalogPlugin): # {{{
help = _('The fields to output when cataloging books in the '
'database. Should be a comma-separated list of fields.\n'
'Available fields: %s.\n'
+ 'plus user-created custom fields.\n'
'Example: %s=title,authors,tags\n'
"Default: '%%default'\n"
"Applies to: BIBTEX output format")%(', '.join(FIELDS),
@@ -269,7 +270,7 @@ class BIBTEX(CatalogPlugin): # {{{
dest = 'bib_cit',
action = None,
help = _('The template for citation creation from database fields.\n'
- ' Should be a template with {} enclosed fields.\n'
+ 'Should be a template with {} enclosed fields.\n'
'Available fields: %s.\n'
"Default: '%%default'\n"
"Applies to: BIBTEX output format")%', '.join(TEMPLATE_ALLOWED_FIELDS)),
@@ -344,7 +345,7 @@ class BIBTEX(CatalogPlugin): # {{{
if field == 'authors' :
bibtex_entry.append(u'author = "%s"' % bibtexdict.bibtex_author_format(item))
- elif field in ['title', 'publisher', 'cover', 'uuid',
+ elif field in ['title', 'publisher', 'cover', 'uuid', 'ondevice',
'author_sort', 'series'] :
bibtex_entry.append(u'%s = "%s"' % (field, bibtexdict.utf8ToBibtex(item)))
@@ -378,7 +379,7 @@ class BIBTEX(CatalogPlugin): # {{{
if calibre_files:
files = [u':%s:%s' % (format, format.rpartition('.')[2].upper())\
for format in item]
- bibtex_entry.append(u'files = "%s"' % u', '.join(files))
+ bibtex_entry.append(u'file = "%s"' % u', '.join(files))
elif field == 'series_index' :
bibtex_entry.append(u'volume = "%s"' % int(item))
@@ -474,6 +475,8 @@ class BIBTEX(CatalogPlugin): # {{{
if opts.verbose:
opts_dict = vars(opts)
log("%s(): Generating %s" % (self.name,self.fmt))
+ if opts.connected_device['is_device_connected']:
+ log(" connected_device: %s" % opts.connected_device['name'])
if opts_dict['search_text']:
log(" --search='%s'" % opts_dict['search_text'])
@@ -548,6 +551,7 @@ class BIBTEX(CatalogPlugin): # {{{
as outfile:
#File header
nb_entries = len(data)
+
#check in book strict if all is ok else throw a warning into log
if bib_entry == 'book' :
nb_books = len(filter(check_entry_book_valid, data))
@@ -555,6 +559,11 @@ class BIBTEX(CatalogPlugin): # {{{
log(" WARNING: only %d entries in %d are book compatible" % (nb_books, nb_entries))
nb_entries = nb_books
+ # If connected device, add 'On Device' values to data
+ if opts.connected_device['is_device_connected'] and 'ondevice' in fields:
+ for entry in data:
+ entry['ondevice'] = db.catalog_plugin_on_device_temp_mapping[entry['id']]['ondevice']
+
outfile.write(u'%%%Calibre catalog\n%%%{0} entries in catalog\n\n'.format(nb_entries))
outfile.write(u'@preamble{"This catalog of %d entries was generated by calibre on %s"}\n\n'
% (nb_entries, nowf().strftime("%A, %d. %B %Y %H:%M").decode(preferred_encoding)))
diff --git a/src/calibre/library/check_library.py b/src/calibre/library/check_library.py
index b49330db3e..19ecb97308 100644
--- a/src/calibre/library/check_library.py
+++ b/src/calibre/library/check_library.py
@@ -30,8 +30,8 @@ CHECKS = [('invalid_titles', _('Invalid titles'), True, False),
('missing_formats', _('Missing book formats'), False, False),
('extra_formats', _('Extra book formats'), True, False),
('extra_files', _('Unknown files in books'), True, False),
- ('missing_covers', _('Missing covers in books'), False, True),
- ('extra_covers', _('Extra covers in books'), True, True),
+ ('missing_covers', _('Missing covers files'), False, True),
+ ('extra_covers', _('Cover files not in database'), True, True),
('failed_folders', _('Folders raising exception'), False, False)
]
diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py
index f94081f046..467a3f309e 100644
--- a/src/calibre/library/custom_columns.py
+++ b/src/calibre/library/custom_columns.py
@@ -440,22 +440,24 @@ class CustomColumns(object):
self.dirtied(ids, commit=False)
self.conn.commit()
- def set_custom(self, id, val, label=None, num=None,
- append=False, notify=True, extra=None, commit=True):
- self._set_custom(id, val, label=label, num=num, append=append,
- notify=notify, extra=extra)
+ def set_custom(self, id, val, label=None, num=None, append=False,
+ notify=True, extra=None, commit=True, allow_case_change=False):
+ rv = self._set_custom(id, val, label=label, num=num, append=append,
+ notify=notify, extra=extra,
+ allow_case_change=allow_case_change)
self.dirtied([id], commit=False)
if commit:
self.conn.commit()
+ return rv
- def _set_custom(self, id_, val, label=None, num=None,
- append=False, notify=True, extra=None):
+ def _set_custom(self, id_, val, label=None, num=None, append=False,
+ notify=True, extra=None, allow_case_change=False):
if label is not None:
data = self.custom_column_label_map[label]
if num is not None:
data = self.custom_column_num_map[num]
if data['datatype'] == 'composite':
- return None
+ return set([])
if not data['editable']:
raise ValueError('Column %r is not editable'%data['label'])
table, lt = self.custom_table_names(data['num'])
@@ -466,10 +468,11 @@ class CustomColumns(object):
if data['datatype'] == 'series' and extra is None:
(val, extra) = self._get_series_values(val)
+ books_to_refresh = set([])
if data['normalized']:
if data['datatype'] == 'enumeration' and (
val and val not in data['display']['enum_values']):
- return None
+ return books_to_refresh
if not append or not data['is_multiple']:
self.conn.execute('DELETE FROM %s WHERE book=?'%lt, (id_,))
self.conn.execute(
@@ -483,6 +486,7 @@ class CustomColumns(object):
for x in set(set_val) - set(existing):
if x is None:
continue
+ case_change = False
existing = list(self.all_custom(num=data['num']))
lx = [t.lower() if hasattr(t, 'lower') else t for t in existing]
try:
@@ -492,13 +496,14 @@ class CustomColumns(object):
if idx > -1:
ex = existing[idx]
xid = self.conn.get(
- 'SELECT id FROM %s WHERE value=?'%table, (ex,), all=False)
- if ex != x:
+ 'SELECT id FROM %s WHERE value=?'%table, (ex,), all=False)
+ if allow_case_change and ex != x:
+ case_change = True
self.conn.execute(
- 'UPDATE %s SET value=? WHERE id=?'%table, (x, xid))
+ 'UPDATE %s SET value=? WHERE id=?'%table, (x, xid))
else:
xid = self.conn.execute(
- 'INSERT INTO %s(value) VALUES(?)'%table, (x,)).lastrowid
+ 'INSERT INTO %s(value) VALUES(?)'%table, (x,)).lastrowid
if not self.conn.get(
'SELECT book FROM %s WHERE book=? AND value=?'%lt,
(id_, xid), all=False):
@@ -512,6 +517,10 @@ class CustomColumns(object):
self.conn.execute(
'''INSERT INTO %s(book, value)
VALUES (?,?)'''%lt, (id_, xid))
+ if case_change:
+ bks = self.conn.get('SELECT book FROM %s WHERE value=?'%lt,
+ (xid,))
+ books_to_refresh |= set([bk[0] for bk in bks])
nval = self.conn.get(
'SELECT custom_%s FROM meta2 WHERE id=?'%data['num'],
(id_,), all=False)
@@ -530,7 +539,7 @@ class CustomColumns(object):
row_is_id=True)
if notify:
self.notify('metadata', [id_])
- return nval
+ return books_to_refresh
def clean_custom(self):
st = ('DELETE FROM {table} WHERE (SELECT COUNT(id) FROM {lt} WHERE'
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 3c6d4016f2..b6ad03fdec 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -430,8 +430,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
authors = self.authors(id, index_is_id=True)
if not authors:
authors = _('Unknown')
- author = ascii_filename(authors.split(',')[0][:self.PATH_LIMIT]).decode(filesystem_encoding, 'ignore')
- title = ascii_filename(self.title(id, index_is_id=True)[:self.PATH_LIMIT]).decode(filesystem_encoding, 'ignore')
+ author = ascii_filename(authors.split(',')[0])[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace')
+ title = ascii_filename(self.title(id, index_is_id=True))[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace')
path = author + '/' + title + ' (%d)'%id
return path
@@ -442,8 +442,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
authors = self.authors(id, index_is_id=True)
if not authors:
authors = _('Unknown')
- author = ascii_filename(authors.split(',')[0][:self.PATH_LIMIT]).decode(filesystem_encoding, 'replace')
- title = ascii_filename(self.title(id, index_is_id=True)[:self.PATH_LIMIT]).decode(filesystem_encoding, 'replace')
+ author = ascii_filename(authors.split(',')[0])[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace')
+ title = ascii_filename(self.title(id, index_is_id=True))[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace')
name = title + ' - ' + author
while name.endswith('.'):
name = name[:-1]
@@ -1412,7 +1412,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
icon = icon_map['search']
for srch in saved_searches().names():
items.append(Tag(srch, tooltip=saved_searches().lookup(srch),
- icon=icon, category='search'))
+ sort=srch, icon=icon, category='search'))
if len(items):
if icon_map is not None:
icon_map['search'] = icon_map['search']
@@ -1479,29 +1479,34 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return float(tweaks['series_index_auto_increment'])
return 1.0
- def set(self, row, column, val):
+ def set(self, row, column, val, allow_case_change=False):
'''
Convenience method for setting the title, authors, publisher or rating
'''
id = self.data[row][0]
col = {'title':1, 'authors':2, 'publisher':3, 'rating':4, 'tags':7}[column]
+ books_to_refresh = set()
self.data.set(row, col, val)
if column == 'authors':
val = string_to_authors(val)
- self.set_authors(id, val, notify=False)
+ books_to_refresh |= self.set_authors(id, val, notify=False,
+ allow_case_change=allow_case_change)
elif column == 'title':
self.set_title(id, val, notify=False)
elif column == 'publisher':
- self.set_publisher(id, val, notify=False)
+ books_to_refresh |= self.set_publisher(id, val, notify=False,
+ allow_case_change=allow_case_change)
elif column == 'rating':
self.set_rating(id, val, notify=False)
elif column == 'tags':
- self.set_tags(id, [x.strip() for x in val.split(',') if x.strip()],
- append=False, notify=False)
+ books_to_refresh |= \
+ self.set_tags(id, [x.strip() for x in val.split(',') if x.strip()],
+ append=False, notify=False, allow_case_change=allow_case_change)
self.data.refresh_ids(self, [id])
self.set_path(id, True)
self.notify('metadata', [id])
+ return books_to_refresh
def set_metadata(self, id, mi, ignore_errors=False,
set_title=True, set_authors=True, commit=True):
@@ -1549,7 +1554,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
elif mi.cover is not None:
if os.access(mi.cover, os.R_OK):
with lopen(mi.cover, 'rb') as f:
- doit(self.set_cover, id, f, commit=False)
+ raw = f.read()
+ if raw:
+ doit(self.set_cover, id, raw, commit=False)
if mi.tags:
doit(self.set_tags, id, mi.tags, notify=False, commit=False)
if mi.comments:
@@ -1625,54 +1632,73 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
result.append(r)
return ' & '.join(result).replace('|', ',')
- def _set_authors(self, id, authors):
+ def _set_authors(self, id, authors, allow_case_change=False):
if not authors:
authors = [_('Unknown')]
self.conn.execute('DELETE FROM books_authors_link WHERE book=?',(id,))
+ books_to_refresh = set([])
+ final_authors = []
for a in authors:
+ case_change = False
if not a:
continue
a = a.strip().replace(',', '|')
if not isinstance(a, unicode):
a = a.decode(preferred_encoding, 'replace')
- author = self.conn.get('SELECT id from authors WHERE name=?', (a,), all=False)
- if author:
- aid = author
+ aus = self.conn.get('SELECT id, name FROM authors WHERE name=?', (a,))
+ if aus:
+ aid, name = aus[0]
# Handle change of case
- self.conn.execute('UPDATE authors SET name=? WHERE id=?', (a, aid))
+ if name != a:
+ if allow_case_change:
+ self.conn.execute('''UPDATE authors
+ SET name=? WHERE id=?''', (a, aid))
+ case_change = True
+ else:
+ a = name
else:
- aid = self.conn.execute('INSERT INTO authors(name) VALUES (?)', (a,)).lastrowid
+ aid = self.conn.execute('''INSERT INTO authors(name)
+ VALUES (?)''', (a,)).lastrowid
+ final_authors.append(a.replace('|', ','))
try:
- self.conn.execute('INSERT INTO books_authors_link(book, author) VALUES (?,?)',
- (id, aid))
+ self.conn.execute('''INSERT INTO books_authors_link(book, author)
+ VALUES (?,?)''', (id, aid))
except IntegrityError: # Sometimes books specify the same author twice in their metadata
pass
+ if case_change:
+ bks = self.conn.get('''SELECT book FROM books_authors_link
+ WHERE author=?''', (aid,))
+ books_to_refresh |= set([bk[0] for bk in bks])
ss = self.author_sort_from_book(id, index_is_id=True)
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
(ss, id))
self.data.set(id, self.FIELD_MAP['authors'],
- ','.join([a.replace(',', '|') for a in authors]),
+ ','.join([a.replace(',', '|') for a in final_authors]),
row_is_id=True)
self.data.set(id, self.FIELD_MAP['author_sort'], ss, row_is_id=True)
aum = self.authors_with_sort_strings(id, index_is_id=True)
self.data.set(id, self.FIELD_MAP['au_map'],
':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (au, aus) in aum]),
row_is_id=True)
+ return books_to_refresh
- def set_authors(self, id, authors, notify=True, commit=True):
+ def set_authors(self, id, authors, notify=True, commit=True,
+ allow_case_change=False):
'''
Note that even if commit is False, the db will still be committed to
because this causes the location of files to change
:param authors: A list of authors.
'''
- self._set_authors(id, authors)
+ books_to_refresh = self._set_authors(id, authors,
+ allow_case_change=allow_case_change)
self.dirtied([id], commit=False)
if commit:
self.conn.commit()
self.set_path(id, index_is_id=True)
if notify:
self.notify('metadata', [id])
+ return books_to_refresh
def set_title_sort(self, id, title_sort_, notify=True, commit=True):
if not title_sort_:
@@ -1695,10 +1721,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
title = title.decode(preferred_encoding, 'replace')
self.conn.execute('UPDATE books SET title=? WHERE id=?', (title, id))
self.data.set(id, self.FIELD_MAP['title'], title, row_is_id=True)
- if tweaks['title_series_sorting'] == 'library_order':
- self.data.set(id, self.FIELD_MAP['sort'], title_sort(title), row_is_id=True)
- else:
- self.data.set(id, self.FIELD_MAP['sort'], title, row_is_id=True)
+ ts = self.conn.get('SELECT sort FROM books WHERE id=?', (id,),
+ all=False)
+ if ts:
+ self.data.set(id, self.FIELD_MAP['sort'], ts, row_is_id=True)
return True
def set_title(self, id, title, notify=True, commit=True):
@@ -1736,24 +1762,44 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.notify('metadata', [id])
- def set_publisher(self, id, publisher, notify=True, commit=True):
+ def set_publisher(self, id, publisher, notify=True, commit=True,
+ allow_case_change=False):
self.conn.execute('DELETE FROM books_publishers_link WHERE book=?',(id,))
- self.conn.execute('DELETE FROM publishers WHERE (SELECT COUNT(id) FROM books_publishers_link WHERE publisher=publishers.id) < 1')
+ self.conn.execute('''DELETE FROM publishers WHERE (SELECT COUNT(id)
+ FROM books_publishers_link
+ WHERE publisher=publishers.id) < 1''')
+ books_to_refresh = set()
if publisher:
+ case_change = False
if not isinstance(publisher, unicode):
publisher = publisher.decode(preferred_encoding, 'replace')
- pub = self.conn.get('SELECT id from publishers WHERE name=?', (publisher,), all=False)
- if pub:
- aid = pub
+ pubx = self.conn.get('''SELECT id,name from publishers
+ WHERE name=?''', (publisher,))
+ if pubx:
+ aid, cur_name = pubx[0]
+ if publisher != cur_name:
+ if allow_case_change:
+ self.conn.execute('''UPDATE publishers SET name=?
+ WHERE id=?''', (publisher, aid))
+ case_change = True
+ else:
+ publisher = cur_name
else:
- aid = self.conn.execute('INSERT INTO publishers(name) VALUES (?)', (publisher,)).lastrowid
- self.conn.execute('INSERT INTO books_publishers_link(book, publisher) VALUES (?,?)', (id, aid))
- self.dirtied([id], commit=False)
- if commit:
- self.conn.commit()
- self.data.set(id, self.FIELD_MAP['publisher'], publisher, row_is_id=True)
- if notify:
- self.notify('metadata', [id])
+ aid = self.conn.execute('''INSERT INTO publishers(name)
+ VALUES (?)''', (publisher,)).lastrowid
+ self.conn.execute('''INSERT INTO books_publishers_link(book, publisher)
+ VALUES (?,?)''', (id, aid))
+ if case_change:
+ bks = self.conn.get('''SELECT book FROM books_publishers_link
+ WHERE publisher=?''', (aid,))
+ books_to_refresh |= set([bk[0] for bk in bks])
+ self.dirtied([id], commit=False)
+ if commit:
+ self.conn.commit()
+ self.data.set(id, self.FIELD_MAP['publisher'], publisher, row_is_id=True)
+ if notify:
+ self.notify('metadata', [id])
+ return books_to_refresh
def set_uuid(self, id, uuid, notify=True, commit=True):
if uuid:
@@ -2117,17 +2163,21 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def commit(self):
self.conn.commit()
- def set_tags(self, id, tags, append=False, notify=True, commit=True):
+ def set_tags(self, id, tags, append=False, notify=True, commit=True,
+ allow_case_change=False):
'''
@param tags: list of strings
@param append: If True existing tags are not removed
'''
if not append:
self.conn.execute('DELETE FROM books_tags_link WHERE book=?', (id,))
- self.conn.execute('DELETE FROM tags WHERE (SELECT COUNT(id) FROM books_tags_link WHERE tag=tags.id) < 1')
+ self.conn.execute('''DELETE FROM tags WHERE (SELECT COUNT(id)
+ FROM books_tags_link WHERE tag=tags.id) < 1''')
otags = self.get_tags(id)
tags = self.cleanup_tags(tags)
+ books_to_refresh = set([])
for tag in (set(tags)-otags):
+ case_changed = False
tag = tag.strip()
if not tag:
continue
@@ -2142,15 +2192,20 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if idx > -1:
etag = existing_tags[idx]
tid = self.conn.get('SELECT id FROM tags WHERE name=?', (etag,), all=False)
- if etag != tag:
+ if allow_case_change and etag != tag:
self.conn.execute('UPDATE tags SET name=? WHERE id=?', (tag, tid))
+ case_changed = True
else:
tid = self.conn.execute('INSERT INTO tags(name) VALUES(?)', (tag,)).lastrowid
- if not self.conn.get('SELECT book FROM books_tags_link WHERE book=? AND tag=?',
- (id, tid), all=False):
- self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)',
- (id, tid))
+ if not self.conn.get('''SELECT book FROM books_tags_link
+ WHERE book=? AND tag=?''', (id, tid), all=False):
+ self.conn.execute('''INSERT INTO books_tags_link(book, tag)
+ VALUES (?,?)''', (id, tid))
+ if case_changed:
+ bks = self.conn.get('SELECT book FROM books_tags_link WHERE tag=?',
+ (tid,))
+ books_to_refresh |= set([bk[0] for bk in bks])
self.dirtied([id], commit=False)
if commit:
self.conn.commit()
@@ -2158,12 +2213,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.data.set(id, self.FIELD_MAP['tags'], tags, row_is_id=True)
if notify:
self.notify('metadata', [id])
+ return books_to_refresh
def unapply_tags(self, book_id, tags, notify=True):
for tag in tags:
id = self.conn.get('SELECT id FROM tags WHERE name=?', (tag,), all=False)
if id:
- self.conn.execute('DELETE FROM books_tags_link WHERE tag=? AND book=?', (id, book_id))
+ self.conn.execute('''DELETE FROM books_tags_link
+ WHERE tag=? AND book=?''', (id, book_id))
self.conn.commit()
self.data.refresh_ids(self, [book_id])
if notify:
@@ -2207,31 +2264,44 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
pass
return (val, None)
- def set_series(self, id, series, notify=True, commit=True):
+ def set_series(self, id, series, notify=True, commit=True, allow_case_change=True):
self.conn.execute('DELETE FROM books_series_link WHERE book=?',(id,))
self.conn.execute('''DELETE FROM series
WHERE (SELECT COUNT(id) FROM books_series_link
WHERE series=series.id) < 1''')
(series, idx) = self._get_series_values(series)
+ books_to_refresh = set([])
if series:
+ case_change = False
if not isinstance(series, unicode):
series = series.decode(preferred_encoding, 'replace')
series = series.strip()
series = u' '.join(series.split())
- s = self.conn.get('SELECT id from series WHERE name=?', (series,), all=False)
- if s:
- aid = s
+ sx = self.conn.get('SELECT id,name from series WHERE name=?', (series,))
+ if sx:
+ aid, cur_name = sx[0]
+ if cur_name != series:
+ if allow_case_change:
+ self.conn.execute('UPDATE series SET name=? WHERE id=?', (series, aid))
+ case_change = True
+ else:
+ series = cur_name
else:
aid = self.conn.execute('INSERT INTO series(name) VALUES (?)', (series,)).lastrowid
self.conn.execute('INSERT INTO books_series_link(book, series) VALUES (?,?)', (id, aid))
if idx:
self.set_series_index(id, idx, notify=notify, commit=commit)
+ if case_change:
+ bks = self.conn.get('SELECT book FROM books_series_link WHERE series=?',
+ (aid,))
+ books_to_refresh |= set([bk[0] for bk in bks])
self.dirtied([id], commit=False)
if commit:
self.conn.commit()
self.data.set(id, self.FIELD_MAP['series'], series, row_is_id=True)
if notify:
self.notify('metadata', [id])
+ return books_to_refresh
def set_series_index(self, id, idx, notify=True, commit=True):
if idx is None:
diff --git a/src/calibre/library/restore.py b/src/calibre/library/restore.py
index bc2c740279..76f3c0333d 100644
--- a/src/calibre/library/restore.py
+++ b/src/calibre/library/restore.py
@@ -141,7 +141,7 @@ class Restore(Thread):
sizes = [os.path.getsize(os.path.join(dirpath, x)) for x in formats]
names = [os.path.splitext(x)[0] for x in formats]
opf = os.path.join(dirpath, 'metadata.opf')
- mi = OPF(opf).to_book_metadata()
+ mi = OPF(opf, basedir=dirpath).to_book_metadata()
timestamp = os.path.getmtime(opf)
path = os.path.relpath(dirpath, self.src_library_path).replace(os.sep,
'/')
diff --git a/src/calibre/manual/conversion.rst b/src/calibre/manual/conversion.rst
index 7f3ff21fe0..60f8a10fc6 100644
--- a/src/calibre/manual/conversion.rst
+++ b/src/calibre/manual/conversion.rst
@@ -311,10 +311,25 @@ remove all non-breaking-space entities, or may include false positive matches re
:guilabel:`Ensure scene breaks are consistently formatted`
With this option |app| will attempt to detect common scene-break markers and ensure that they are center aligned.
- It also attempts to detect scene breaks defined by white space and replace them with a horizontal rule 15% of the
- page width. Some readers may find this desirable as these 'soft' scene breaks often become page breaks on readers, and
- thus become difficult to distinguish.
+ 'Soft' scene break markers, i.e. scene breaks only defined by extra white space, are styled to ensure that they
+ will not be displayed in conjunction with page breaks.
+:guilabel:`Replace scene breaks`
+ If this option is configured then |app| will replace scene break markers it finds with the replacement text specified by the
+ user. Please note that some ornamental characters may not be supported across all reading devices.
+
+ In general you should avoid using html tags, |app| will discard any tags and use pre-defined markup.
+ tags, i.e. horizontal rules, and
tags are exceptions. Horizontal rules can optionally be specified with styles, if you
+ choose to add your own style be sure to include the 'width' setting, otherwise the style information will be discarded. Image
+ tags can used, but |app| does not provide the ability to add the image during conversion, this must be done after the fact using
+ the 'Tweak Epub' feature, or Sigil.
+
+ Example image tag (place the image within an 'Images' folder inside the epub after conversion):
+
+
+ Example horizontal rule with styles:
+
+
:guilabel:`Remove unnecessary hyphens`
|app| will analyze all hyphenated content in the document when this option is enabled. The document itself is used
as a dictionary for analysis. This allows |app| to accurately remove hyphens for any words in the document in any language,
@@ -628,7 +643,7 @@ between 0 and 1. The default is 0.45, just under the median line length. Lower t
text in the unwrapping. Increase to include less. You can adjust this value in the conversion settings under :guilabel:`PDF Input`.
Also, they often have headers and footers as part of the document that will become included with the text.
-Use the options to remove headers and footers to mitigate this issue. If the headers and footers are not
+Use the Search and Replace panel to remove headers and footers to mitigate this issue. If the headers and footers are not
removed from the text it can throw off the paragraph unwrapping. To learn how to use the header and footer removal options, read
:ref:`regexptutorial`.
diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst
index 15e836940e..18c53ade5d 100644
--- a/src/calibre/manual/faq.rst
+++ b/src/calibre/manual/faq.rst
@@ -295,7 +295,9 @@ 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 |app| developers will ever feel motivated enough to support it.
+any |app| developers will ever feel motivated enough to support it. There is however, a |app| plugin
+that allows you to create collections on your Kindle from the |app| metadata. It is available
+`here `_.
Library Management
------------------
@@ -389,6 +391,8 @@ Take your pick:
* A tribute to the SONY Librie which was the first e-ink based e-book reader
* My wife chose it ;-)
+|app| is pronounced as cal-i-ber *not* ca-li-bre. If you're wondering, |app| is the British/commonwealth spelling for caliber. Being Indian, that's the natural spelling for me.
+
Why does |app| show only some of my fonts on OS X?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|app| embeds fonts in ebook files it creates. E-book files support embedding only TrueType (.ttf) fonts. Most fonts on OS X systems are in .dfont format, thus they cannot be embedded. |app| shows only TrueType fonts found on your system. You can obtain many TrueType fonts on the web. Simply download the .ttf files and add them to the Library/Fonts directory in your home directory.
diff --git a/src/calibre/translations/af.po b/src/calibre/translations/af.po
index 6ab1845db6..a018d96646 100644
--- a/src/calibre/translations/af.po
+++ b/src/calibre/translations/af.po
@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: calibre\n"
"Report-Msgid-Bugs-To: FULL NAME \n"
-"POT-Creation-Date: 2011-01-21 23:34+0000\n"
+"POT-Creation-Date: 2011-01-30 19:51+0000\n"
"PO-Revision-Date: 2009-11-06 19:11+0000\n"
"Last-Translator: Kovid Goyal \n"
"Language-Team: Afrikaans \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-01-23 04:39+0000\n"
+"X-Launchpad-Export-Date: 2011-01-31 04:37+0000\n"
"X-Generator: Launchpad (build 12177)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
@@ -45,7 +45,7 @@ msgstr "Doen absolute niks"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1894
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1896
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:24
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:235
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:236
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:31
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:32
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:73
@@ -55,7 +55,7 @@ msgstr "Doen absolute niks"
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:36
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:61
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:54
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:358
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:365
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:36
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:64
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:66
@@ -71,6 +71,7 @@ msgstr "Doen absolute niks"
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:91
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:101
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/snb.py:16
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:56
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:14
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:42
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:68
@@ -82,9 +83,9 @@ msgstr "Doen absolute niks"
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:878
#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:49
#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:51
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:952
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:957
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1023
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:958
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:963
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1029
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:143
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:150
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:64
@@ -114,8 +115,8 @@ msgstr "Doen absolute niks"
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:101
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:329
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:331
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:364
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:371
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:299
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:306
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:100
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:331
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:334
@@ -125,14 +126,14 @@ msgstr "Doen absolute niks"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:120
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:145
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:147
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1050
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1053
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1064
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1067
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:55
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:67
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:145
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:185
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:724
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:726
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:193
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:236
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:245
@@ -147,18 +148,18 @@ msgstr "Doen absolute niks"
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:191
#: /home/kovid/work/calibre/src/calibre/library/cli.py:215
#: /home/kovid/work/calibre/src/calibre/library/database.py:914
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:402
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:414
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1474
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1575
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2415
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2417
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2548
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:432
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:444
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1529
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1632
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2472
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2474
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2605
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:229
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:158
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:161
#: /home/kovid/work/calibre/src/calibre/library/server/xml.py:79
-#: /home/kovid/work/calibre/src/calibre/utils/localization.py:118
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:129
#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:46
#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:64
#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:78
@@ -431,7 +432,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:889
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:163
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:269
msgid "Plugins"
msgstr ""
@@ -503,57 +504,57 @@ msgid "This profile is intended for the SONY PRS-900."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:90
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:522
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:538
msgid "This profile is intended for the Microsoft Reader."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:101
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:533
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:549
msgid "This profile is intended for the Mobipocket books."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:114
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:546
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:562
msgid "This profile is intended for the Hanlin V3 and its clones."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:126
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:558
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:574
msgid "This profile is intended for the Hanlin V5 and its clones."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:136
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:566
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:582
msgid "This profile is intended for the Cybook G3."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:149
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:579
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:596
msgid "This profile is intended for the Cybook Opus."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:161
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:592
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:609
msgid "This profile is intended for the Amazon Kindle."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:173
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:642
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:659
msgid "This profile is intended for the Irex Illiad."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:185
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:655
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:672
msgid "This profile is intended for the IRex Digital Reader 1000."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:198
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:669
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:686
msgid "This profile is intended for the IRex Digital Reader 800."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:210
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:683
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:700
msgid "This profile is intended for the B&N Nook."
msgstr ""
@@ -592,24 +593,32 @@ msgid "This profile is intended for the SONY PRS-300."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:502
+msgid "Suitable for use with any e-ink device"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:509
+msgid "Suitable for use with any large screen e-ink device"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:518
msgid "This profile is intended for the 5-inch JetBook."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:511
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:527
msgid ""
"This profile is intended for the SONY PRS line. The 500/505/700 etc, in "
"landscape mode. Mainly useful for comics."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:618
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:635
msgid "This profile is intended for the Amazon Kindle DX."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:695
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:712
msgid "This profile is intended for the B&N Nook Color."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:706
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:723
msgid "This profile is intended for the Sanda Bambook."
msgstr ""
@@ -683,13 +692,13 @@ msgstr ""
msgid "Communicate with Android phones."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:61
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:62
msgid ""
"Comma separated list of directories to send e-books to on the device. The "
"first one that exists will be used"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:107
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:108
msgid "Communicate with S60 phones."
msgstr ""
@@ -758,19 +767,19 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:886
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:892
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:922
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:264
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:219
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:232
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2279
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:262
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:244
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:257
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2336
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:150
msgid "News"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2554
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:65
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:599
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2242
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2260
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:625
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2299
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2317
msgid "Catalog"
msgstr ""
@@ -882,7 +891,7 @@ msgid "Communicate with the Blackberry smart phone."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:14
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:254
#: /home/kovid/work/calibre/src/calibre/devices/nuut2/driver.py:18
#: /home/kovid/work/calibre/src/calibre/devices/prs500/driver.py:90
msgid "Kovid Goyal"
@@ -896,23 +905,23 @@ msgstr ""
msgid "Communicate with the Cybook Orizon eBook reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:24
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:25
msgid "Communicate with the EB600 eBook reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:193
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:194
msgid "Communicate with the Astak Mentor EB600"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:216
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:217
msgid "Communicate with the PocketBook 301 reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:233
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:234
msgid "Communicate with the PocketBook 602/603/902/903 reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:252
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253
msgid "Communicate with the PocketBook 701"
msgstr ""
@@ -1041,7 +1050,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:446
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:292
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:295
msgid "Not Implemented"
msgstr ""
@@ -1901,27 +1910,27 @@ msgstr ""
msgid "Replacement to replace the text found with sr3-search."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:671
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:673
msgid "Could not find an ebook inside the archive"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:729
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:731
msgid "Values of series index and rating must be numbers. Ignoring"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:736
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:738
msgid "Failed to parse date/time"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:891
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:893
msgid "Converting input to HTML..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:918
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:920
msgid "Running transforms on ebook..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1006
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1008
msgid "Creating"
msgstr ""
@@ -2398,13 +2407,13 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:544
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:62
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:495
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:546
msgid "No"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:544
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:62
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:495
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:546
msgid "Yes"
msgstr ""
@@ -2599,7 +2608,7 @@ msgid "Download covers from openlibrary.org"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:108
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:137
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:140
msgid "ISBN: %s not found"
msgstr ""
@@ -2607,28 +2616,32 @@ msgstr ""
msgid "Download covers from librarything.com"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:129
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:82
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:130
msgid "LibraryThing.com timed out. Try again later."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:136
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:89
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:139
msgid ""
"Could not fetch cover as server is experiencing high load. Please try again "
"later."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:140
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:93
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:143
msgid "LibraryThing.com server error. Try again later."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:226
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:177
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:270
+msgid ""
+"To use librarything.com you must sign up for a %sfree account%s and enter "
+"your username and password separated by a : below."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:240
msgid "Download covers from Douban.com"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:235
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:249
msgid "Douban.com API timed out. Try again later."
msgstr ""
@@ -2679,7 +2692,7 @@ msgid "Downloads social metadata from amazon.com"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:254
-msgid "Downloads series/tags/rating information from librarything.com"
+msgid "Downloads series/covers/rating information from librarything.com"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:25
@@ -2789,11 +2802,7 @@ msgstr ""
msgid "The publisher of the book to search for."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:90
-msgid " not found."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:100
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:75
msgid ""
"\n"
"%prog [options] ISBN\n"
@@ -2866,10 +2875,14 @@ msgid "Cover saved to file "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1308
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1442
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1448
msgid "Cover"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:14
+msgid "Metadata source"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:22
msgid "Modify images to meet Palm device size limitations."
msgstr ""
@@ -2912,70 +2925,70 @@ msgstr ""
msgid "This is an Amazon Topaz book. It cannot be processed."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1443
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1449
msgid "Title Page"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1444
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1450
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:54
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:199
msgid "Table of Contents"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1445
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1451
msgid "Index"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1446
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1452
msgid "Glossary"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1447
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1453
msgid "Acknowledgements"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1448
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1454
msgid "Bibliography"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1449
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1455
msgid "Colophon"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1450
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1456
msgid "Copyright"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1451
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1457
msgid "Dedication"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1452
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1458
msgid "Epigraph"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1453
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1459
msgid "Foreword"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1454
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1460
msgid "List of Illustrations"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1455
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1461
msgid "List of Tables"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1456
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1462
msgid "Notes"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1457
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1463
msgid "Preface"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1458
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1464
msgid "Main Text"
msgstr ""
@@ -2984,8 +2997,8 @@ msgid "%s format books are not supported"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/cover.py:98
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:218
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:220
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:691
msgid "Book %s of %s"
msgstr ""
@@ -3043,7 +3056,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/output.py:32
#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:37
#: /home/kovid/work/calibre/src/calibre/ebooks/rb/output.py:21
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:35
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:34
msgid "Add Table of Contents to beginning of the book."
msgstr ""
@@ -3262,7 +3275,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:49
msgid ""
"Preserve the aspect ratio of the cover, instead of stretching it to fill the "
-"ull first page of the generated pdf."
+"full first page of the generated pdf."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:55
@@ -3299,13 +3312,13 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:25
#: /home/kovid/work/calibre/src/calibre/ebooks/tcr/output.py:23
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:31
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:30
msgid ""
"Specify the character encoding of the output document. The default is utf-8."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:29
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:38
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:37
msgid ""
"The maximum number of characters per line. This splits on the first space "
"before the specified value. If no space is found the line will be broken at "
@@ -3383,35 +3396,39 @@ msgstr ""
msgid "Do not insert a Table of Contents into the output text."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:25
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:24
msgid ""
"Type of newline to use. Options are %s. Default is 'system'. Use 'old_mac' "
"for compatibility with Mac OS 9 and earlier. For Mac OS X use 'unix'. "
"'system' will default to the newline type used by this OS."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:45
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:44
msgid ""
"Force splitting on the max-line-length value when no space is present. Also "
"allows max-line-length to be below the minimum"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:49
-msgid "Produce Markdown formatted text."
+msgid ""
+"Formatting used within the document.\n"
+"* plain: Produce plain text.\n"
+"* markdown: Produce Markdown formatted text.\n"
+"* textile: Produce Textile formatted text."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:52
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:55
msgid ""
"Do not remove links within the document. This is only useful when paired "
-"with the markdown-format option because links are always removed with plain "
-"text output."
+"with a txt-output-formatting option that is not none because links are "
+"always removed with plain text output."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:57
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:60
msgid ""
"Do not remove image references within the document. This is only useful when "
-"paired with the markdown-format option because image references are always "
-"removed with plain text output."
+"paired with a txt-output-formatting option that is not none because links "
+"are always removed with plain text output."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:70
@@ -3492,58 +3509,44 @@ msgstr ""
msgid "Default action to perform when send to device button is clicked"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:124
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:126
msgid "Maximum number of waiting worker processes"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:126
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:128
msgid "Download social metadata (tags/rating/etc.)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:128
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:130
msgid "Overwrite author and title with new metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:130
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:132
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:101
msgid "Automatically download the cover, if available"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:132
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:134
msgid "Limit max simultaneous jobs to number of CPUs"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:134
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:136
msgid "tag browser categories not to display"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:136
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:138
msgid "The layout of the user interface"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:138
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:140
msgid "Show the average rating per item indication in the tag browser"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:140
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:142
msgid "Disable UI animations"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:188
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:520
-msgid "Copied"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:222
-msgid "Copy to Clipboard"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:222
-#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:96
-msgid "Copy"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:475
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:410
msgid "Choose Files"
msgstr ""
@@ -3587,89 +3590,89 @@ msgstr ""
msgid "Add from ISBN"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:233
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:236
msgid "Uploading books to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:189
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:306
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:192
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:308
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:517
msgid "Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:190
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:193
msgid "EPUB Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:191
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:194
msgid "LRF Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:192
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:195
msgid "HTML Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:193
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:196
msgid "LIT Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:194
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:197
msgid "MOBI Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:195
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:198
msgid "Topaz books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:196
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:199
msgid "Text books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:197
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:200
msgid "PDF Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:198
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:201
msgid "SNB Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:199
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:202
msgid "Comics"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:200
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:203
msgid "Archives"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:204
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:207
msgid "Supported books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:246
msgid "Merged some books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:244
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:247
msgid ""
"Some duplicates were found and merged into the following existing books:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:253
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:256
msgid "Failed to read metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:254
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:257
msgid "Failed to read metadata from the following"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:275
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:280
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:299
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:278
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:283
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:302
msgid "Add to library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:280
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:283
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:116
#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:85
@@ -3677,12 +3680,12 @@ msgstr ""
msgid "No book selected"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:293
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:296
msgid ""
"The following books are virtual and cannot be added to the calibre library:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:299
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:302
msgid "No book files found"
msgstr ""
@@ -3700,12 +3703,12 @@ msgid "Fetch annotations (experimental)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:56
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:240
msgid "Use library only"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:57
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:237
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:241
msgid "User annotations generated from main library only"
msgstr ""
@@ -3751,7 +3754,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:20
#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:34
-msgid "Create catalog of books in your calibre library"
+msgid "Create a catalog of the books in your calibre library"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:31
@@ -3774,199 +3777,188 @@ msgstr ""
msgid "Select destination for %s.%s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:112
-msgid "Checking database integrity"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:128
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:594
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:41
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:201
-#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54
-msgid "Error"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:129
-msgid "Failed to check database integrity"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:134
-msgid "Some inconsistencies found"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:135
-msgid ""
-"The following books had formats or covers listed in the database that are "
-"not actually available. The entries for the formats/covers have been "
-"removed. You should check them manually. This can happen if you manipulate "
-"the files in the library folder directly."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:142
-msgid "No errors found"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:143
-msgid "The integrity check completed with no uncorrectable errors found."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:152
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:81
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:54
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:167
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:126
msgid "%d books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:153
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:82
msgid "Choose calibre library to work with"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:162
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:91
msgid "Switch/create library..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:102
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:77
msgid "Quick switch"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:104
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:78
msgid "Rename library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:177
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:106
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:79
msgid "Delete library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:109
msgid "Pick a random book"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:199
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:128
msgid "Library Maintenance"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:200
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:129
msgid "Library metadata backup status"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:204
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:133
msgid "Start backing up metadata of all books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:137
msgid "Check library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:212
-msgid "Check database integrity"
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:141
+msgid "Restore database"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:216
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:363
-msgid "Recover database"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:289
msgid "Rename"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:290
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:217
msgid "Choose a new name for the library %s. "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:291
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:218
msgid "Note that the actual library folder will be renamed."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:298
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:225
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:191
msgid "Already exists"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:299
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:226
msgid "The folder %s already exists. Delete it first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:305
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:232
msgid "Rename failed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:306
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:233
msgid ""
"Failed to rename the library at %s. The most common cause for this is if one "
"of the files in the library is open in another program."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:316
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:243
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:53
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:78
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:360
+#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:424
+#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:430
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:102
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:169
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:275
msgid "Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:317
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:244
msgid "All files from %s will be permanently deleted. Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:336
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:263
msgid "none"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:337
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:264
msgid "Backup status"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:338
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:265
msgid "Book metadata files remaining to be written: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:344
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:271
msgid "Backup metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:345
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:272
msgid ""
"Metadata will be backed up while calibre is running, at the rate of "
-"approximately 1 book per second."
+"approximately 1 book every three seconds."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:364
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:304
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:106
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:111
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:286
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:340
+msgid "Success"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:305
msgid ""
-"This command rebuilds your calibre database from the information stored by "
-"calibre in the OPF files.This function is not currently available in the "
-"GUI. You can recover your database using the 'calibredb restore_database' "
-"command line function."
+"Found no errors in your calibre library database. Do you want calibre to "
+"check if the files in your library match the information in the database?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:378
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:310
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:672
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:877
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:101
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:190
+msgid "Failed"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:311
+msgid "Database integrity check failed, click Show details for details."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:316
+msgid "No problems found"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:317
+msgid "The files in your library match the information in the database."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:326
msgid "No library found"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:379
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:327
msgid ""
"No existing calibre library was found at %s. It will be removed from the "
"list of known libraries."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:418
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:423
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:380
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:385
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:167
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:780
msgid "Not allowed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:419
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:381
msgid ""
"You cannot change libraries while using the environment variable "
"CALIBRE_OVERRIDE_DATABASE_PATH."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:424
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:386
msgid "You cannot change libraries while jobs are running."
msgstr ""
@@ -3987,7 +3979,7 @@ msgid "Bulk convert"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:86
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:489
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:506
msgid "Cannot convert"
msgstr ""
@@ -4032,13 +4024,6 @@ msgstr ""
msgid "Could not copy books: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:674
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:854
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:190
-msgid "Failed"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:153
msgid "Copied %d books to %s"
msgstr ""
@@ -4608,7 +4593,7 @@ msgid "Selected books have no formats"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:101
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:79
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:127
msgid "Choose the format to view"
msgstr ""
@@ -4659,7 +4644,7 @@ msgid "The specified directory could not be processed."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:250
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:823
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:821
msgid "No books"
msgstr ""
@@ -4788,8 +4773,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:79
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:80
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:86
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:485
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:490
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:530
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417
@@ -4799,10 +4784,11 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:462
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:92
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:161
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:164
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:168
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:171
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:170
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:183
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:131
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:133
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:136
@@ -4895,7 +4881,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:78
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:83
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:294
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:316
msgid "None"
msgstr ""
@@ -4923,7 +4909,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input.py:13
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:17
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:16
msgid "Options specific to"
msgstr ""
@@ -4939,11 +4925,11 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/pml_output.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output.py:15
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:17
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:16
msgid "output"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:86
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:89
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:42
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:295
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_tab_template_ui.py:32
@@ -4974,7 +4960,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/xpath_wizard_ui.py:72
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:77
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:40
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:111
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:114
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:136
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:81
@@ -4984,7 +4970,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:139
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:60
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:113
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:58
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:86
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:46
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:67
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:68
@@ -4996,64 +4982,68 @@ msgstr ""
msgid "Form"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:87
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:90
msgid "Bib file encoding:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:88
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:91
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:43
msgid "Fields to include in output:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:89
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:92
msgid "ascii/LaTeX"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:90
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:93
msgid "Encoding configuration (change if you have errors) :"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:91
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:94
msgid "strict"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:92
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:95
msgid "replace"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:93
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:96
msgid "ignore"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:94
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:97
msgid "backslashreplace"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:95
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:98
msgid "BibTeX entry type:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:96
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:99
msgid "mixed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:97
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:100
msgid "misc"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:98
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:101
msgid "book"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:99
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:102
msgid "Create a citation tag?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:100
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:103
+msgid "Add files path with formats?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:104
msgid "Expression to form the BibTeX citation tag:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:101
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:105
msgid ""
"Some explanation about this template:\n"
" -The fields availables are 'author_sort', 'authors', 'id',\n"
@@ -5293,6 +5283,10 @@ msgstr ""
msgid "Remove formatting"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:96
+msgid "Copy"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:97
msgid "Paste"
msgstr ""
@@ -5390,18 +5384,18 @@ msgstr ""
msgid "HTML Source"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:38
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:40
msgid ""
"For settings that cannot be specified in this dialog, use the values saved "
"in a previous conversion (if they exist) instead of using the defaults "
"specified in the Preferences"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:72
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:74
msgid "Bulk Convert"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:87
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:89
#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:189
msgid "Options specific to the output format."
msgstr ""
@@ -5925,7 +5919,7 @@ msgid "Change the title of this book"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:166
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:450
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:495
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420
msgid "&Author(s): "
msgstr ""
@@ -5941,7 +5935,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:169
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:460
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:505
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:428
msgid "&Publisher: "
msgstr ""
@@ -5952,7 +5946,7 @@ msgid "Ta&gs: "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:171
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:462
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:507
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:430
msgid ""
"Tags categorize the book. This is particularly useful while searching. "
@@ -5960,7 +5954,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:469
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:514
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:433
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:214
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:289
@@ -5969,8 +5963,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:173
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:470
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:471
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:515
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:516
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:434
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:435
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:288
@@ -6131,35 +6125,59 @@ msgstr ""
msgid "RB Output"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:85
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:133
msgid "No formats available"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:86
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:134
msgid "Cannot build regex using the GUI builder without a book."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:105
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:153
msgid "Open book"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:57
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:90
msgid "Regex Builder"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:58
-msgid "Preview"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:59
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:91
msgid "Regex:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:60
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:122
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:92
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:125
msgid "Test"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:93
+msgid "Occurrences:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:94
+msgid "0"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:95
+msgid "Goto:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:96
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:81
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:89
+msgid "&Previous"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:97
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:82
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:88
+msgid "&Next"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:98
+msgid "Preview"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:15
msgid ""
"Search\n"
@@ -6173,13 +6191,13 @@ msgstr ""
msgid "&Search Regular Expression"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:52
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:86
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:71
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:99
msgid "Invalid regular expression"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:53
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:87
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:72
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:100
msgid "Invalid regular expression: %s"
msgstr ""
@@ -6221,6 +6239,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:117
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:76
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:96
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box_ui.py:52
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:53
msgid "Dialog"
msgstr ""
@@ -6367,7 +6386,7 @@ msgstr ""
msgid "Do not insert Table of Contents into output text when using markdown"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:16
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:15
msgid "TXT Output"
msgstr ""
@@ -6496,75 +6515,86 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:87
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:111
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:148
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:167
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:273
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:495
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:302
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:306
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:499
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:500
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:182
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:289
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:546
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:576
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:599
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:650
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:303
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:308
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:501
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:502
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:114
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:134
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:235
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:268
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:272
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:975
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:973
msgid "Undefined"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:125
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:607
msgid "star(s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:126
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:608
msgid "Unrated"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:159
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:169
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:637
msgid "Set '%s' to today"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:269
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:171
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:639
+msgid "Clear '%s'"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:285
msgid " index:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:335
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:351
msgid ""
"The enumeration \"{0}\" contains an invalid value that will be set to the "
"default"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:495
-msgid "Do not change"
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:505
+msgid "Apply changes"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:544
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:683
msgid "Remove series"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:547
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:686
msgid "Automatically number books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:550
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:689
msgid "Force numbers to start with "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:608
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:758
msgid ""
"The enumeration \"{0}\" contains invalid values that will not appear in the "
"list"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:664
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:800
msgid "Remove all tags"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:685
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:820
msgid "tags to add"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:690
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:826
msgid "tags to remove"
msgstr ""
@@ -6646,93 +6676,108 @@ msgstr ""
msgid "Eject device"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:594
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:41
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:304
+#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54
+msgid "Error"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:595
msgid "Error communicating with device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:611
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1100
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1114
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:297
msgid "No suitable formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:629
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:627
msgid "Select folder to open as device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:680
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:678
msgid "Error talking to device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:681
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:679
msgid ""
"There was a temporary error talking to the device. Please unplug and "
"reconnect the device and or reboot."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:724
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:722
msgid "Device: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:726
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:724
msgid " detected."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:824
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:822
msgid "selected to send"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:829
-msgid "Choose format to send to device"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:838
-msgid "No device"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:839
-msgid "Cannot send: No device is connected"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:842
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:846
-msgid "No card"
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:841
+msgid "%i of %i Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:843
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:847
+msgid "0 of %i Books"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:844
+msgid "Choose format to send to device"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:852
+msgid "No device"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:853
+msgid "Cannot send: No device is connected"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:856
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:860
+msgid "No card"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:857
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:861
msgid "Cannot send: Device has no storage card"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:893
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:976
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1094
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:907
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:990
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1108
msgid "Auto convert the following books before uploading to the device?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:922
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:936
msgid "Sending catalogs to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1007
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1021
msgid "Sending news to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1061
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1075
msgid "Sending books to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1101
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1115
msgid ""
"Could not upload the following books to the device, as no suitable formats "
"were found. Convert the book(s) to a format supported by your device first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1165
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1179
msgid "No space on device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1166
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1180
msgid ""
"
Cannot upload books to device there is no more free space available "
msgstr ""
@@ -6816,107 +6861,197 @@ msgstr ""
msgid "Fit &cover within view"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:81
-msgid "&Previous"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:82
-msgid "&Next"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog.py:38
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog.py:33
msgid "My Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:80
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:92
#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:309
msgid "Generate catalog"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:81
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:93
msgid "Generate catalog for {0} books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:82
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:94
msgid "Catalog &format:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:83
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:95
msgid ""
"Catalog &title (existing catalog with the same title will be replaced):"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:84
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:96
msgid "&Send catalog to device automatically"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:85
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:97
msgid "Catalog options"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:25
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:26
-msgid "Check Library"
+msgid "Checking database integrity"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:35
-msgid "&Run the check"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:55
+msgid "Dumping database to SQL"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:38
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:81
+msgid "Loading database from SQL"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:148
+msgid "Check Library -- Problems Found"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:158
+msgid ""
+"
Help
\n"
+"\n"
+" calibre stores the list of your books and their metadata in a\n"
+" database. The actual book files and covers are stored as normal\n"
+" files in the calibre library folder. The database contains a list of "
+"the files\n"
+" and covers belonging to each book entry. This tool checks that the\n"
+" actual files in the library folder on your computer match the\n"
+" information in the database.
\n"
+"\n"
+" The result of each type of check is shown to the left. The "
+"various\n"
+" checks are:\n"
+"
\n"
+" \n"
+" - Invalid titles: These are files and folders appearing\n"
+" in the library where books titles should, but that do not have the\n"
+" correct form to be a book title.
\n"
+" - Extra titles: These are extra files in your calibre\n"
+" library that appear to be correctly-formed titles, but have no "
+"corresponding\n"
+" entries in the database
\n"
+" - Invalid authors: These are files appearing\n"
+" in the library where only author folders should be.
\n"
+" - Extra authors: These are folders in the\n"
+" calibre library that appear to be authors but that do not have "
+"entries\n"
+" in the database
\n"
+" - Missing book formats: These are book formats that are in\n"
+" the database but have no corresponding format file in the book's "
+"folder.\n"
+"
- Extra book formats: These are book format files found in\n"
+" the book's folder but not in the database.\n"
+"
- Unknown files in books: These are extra files in the\n"
+" folder of each book that do not correspond to a known format or "
+"cover\n"
+" file.
\n"
+" - Missing cover files: These represent books that are "
+"marked\n"
+" in the database as having covers but the actual cover files are\n"
+" missing.
\n"
+" - Cover files not in database: These are books that have\n"
+" cover files but are marked as not having covers in the "
+"database.
\n"
+" - Folder raising exception: These represent folders in the\n"
+" calibre library that could not be processed/understood by this\n"
+" tool.
\n"
+"
\n"
+"\n"
+" There are two kinds of automatic fixes possible: Delete\n"
+" marked and Fix marked.
\n"
+" Delete marked is used to remove extra files/folders/covers "
+"that\n"
+" have no entries in the database. Check the box next to the item you "
+"want\n"
+" to delete. Use with caution.
\n"
+" Fix marked is applicable only to covers (the two lines "
+"marked\n"
+" 'fixable'). In the case of missing cover files, checking the "
+"fixable\n"
+" box and pushing this button will remove the cover mark from the\n"
+" database for all the files in that category. In the case of extra\n"
+" cover files, checking the fixable box and pushing this button will\n"
+" add the cover mark to the database for all the files in that\n"
+" category.
\n"
+" "
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:218
+msgid "&Run the check again"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:221
msgid "Copy &to clipboard"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:45
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:228
msgid "Delete marked files (checked subitems)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:51
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:234
msgid "Fix marked sections (checked fixable items)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:61
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:244
msgid "Names to ignore:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:66
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:249
msgid ""
"Enter comma-separated standard file name wildcards, such as synctoy*.dat"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:69
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:252
msgid "Extensions to ignore"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:74
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:257
msgid ""
"Enter comma-separated extensions without a leading dot. Used only in book "
"folders"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:111
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:306
msgid "(fixable)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:134
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:329
msgid "Path from library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:134
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:329
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:89
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:253
msgid "Name"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:158
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:354
msgid ""
"The marked files and folders will be permanently deleted. Are you "
"sure?"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:45
msgid "Choose Format"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:49
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1162
+msgid "Format"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:50
+msgid "Existing"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:51
+msgid "Convertible"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library.py:43
msgid "Choose location for calibre library"
msgstr ""
@@ -6954,7 +7089,7 @@ msgid "No location selected"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library.py:89
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:665
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:670
msgid "Bad location"
msgstr ""
@@ -7082,11 +7217,6 @@ msgstr ""
msgid "Date"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1162
-msgid "Format"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device_ui.py:55
msgid "Delete from device"
msgstr ""
@@ -7107,12 +7237,12 @@ msgid "Author sort"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:117
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:837
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:893
msgid "Invalid author name"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:118
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:838
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:894
msgid "Author names cannot contain & characters."
msgstr ""
@@ -7242,82 +7372,103 @@ msgstr ""
msgid "Stop &all non device jobs"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:57
-msgid "Title/Author"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:43
+msgid "&Copy to clipboard"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:48
+msgid "Show &details"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:49
+msgid "Hide &details"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:53
+msgid "Show detailed information about this error"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:92
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:525
+msgid "Copied"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:58
-msgid "Standard metadata"
+msgid "Title/Author"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:59
-msgid "Custom metadata"
+msgid "Standard metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:60
+msgid "Custom metadata"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:61
msgid "Search/Replace"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:64
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:65
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress.py:76
msgid "Working"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:256
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:361
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:257
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:384
msgid "Lower Case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:257
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:360
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:258
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:383
msgid "Upper Case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:258
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:363
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:259
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:386
msgid "Title Case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:259
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:364
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:260
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:387
msgid "Capitalize"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263
msgid "Character match"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:264
msgid "Regular Expression"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:266
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:267
msgid "Replace field"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:267
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:268
msgid "Prepend to field"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:268
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:269
msgid "Append to field"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:278
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:279
msgid "Editing meta information for %d books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:318
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:320
msgid ""
"Immediately make all changes without closing the dialog. This operation "
"cannot be canceled or undone"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:369
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:378
msgid "Book %d:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:384
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:393
msgid ""
"You can destroy your library using this feature. Changes are "
"permanent. There is no undo function. You are strongly encouraged to back up "
@@ -7325,7 +7476,7 @@ msgid ""
"character matching or regular expressions. "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:392
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:401
msgid ""
"In character mode, the field is searched for the entered search text. The "
"text is replaced by the specified replacement text everywhere it is found in "
@@ -7335,7 +7486,7 @@ msgid ""
"text will match both upper- and lower-case letters"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:403
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:412
msgid ""
"In regular expression mode, the search text is an arbitrary python-"
"compatible regular expression. The replacement text can contain "
@@ -7350,121 +7501,145 @@ msgid ""
"function."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:458
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:476
msgid "S/R TEMPLATE ERROR"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:578
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:596
msgid "You must specify a destination when source is a composite field"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:681
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:689
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:788
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:699
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:707
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:811
msgid "Search/replace invalid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:682
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:700
msgid ""
"Authors cannot be set to the empty string. Book title %s not processed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:690
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:708
msgid "Title cannot be set to the empty string. Book title %s not processed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:789
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:812
msgid "Search pattern is invalid: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:840
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:863
msgid ""
"Applying changes to %d books.\n"
"Phase {0} {1}%%."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:449
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:892
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:555
+msgid "Delete saved search/replace"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:893
+msgid "The selected saved search/replace will be deleted. Are you sure?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:910
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:918
+msgid "Save search/replace"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:911
+msgid "Search/replace name:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:919
+msgid ""
+"That saved search/replace already exists and will be overwritten. Are you "
+"sure?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:494
msgid "Edit Meta information"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:451
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:496
msgid "A&utomatically set author sort"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:452
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:497
msgid "&Swap title and author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:453
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:498
msgid "Author s&ort: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:454
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:499
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:455
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:500
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:424
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:774
msgid "&Rating:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:456
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:457
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:501
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:502
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:425
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:426
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:775
msgid "Rating of this book. 0-5 stars"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:458
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:503
msgid "No change"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:459
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:504
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:427
msgid " stars"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:461
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:506
msgid "Add ta&gs: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:463
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:464
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:508
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:509
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:431
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:432
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:138
msgid "Open Tag Editor"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:465
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:510
msgid "&Remove tags:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:466
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:511
msgid "Comma separated list of tags to remove from the books. "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:467
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:512
msgid "Check this box to remove all tags from the books."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:468
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:513
msgid "Remove &all"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:472
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:517
msgid "If checked, the series will be cleared"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:473
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:518
msgid "&Clear series"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:474
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:519
msgid ""
"If not checked, the series number for the books will be set to 1.\n"
"If checked, selected books will be automatically numbered, in the order\n"
@@ -7472,161 +7647,182 @@ msgid ""
"Book A will have series number 1 and Book B series number 2."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:478
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:523
msgid "&Automatically number books in this series"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:479
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:524
msgid ""
"Series will normally be renumbered from the highest number in the database\n"
"for that series. Checking this box will tell calibre to start numbering\n"
"from the value in the box"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:482
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:527
msgid "&Force numbers to start with:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:483
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:528
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:440
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:959
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:957
msgid "&Date:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:484
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:529
msgid "d MMM yyyy"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:486
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:491
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536
msgid "&Apply date"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:487
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532
msgid "&Published:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:489
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:444
msgid "Clear published date"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:492
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:537
msgid "Remove &format:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:493
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:538
msgid ""
"Force the title to be in title case. If both this and swap authors are "
"checked,\n"
"title and author are swapped before the title case is set"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:495
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:540
msgid "Change title to title &case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:496
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:541
msgid ""
"Remove stored conversion settings for the selected books.\n"
"\n"
"Future conversion of these books will use the default settings."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:499
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:544
msgid "Remove &stored conversion settings for the selected books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:500
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:545
msgid "Change &cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:501
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:546
msgid "&Generate default cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:502
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:547
msgid "&Remove cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:503
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:548
msgid "Set from &ebook file(s)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:504
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:549
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:465
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:380
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:509
msgid "&Basic metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:505
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:550
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:466
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:387
msgid "&Custom metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:506
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:551
+msgid "Load searc&h/replace:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:552
+msgid "Select saved search/replace to load."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:553
+msgid "Save current search/replace"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:554
+msgid "Sa&ve"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:556
+#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:64
+msgid "Delete"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:557
msgid "Search &field:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:507
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:558
msgid "The name of the field that you want to search"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:508
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:559
msgid "Search &mode:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:509
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:560
msgid ""
"Choose whether to use basic text matching or advanced regular expression "
"matching"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:510
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:561
msgid "Te&mplate:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:511
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:562
msgid "Enter a template to be used as the source for the search/replace"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:512
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:563
msgid "&Search for:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:513
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:564
msgid ""
"Enter the what you are looking for, either plain text or a regular "
"expression, depending on the mode"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:514
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:565
msgid ""
"Check this box if the search string must match exactly upper and lower case. "
"Uncheck it if case is to be ignored"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:515
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:566
msgid "Cas&e sensitive"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:516
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:567
msgid "&Replace with:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:517
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:568
msgid ""
"The replacement text. The matched search text will be replaced with this "
"string"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:518
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:569
msgid "&Apply function after replace:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:519
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:570
msgid ""
"Specify how the text is to be processed after matching and replacement. In "
"character mode, the entire\n"
@@ -7634,25 +7830,25 @@ msgid ""
"processed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:521
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:572
msgid "&Destination field:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:522
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:573
msgid ""
"The field that the text will be put into after all replacements.\n"
"If blank, the source field is used if the field is modifiable"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:524
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:575
msgid "M&ode:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:525
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:576
msgid "Specify how the text should be copied into the destination."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:526
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:577
msgid ""
"Specifies whether result items should be split into multiple values or\n"
"left as single values. This option has the most effect when the source field "
@@ -7660,41 +7856,41 @@ msgid ""
"not multiple and the destination field is multiple"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:529
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:580
msgid "Split &result"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:530
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:581
msgid "For multiple-valued fields, sho&w"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582
msgid "values starting a&t"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:583
msgid "with values separated b&y"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:533
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:584
msgid ""
"Used when displaying test results to separate values in multiple-valued "
"fields"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:585
msgid "Test text"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:586
msgid "Test result"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:587
msgid "Your test:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:537
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:588
msgid "&Search and replace"
msgstr ""
@@ -7730,115 +7926,115 @@ msgstr ""
msgid "Not a valid picture"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:214
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:685
msgid "Specify title and author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:213
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:215
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:686
msgid "You must specify a title and author before generating a cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:244
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:246
msgid "Downloading cover..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:260
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:265
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:271
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:276
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:267
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:273
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:278
msgid "Cannot fetch cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:261
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:272
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:277
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:274
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:279
msgid "Could not fetch cover.
"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:264
msgid "The download timed out."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:266
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:268
msgid "Could not find cover for this book. Try specifying the ISBN first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:278
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:280
msgid ""
"For the error message from each cover source, click Show details below."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:285
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:287
msgid "Bad cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:286
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:288
msgid "The cover is not a valid picture"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:305
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:307
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:515
msgid "Choose formats for "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:336
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:338
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:547
msgid "No permission"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:337
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:339
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:548
msgid "You do not have permission to read the following files:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:364
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:365
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:366
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:367
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:579
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:580
msgid "No format selected"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:376
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:378
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:591
msgid "Could not read metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:377
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:379
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:592
msgid "Could not read metadata from %s format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:449
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:451
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:225
msgid ""
" The green color indicates that the current author sort matches the current "
"author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:452
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:454
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:228
msgid ""
" The red color indicates that the current author sort does not match the "
"current author. No action is required if this is what you want."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:459
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:461
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:119
msgid ""
" The green color indicates that the current title sort matches the current "
"title"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:462
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:464
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:122
msgid ""
" The red color warns that the current title sort does not match the current "
"title. No action is required if this is what you want."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:468
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:470
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:47
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:102
#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:221
@@ -7846,14 +8042,14 @@ msgstr ""
msgid "Previous"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:471
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:479
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:473
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:481
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:347
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:351
msgid "Save changes and edit the metadata of %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:476
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:478
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:44
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:103
#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:211
@@ -7861,27 +8057,27 @@ msgstr ""
msgid "Next"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:680
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:685
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:899
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:682
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:687
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:897
msgid "This ISBN number is valid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:688
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:906
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:690
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:904
msgid "This ISBN number is invalid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:768
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:770
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:848
msgid "Tags changed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:769
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:771
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:849
msgid ""
"You have changed the tags. In order to use the tags editor, you must either "
-"discard or apply these changes"
+"discard or apply these changes. Apply changes?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:805
@@ -7980,7 +8176,7 @@ msgid "Remove unused series (Series that have no books)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:439
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:872
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:870
msgid "IS&BN:"
msgstr ""
@@ -7989,7 +8185,7 @@ msgid "dd MMM yyyy"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:442
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1010
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1008
msgid "Publishe&d:"
msgstr ""
@@ -8090,6 +8286,41 @@ msgstr ""
msgid "Aborting..."
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:23
+msgid ""
+"Restoring database from backups, do not interrupt, this will happen in three "
+"stages"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:25
+msgid "Restoring database"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:79
+msgid ""
+"Your list of books, with all their metadata is stored in a single file, "
+"called a database. In addition, metadata for each individual book is stored "
+"in that books' folder, as a backup.This operation will rebuild the "
+"database from the individual book metadata. This is useful if the database "
+"has been corrupted and you get a blank list of books. Note that restoring "
+"only restores books, not any settings stored in the database, or any custom "
+"recipes.
Do you want to restore the database?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:102
+msgid "Restoring database failed, click Show details to see details"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:107
+msgid ""
+"Restoring the database succeeded with some warnings click Show details to "
+"see the details."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:112
+msgid "Restoring database was successful"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:55
msgid ""
"The current saved search will be permanently deleted. Are you sure?"
@@ -8176,11 +8407,11 @@ msgstr ""
msgid "Download all scheduled new sources"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:348
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:353
msgid "No internet connection"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:349
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:354
msgid "Cannot download news as no internet connection is active"
msgstr ""
@@ -8422,63 +8653,78 @@ msgstr ""
msgid "Publishers"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:124
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:128
msgid " (not on any book)"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:197
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:151
+msgid "Name already used"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:176
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:198
+msgid "That name is already used, perhaps with different case."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:211
msgid ""
"The current tag category will be permanently deleted. Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:158
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:166
msgid "User Categories Editor"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:159
-msgid "A&vailable items"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:160
-msgid "Apply tags to current tag category"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:162
-msgid "A&pplied items"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:163
-msgid "Unapply (remove) tag from current tag category"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:165
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:167
msgid "Category name: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:166
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:168
msgid "Select a category to edit"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:167
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:169
msgid "Delete this selected tag category"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:169
-msgid "Enter a new category name. Select the kind before adding it."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:170
-msgid "Add the new category"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:171
+msgid "Enter a category name, then use the add button or the rename button"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:172
+msgid "Add a new category"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:174
+msgid "Rename the current category to the what is in the box"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:176
msgid "Category filter: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:177
msgid "Select the content kind of the new category"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:178
+msgid "A&vailable items"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:179
+msgid "Apply tags to current tag category"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:181
+msgid "A&pplied items"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:182
+msgid "Unapply (remove) tag from current tag category"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor.py:70
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:105
msgid "Are your sure?"
@@ -8535,12 +8781,12 @@ msgid "%s (was %s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:74
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:827
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:883
msgid "Item is blank"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:75
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:828
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:884
msgid "An item cannot be set to nothing. Delete it instead."
msgstr ""
@@ -8609,7 +8855,7 @@ msgid "Send test mail from %s to:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:58
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:120
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:123
msgid "&Test"
msgstr ""
@@ -8841,7 +9087,7 @@ msgid "Attached, you will find the e-book"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:247
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:117
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:186
msgid "by"
msgstr ""
@@ -8874,7 +9120,7 @@ msgstr ""
msgid "Sent news to"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:112
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:115
msgid ""
"\n"
@@ -8898,64 +9144,64 @@ msgid ""
"metadata entries are documented in tooltips.
"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:119
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:122
msgid "Regular &expression"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:121
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:124
msgid "File &name:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:123
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:126
msgid "Title:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:124
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:127
msgid "Regular expression (?P<title>)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:125
#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:128
#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:131
#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:134
#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:137
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:93
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:97
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:102
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:107
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:109
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:140
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:106
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:110
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:115
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:120
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:122
msgid "No match"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:126
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:129
msgid "Authors:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:127
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:130
msgid "Regular expression (?P)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:129
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:132
msgid "Series:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:130
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:133
msgid "Regular expression (?P)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:132
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:135
msgid "Series index:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:133
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:136
msgid "Regular expression (?P)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:135
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:138
msgid "ISBN:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:136
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:139
msgid "Regular expression (?P)"
msgstr ""
@@ -9069,6 +9315,14 @@ msgstr ""
msgid " - Jobs"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:424
+msgid "Do you really want to stop the selected job?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:430
+msgid "Do you really want to stop all non-device jobs?"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:53
msgid "Eject this device"
msgstr ""
@@ -9082,7 +9336,7 @@ msgid "Show books in the main memory of the device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:67
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:847
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:901
msgid "Card A"
msgstr ""
@@ -9091,7 +9345,7 @@ msgid "Show books in storage card A"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:69
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:849
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:903
msgid "Card B"
msgstr ""
@@ -9180,7 +9434,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:738
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1282
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:538
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:589
msgid "The lookup/search name is \"{0}\""
msgstr ""
@@ -9352,7 +9606,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:61
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:673
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:678
msgid "Calibre Library"
msgstr ""
@@ -9386,7 +9640,7 @@ msgid "The database repair failed. Starting with a new empty library."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:197
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:229
msgid "Bad database location"
msgstr ""
@@ -9394,64 +9648,64 @@ msgstr ""
msgid "Bad database location %r. calibre will now quit."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:211
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:210
msgid "Corrupted database"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:211
msgid ""
"Your calibre database appears to be corrupted. Do you want calibre to try "
"and repair it automatically? If you say No, a new empty calibre library will "
"be created."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:218
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:217
msgid ""
"Repairing database. This can take a very long time for a large collection"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:231
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:230
msgid ""
"Bad database location %r. Will start with a new, empty calibre library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:241
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:240
msgid "Starting %s: Loading books..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:321
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:320
msgid "If you are sure it is not running"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:323
-msgid "Cannot Start "
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:324
-msgid "%s is already running."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:327
msgid "may be running in the system tray, in the"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:329
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:325
msgid "upper right region of the screen."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:331
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:327
msgid "lower right region of the screen."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:334
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:330
msgid "try rebooting your computer."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:336
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:348
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:332
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:346
msgid "try deleting the file"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:335
+msgid "Cannot Start "
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:336
+msgid "%s is already running."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:20
msgid ""
"Redirect console output to a dialog window (both stdout and stderr). Useful "
@@ -9467,8 +9721,7 @@ msgid "&Quit"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:90
-#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:109
-msgid "ERROR: Unhandled exception"
+msgid "Unhandled exception"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:111
@@ -9505,11 +9758,11 @@ msgid ""
"
They can be any wordsor phrases, separated by commas."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:913
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:911
msgid "&Publisher:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:978
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:976
msgid "Clear date"
msgstr ""
@@ -10300,101 +10553,117 @@ msgstr ""
msgid "Delete plugboard"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:110
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:179
msgid "%(plugin_type)s %(plugins)s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:111
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:180
msgid "plugins"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:120
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:189
msgid ""
"\n"
"Customization: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:162
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:218
+msgid "Search for plugin"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:226
+msgid "No matches"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:227
+msgid "Could not find any matching plugins"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:268
msgid "Add plugin"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:170
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:276
msgid ""
"Installing plugins is a security risk. Plugins can contain a "
"virus/malware. Only install it if you got it from a trusted source. Are you "
"sure you want to proceed?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:180
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:231
-msgid "Success"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:181
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:287
msgid ""
"Plugin {0} successfully installed under {1} plugins. You may "
"have to restart calibre for the plugin to take effect."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:192
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:295
msgid "No valid plugin path"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:193
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:296
msgid "%s is not a valid plugin path"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:202
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:305
msgid "Select an actual plugin under %s to customize"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:311
msgid "Plugin cannot be disabled"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:209
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:312
msgid "The plugin: %s cannot be disabled"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:219
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:322
msgid "Plugin not customizable"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:220
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:323
msgid "Plugin: %s does not need customization"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:226
-msgid "Plugin {0} successfully removed"
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:329
+msgid "Must restart"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:234
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:330
+msgid ""
+"You must restart calibre before you can configure the %s plugin"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:335
+msgid "Plugin {0} successfully removed"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:343
msgid "Cannot remove builtin plugin"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:235
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:344
msgid " cannot be removed. It is a builtin plugin. Try disabling it instead."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:59
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:87
msgid ""
"Here you can customize the behavior of Calibre by controlling what plugins "
"it uses."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:60
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:90
msgid "Enable/&Disable plugin"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:61
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:91
msgid "&Customize plugin"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:62
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:92
msgid "&Remove plugin"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:63
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:93
msgid "&Add a new plugin"
msgstr ""
@@ -10510,7 +10779,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:75
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:365
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:381
msgid "Failed to start content server"
msgstr ""
@@ -10691,10 +10960,6 @@ msgstr ""
msgid "Function not defined"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:151
-msgid "Name already used"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:155
msgid "Argument count must be -1 or greater than zero"
msgstr ""
@@ -10837,7 +11102,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:94
#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:271
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:593
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:616
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:277
msgid "Search"
msgstr ""
@@ -10930,72 +11195,88 @@ msgstr ""
msgid "&Alternate shortcut:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:251
-msgid "Rename '%s'"
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:273
+msgid "Rename %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:255
-msgid "Edit sort for '%s'"
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:277
+msgid "Edit sort for %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:260
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:282
+msgid "Search for %s"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:287
+msgid "Search for everything but %s"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:293
msgid "Hide category %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:296
msgid "Show category"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:272
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:276
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:304
+msgid "Search for books in category %s"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:308
+msgid "Search for books not in category %s"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:315
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:319
msgid "Manage %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:279
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:322
msgid "Manage Saved Searches"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:286
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:290
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:329
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:333
msgid "Manage User Categories"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:297
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:340
msgid "Show all categories"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:300
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:343
msgid "Change sub-categorization scheme"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:625
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:677
msgid ""
"Changing the authors for several books can take a while. Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:630
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:682
msgid ""
"Changing the metadata for that many books can take a while. Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:687
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:325
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:743
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:350
msgid "Searches"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:842
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:898
msgid "Duplicate search name"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:843
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:899
msgid "The saved search name %s is already used."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1232
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1313
msgid "Find item in tag browser"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1235
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1316
msgid ""
"Search for items. This is a \"contains\" search; items containing the\n"
"text anywhere in the name will be found. You can limit the search\n"
@@ -11005,60 +11286,60 @@ msgid ""
"containing the text \"foo\""
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1244
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1325
msgid "ALT+f"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1248
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1329
msgid "F&ind"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1249
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1330
msgid "Find the first/next matching item"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1256
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1337
msgid "Collapse all categories"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1277
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1358
msgid "No More Matches. Click Find again to go to first match"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1290
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1371
msgid "Sort by name"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1290
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1371
msgid "Sort by popularity"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1291
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1372
msgid "Sort by average rating"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1294
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1375
msgid "Set the sort order for entries in the Tag Browser"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1300
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1381
msgid "Match all"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1300
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1381
msgid "Match any"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1305
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1386
msgid ""
"When selecting multiple entries in the Tag Browser match any or all of them"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1309
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1390
msgid "Manage &user categories"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1312
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1393
msgid "Add your own categories to the Tag Browser"
msgstr ""
@@ -11104,68 +11385,68 @@ msgid ""
"reconvert them?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:182
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:192
msgid "&Restore"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:184
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:194
msgid "&Donate to support calibre"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:188
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:198
msgid "&Eject connected device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:229
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:243
msgid "Calibre Quick Start Guide"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:291
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:305
msgid "Debug mode"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:292
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:306
msgid ""
"You have started calibre in debug mode. After you quit calibre, the debug "
"log will be available in the file: %s
The log will be displayed "
"automatically."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:477
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:494
msgid "Conversion Error"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:500
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:517
msgid "Recipe Disabled"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:516
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:533
msgid "Failed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:553
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:570
msgid ""
"is the result of the efforts of many volunteers from all over the world. If "
"you find it useful, please consider donating to support its development. "
"Your donation helps keep calibre development going."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:579
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:596
msgid "There are active jobs. Are you sure you want to quit?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:582
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:599
msgid ""
" is communicating with the device!
\n"
" Quitting may cause corruption on the device.
\n"
" Are you sure you want to quit?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:586
-msgid "WARNING: Active jobs"
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:603
+msgid "Active jobs"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:658
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:669
msgid ""
"will keep running in the system tray. To close it, choose Quit in the "
"context menu of the system tray."
@@ -11225,10 +11506,6 @@ msgstr ""
msgid "Edit"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:64
-msgid "Delete"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:65
msgid "Reset"
msgstr ""
@@ -11689,96 +11966,82 @@ msgstr ""
msgid "Print eBook"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:257
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:279
msgid "Copy Image"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:258
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:280
msgid "Paste Image"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:359
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:382
msgid "Change Case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:362
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:385
msgid "Swap Case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:902
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:925
msgid "Drag to resize"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:937
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:960
msgid "Show"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:944
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:967
msgid "Hide"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:981
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:1004
msgid "Toggle"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:411
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:438
msgid ""
-"If you use the WordPlayer e-book app on your Android phone, you can access "
-"your calibre book collection directly on the device. To do this you have to "
-"turn on the content server."
+"Choose you e-book device. If your device is not in the list, choose a \"%s\" "
+"device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:415
-msgid ""
-"Remember to leave calibre running as the server only runs as long as calibre "
-"is running."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:417
-msgid ""
-"You have to add the URL http://myhostname:8080 as your calibre library in "
-"WordPlayer. Here myhostname should be the fully qualified hostname or the IP "
-"address of the computer calibre is running on."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:494
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:499
msgid "Moving library..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:510
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:511
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:515
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:516
msgid "Failed to move library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:565
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:570
msgid "Invalid database"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:566
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:571
msgid ""
"
An invalid library already exists at %s, delete it before trying to move "
"the existing library.
Error: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:577
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:582
msgid "Could not move library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:652
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:657
msgid "Select location for books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:666
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:671
msgid ""
"You must choose an empty folder for the calibre library. %s is not empty."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:740
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:745
msgid "welcome wizard"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:53
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:54
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:55
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:47
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:47
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:55
@@ -11786,7 +12049,7 @@ msgstr ""
msgid "Welcome to calibre"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:55
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:56
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:56
@@ -11794,12 +12057,6 @@ msgstr ""
msgid "The one stop solution to all your e-book needs."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:56
-msgid ""
-"Choose your book reader. This will set the conversion options to produce "
-"books optimized for your device."
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:57
msgid "&Manufacturers"
msgstr ""
@@ -12040,64 +12297,65 @@ msgstr ""
msgid "Turn on the &content server"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:299
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:306
msgid "today"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:302
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:309
msgid "yesterday"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:305
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:312
msgid "thismonth"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:308
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:309
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:315
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:316
msgid "daysago"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:498
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:508
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:527
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:537
msgid "unchecked"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:498
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:508
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:527
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:537
#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:185
msgid "no"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:501
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:511
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:530
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:540
msgid "checked"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:501
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:511
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:530
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:540
#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:185
msgid "yes"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:505
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:534
msgid "blank"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:505
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:534
msgid "empty"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:53
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:52
msgid ""
"The fields to output when cataloging books in the database. Should be a "
"comma-separated list of fields.\n"
"Available fields: %s,\n"
"plus user-created custom fields.\n"
+"Example: %s=title,authors,tags\n"
"Default: '%%default'\n"
"Applies to: CSV, XML output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:64
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:65
msgid ""
"Output field to sort on.\n"
"Available fields: author_sort, id, rating, size, timestamp, title.\n"
@@ -12105,16 +12363,17 @@ msgid ""
"Applies to: CSV, XML output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:231
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:232
msgid ""
"The fields to output when cataloging books in the database. Should be a "
"comma-separated list of fields.\n"
"Available fields: %s.\n"
+"Example: %s=title,authors,tags\n"
"Default: '%%default'\n"
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:241
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:244
msgid ""
"Output field to sort on.\n"
"Available fields: author_sort, id, rating, size, timestamp, title.\n"
@@ -12122,7 +12381,7 @@ msgid ""
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:250
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:253
msgid ""
"Create a citation for BibTeX entries.\n"
"Boolean value: True, False\n"
@@ -12130,7 +12389,15 @@ msgid ""
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:259
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:262
+msgid ""
+"Create a file entry if formats is selected for BibTeX entries.\n"
+"Boolean value: True, False\n"
+"Default: '%default'\n"
+"Applies to: BIBTEX output format"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:271
msgid ""
"The template for citation creation from database fields.\n"
" Should be a template with {} enclosed fields.\n"
@@ -12139,7 +12406,7 @@ msgid ""
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:269
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:281
msgid ""
"BibTeX file encoding output.\n"
"Available types: utf8, cp1252, ascii.\n"
@@ -12147,7 +12414,7 @@ msgid ""
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:278
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:290
msgid ""
"BibTeX file encoding flag.\n"
"Available types: strict, replace, ignore, backslashreplace.\n"
@@ -12155,7 +12422,7 @@ msgid ""
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:287
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:299
msgid ""
"Entry type for BibTeX catalog.\n"
"Available types: book, misc, mixed.\n"
@@ -12163,14 +12430,14 @@ msgid ""
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:572
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:598
msgid ""
"Title of generated catalog used as title in metadata.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:579
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:605
msgid ""
"Save the output from different stages of the conversion pipeline to the "
"specified directory. Useful if you are unsure at which stage of the "
@@ -12179,7 +12446,7 @@ msgid ""
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:589
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:615
msgid ""
"field:pattern specifying custom field/contents indicating book should be "
"excluded.\n"
@@ -12187,14 +12454,14 @@ msgid ""
"Applies to ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:596
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:622
msgid ""
"Regex describing tags to exclude as genres.\n"
"Default: '%default' excludes bracketed tags, e.g. '[]'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:602
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:628
msgid ""
"Comma-separated list of tag words indicating book should be excluded from "
"output.For example: 'skip' will match 'skip this book' and 'Skip will like "
@@ -12202,56 +12469,56 @@ msgid ""
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:610
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:636
msgid ""
"Include 'Authors' section in catalog.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:617
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:643
msgid ""
"Include 'Descriptions' section in catalog.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:624
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:650
msgid ""
"Include 'Genres' section in catalog.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:631
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:657
msgid ""
"Include 'Titles' section in catalog.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:638
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:664
msgid ""
"Include 'Series' section in catalog.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:645
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:671
msgid ""
"Include 'Recently Added' section in catalog.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:652
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:678
msgid ""
"Custom field containing note text to insert in Description header.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:659
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:685
msgid ""
":[before|after]:[True|False] specifying:\n"
" Custom field containing notes to merge with Comments\n"
@@ -12261,7 +12528,7 @@ msgid ""
"Applies to ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:669
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:695
msgid ""
"Specifies the output profile. In some cases, an output profile is required "
"to optimize the catalog for the device. For example, 'kindle' or "
@@ -12271,14 +12538,14 @@ msgid ""
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:676
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:702
msgid ""
"field:pattern indicating book has been read.\n"
"Default: '%default'\n"
"Applies to ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:682
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:708
msgid ""
"Size hint (in inches) for book covers in catalog.\n"
"Range: 1.0 - 2.0\n"
@@ -12286,22 +12553,22 @@ msgid ""
"Applies to ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:690
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:716
msgid ""
"Tag indicating book to be displayed as wishlist item.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1373
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1399
msgid "No enabled genres found to catalog.\n"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1377
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1403
msgid "No books available to catalog"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1451
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1477
msgid ""
"\n"
"Inconsistent Author Sort values for Author '{0}':\n"
@@ -12313,17 +12580,17 @@ msgid ""
"then rebuild the catalog.\n"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1652
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1678
msgid ""
"No books found to catalog.\n"
"Check 'Excluded books' criteria in E-book options.\n"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1654
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1680
msgid "No books available to include in catalog"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:4975
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:5006
msgid ""
"\n"
"*** Adding 'By Authors' Section required for MOBI output ***"
@@ -12358,11 +12625,11 @@ msgid "Unknown files in books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/check_library.py:33
-msgid "Missing covers in books"
+msgid "Missing covers files"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/check_library.py:34
-msgid "Extra covers in books"
+msgid "Cover files not in database"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/check_library.py:35
@@ -12883,34 +13150,22 @@ msgstr ""
msgid "%sAverage rating is %3.1f"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:845
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:899
msgid "Main"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2574
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2631
msgid "Migrating old database to ebook library in %s
"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2603
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2660
msgid "Copying %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2620
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2677
msgid "Compacting database"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2745
-msgid "Checking SQL integrity..."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2783
-msgid "Checking for missing files."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2811
-msgid "Checked id"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:140
msgid "Ratings"
msgstr ""
@@ -13670,42 +13925,86 @@ msgid "English (Pakistan)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:108
-msgid "English (Israel)"
+msgid "English (Croatia)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:109
-msgid "English (Singapore)"
+msgid "English (Israel)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:110
-msgid "English (Yemen)"
+msgid "English (Singapore)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:111
-msgid "English (Ireland)"
+msgid "English (Yemen)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:112
-msgid "English (China)"
+msgid "English (Ireland)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:113
-msgid "Spanish (Paraguay)"
+msgid "English (China)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:114
-msgid "German (AT)"
+msgid "Spanish (Paraguay)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:115
-msgid "French (BE)"
+msgid "Spanish (Uruguay)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:116
-msgid "Dutch (NL)"
+msgid "Spanish (Argentina)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:117
+msgid "Spanish (Mexico)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:118
+msgid "Spanish (Cuba)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:119
+msgid "Spanish (Chile)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:120
+msgid "Spanish (Ecuador)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:121
+msgid "Spanish (Honduras)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:122
+msgid "Spanish (Venezuela)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:123
+msgid "Spanish (Bolivia)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:124
+msgid "Spanish (Nicaragua)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:125
+msgid "German (AT)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:126
+msgid "French (BE)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:127
+msgid "Dutch (NL)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:128
msgid "Dutch (BE)"
msgstr ""
@@ -13713,6 +14012,10 @@ msgstr ""
msgid "Choose theme (needs restart)"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:109
+msgid "ERROR: Unhandled exception"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:188
msgid "No interpreter"
msgstr ""
diff --git a/src/calibre/translations/ar.po b/src/calibre/translations/ar.po
index 65f1dbfd3d..e5d1a682a1 100644
--- a/src/calibre/translations/ar.po
+++ b/src/calibre/translations/ar.po
@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: calibre\n"
"Report-Msgid-Bugs-To: FULL NAME \n"
-"POT-Creation-Date: 2011-01-21 23:34+0000\n"
+"POT-Creation-Date: 2011-01-30 19:51+0000\n"
"PO-Revision-Date: 2011-01-03 01:54+0000\n"
"Last-Translator: Amr Hesham \n"
"Language-Team: Arabic \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-01-23 04:40+0000\n"
+"X-Launchpad-Export-Date: 2011-01-31 04:38+0000\n"
"X-Generator: Launchpad (build 12177)\n"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:121
@@ -101,7 +101,7 @@ msgstr "لا يفعل شيءً"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1894
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1896
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:24
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:235
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:236
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:31
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:32
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:73
@@ -111,7 +111,7 @@ msgstr "لا يفعل شيءً"
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:36
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:61
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:54
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:358
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:365
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:36
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:64
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:66
@@ -127,6 +127,7 @@ msgstr "لا يفعل شيءً"
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:91
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:101
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/snb.py:16
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:56
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:14
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:42
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:68
@@ -138,9 +139,9 @@ msgstr "لا يفعل شيءً"
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:878
#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:49
#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:51
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:952
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:957
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1023
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:958
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:963
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1029
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:143
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:150
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:64
@@ -170,8 +171,8 @@ msgstr "لا يفعل شيءً"
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:101
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:329
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:331
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:364
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:371
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:299
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:306
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:100
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:331
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:334
@@ -181,14 +182,14 @@ msgstr "لا يفعل شيءً"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:120
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:145
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:147
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1050
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1053
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1064
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1067
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:55
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:67
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:145
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:185
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:724
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:726
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:193
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:236
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:245
@@ -203,18 +204,18 @@ msgstr "لا يفعل شيءً"
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:191
#: /home/kovid/work/calibre/src/calibre/library/cli.py:215
#: /home/kovid/work/calibre/src/calibre/library/database.py:914
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:402
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:414
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1474
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1575
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2415
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2417
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2548
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:432
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:444
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1529
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1632
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2472
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2474
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2605
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:229
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:158
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:161
#: /home/kovid/work/calibre/src/calibre/library/server/xml.py:79
-#: /home/kovid/work/calibre/src/calibre/utils/localization.py:118
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:129
#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:46
#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:64
#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:78
@@ -489,7 +490,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:889
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:163
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:269
msgid "Plugins"
msgstr "الملحقات"
@@ -570,57 +571,57 @@ msgid "This profile is intended for the SONY PRS-900."
msgstr "ملف التعريف هذا هو المقصود لجهاز سوني PRS 900."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:90
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:522
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:538
msgid "This profile is intended for the Microsoft Reader."
msgstr "هذا الطور يستخدم مع Microsoft Reader"
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:101
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:533
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:549
msgid "This profile is intended for the Mobipocket books."
msgstr "ملف التعريف هذا يستخدم مع كتب Mobipocket ."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:114
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:546
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:562
msgid "This profile is intended for the Hanlin V3 and its clones."
msgstr "ملف التعريف هذا يستخدم مع Hanlin V3 وأمثاله."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:126
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:558
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:574
msgid "This profile is intended for the Hanlin V5 and its clones."
msgstr "ملف التعريف هذا يستخدم مع Hanlin V5 وأمثاله."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:136
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:566
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:582
msgid "This profile is intended for the Cybook G3."
msgstr "ملف التعريف هذا يستخدم مع Cybook G3"
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:149
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:579
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:596
msgid "This profile is intended for the Cybook Opus."
msgstr "ملف التعريف هذا يستخدم مع Cybook Opus ."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:161
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:592
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:609
msgid "This profile is intended for the Amazon Kindle."
msgstr "ملف التعريف هذا يستخدم مع Amazon Kindle ."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:173
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:642
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:659
msgid "This profile is intended for the Irex Illiad."
msgstr "ملف التعريف هذا يستخدم مع Irex Illiad ."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:185
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:655
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:672
msgid "This profile is intended for the IRex Digital Reader 1000."
msgstr "ملف التعريف هذا يستخدم مع IRex Digital Reader 1000 ."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:198
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:669
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:686
msgid "This profile is intended for the IRex Digital Reader 800."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:210
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:683
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:700
msgid "This profile is intended for the B&N Nook."
msgstr "ملف التعريف هذا يستخدم مع B&N Nook ."
@@ -661,10 +662,18 @@ msgid "This profile is intended for the SONY PRS-300."
msgstr "ملف التعريف هذا يستخدم مع سوني PRS-300 ."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:502
+msgid "Suitable for use with any e-ink device"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:509
+msgid "Suitable for use with any large screen e-ink device"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:518
msgid "This profile is intended for the 5-inch JetBook."
msgstr "ملف التعريف هذا يستخدم مع الخمسة بوصة JetBook ."
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:511
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:527
msgid ""
"This profile is intended for the SONY PRS line. The 500/505/700 etc, in "
"landscape mode. Mainly useful for comics."
@@ -672,15 +681,15 @@ msgstr ""
"ملف التعريف هذا يستخدم مع سوني خط إنتاج PRS . الـ500/505/700 الخ ، في وضع "
"أفقي.غالباً مفيد للكاريكاتيرات."
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:618
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:635
msgid "This profile is intended for the Amazon Kindle DX."
msgstr "ملف التعريف هذا يستخدم مع Amazon Kindle DX"
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:695
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:712
msgid "This profile is intended for the B&N Nook Color."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:706
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:723
msgid "This profile is intended for the Sanda Bambook."
msgstr ""
@@ -758,13 +767,13 @@ msgstr ""
msgid "Communicate with Android phones."
msgstr "التواصل مع هواتف أندرويد ."
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:61
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:62
msgid ""
"Comma separated list of directories to send e-books to on the device. The "
"first one that exists will be used"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:107
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:108
msgid "Communicate with S60 phones."
msgstr "تواصل معا هواتف S60."
@@ -833,19 +842,19 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:886
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:892
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:922
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:264
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:219
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:232
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2279
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:262
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:244
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:257
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2336
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:150
msgid "News"
msgstr "الأخبار"
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2554
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:65
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:599
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2242
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2260
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:625
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2299
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2317
msgid "Catalog"
msgstr "الفهرس"
@@ -957,7 +966,7 @@ msgid "Communicate with the Blackberry smart phone."
msgstr "تواصل معا جهاز بلاك برري"
#: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:14
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:254
#: /home/kovid/work/calibre/src/calibre/devices/nuut2/driver.py:18
#: /home/kovid/work/calibre/src/calibre/devices/prs500/driver.py:90
msgid "Kovid Goyal"
@@ -971,23 +980,23 @@ msgstr ""
msgid "Communicate with the Cybook Orizon eBook reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:24
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:25
msgid "Communicate with the EB600 eBook reader."
msgstr "التواصل مع القارئ الكتاب الاليكترونى EB600 ."
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:193
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:194
msgid "Communicate with the Astak Mentor EB600"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:216
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:217
msgid "Communicate with the PocketBook 301 reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:233
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:234
msgid "Communicate with the PocketBook 602/603/902/903 reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:252
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253
msgid "Communicate with the PocketBook 701"
msgstr ""
@@ -1116,7 +1125,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:446
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:292
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:295
msgid "Not Implemented"
msgstr "غير مطبق"
@@ -1995,27 +2004,27 @@ msgstr ""
msgid "Replacement to replace the text found with sr3-search."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:671
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:673
msgid "Could not find an ebook inside the archive"
msgstr "لم يتمكّن من الحصول على كتاب داخل الأرشيف"
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:729
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:731
msgid "Values of series index and rating must be numbers. Ignoring"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:736
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:738
msgid "Failed to parse date/time"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:891
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:893
msgid "Converting input to HTML..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:918
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:920
msgid "Running transforms on ebook..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1006
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1008
msgid "Creating"
msgstr ""
@@ -2493,13 +2502,13 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:544
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:62
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:495
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:546
msgid "No"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:544
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:62
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:495
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:546
msgid "Yes"
msgstr ""
@@ -2694,7 +2703,7 @@ msgid "Download covers from openlibrary.org"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:108
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:137
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:140
msgid "ISBN: %s not found"
msgstr ""
@@ -2702,28 +2711,32 @@ msgstr ""
msgid "Download covers from librarything.com"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:129
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:82
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:130
msgid "LibraryThing.com timed out. Try again later."
msgstr "LibraryThing.com لم يرد. حاول لاحقاً."
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:136
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:89
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:139
msgid ""
"Could not fetch cover as server is experiencing high load. Please try again "
"later."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:140
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:93
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:143
msgid "LibraryThing.com server error. Try again later."
msgstr "خطأ في خادم LibraryThing.com. حاول لاحقاً."
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:226
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:177
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:270
+msgid ""
+"To use librarything.com you must sign up for a %sfree account%s and enter "
+"your username and password separated by a : below."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:240
msgid "Download covers from Douban.com"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:235
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:249
msgid "Douban.com API timed out. Try again later."
msgstr ""
@@ -2774,7 +2787,7 @@ msgid "Downloads social metadata from amazon.com"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:254
-msgid "Downloads series/tags/rating information from librarything.com"
+msgid "Downloads series/covers/rating information from librarything.com"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:25
@@ -2884,11 +2897,7 @@ msgstr "العنوان الذي تريد البحث عنه."
msgid "The publisher of the book to search for."
msgstr "الناشر الذي تريد البحث عنه."
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:90
-msgid " not found."
-msgstr " لم يوجد."
-
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:100
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:75
msgid ""
"\n"
"%prog [options] ISBN\n"
@@ -2961,10 +2970,14 @@ msgid "Cover saved to file "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1308
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1442
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1448
msgid "Cover"
msgstr "الغلاف"
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:14
+msgid "Metadata source"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:22
msgid "Modify images to meet Palm device size limitations."
msgstr ""
@@ -3007,70 +3020,70 @@ msgstr ""
msgid "This is an Amazon Topaz book. It cannot be processed."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1443
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1449
msgid "Title Page"
msgstr "صقحة العنوان"
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1444
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1450
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:54
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:199
msgid "Table of Contents"
msgstr "المحتويات"
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1445
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1451
msgid "Index"
msgstr "الفهرس"
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1446
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1452
msgid "Glossary"
msgstr "المسرد"
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1447
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1453
msgid "Acknowledgements"
msgstr "شكر وتقدير"
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1448
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1454
msgid "Bibliography"
msgstr "ببليوغرافيا"
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1449
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1455
msgid "Colophon"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1450
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1456
msgid "Copyright"
msgstr "حقوق المؤلف"
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1451
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1457
msgid "Dedication"
msgstr "الإهداء"
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1452
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1458
msgid "Epigraph"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1453
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1459
msgid "Foreword"
msgstr "افتتاحية"
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1454
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1460
msgid "List of Illustrations"
msgstr "قائمة الرسوم"
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1455
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1461
msgid "List of Tables"
msgstr "قائمة الجداول"
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1456
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1462
msgid "Notes"
msgstr "الملاحظات"
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1457
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1463
msgid "Preface"
msgstr "افتتاحية"
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1458
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1464
msgid "Main Text"
msgstr "النصّ الرئيسي"
@@ -3079,8 +3092,8 @@ msgid "%s format books are not supported"
msgstr "الكتب بتهيئة %s ليست مدعومة"
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/cover.py:98
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:218
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:220
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:691
msgid "Book %s of %s"
msgstr ""
@@ -3138,7 +3151,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/output.py:32
#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:37
#: /home/kovid/work/calibre/src/calibre/ebooks/rb/output.py:21
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:35
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:34
msgid "Add Table of Contents to beginning of the book."
msgstr ""
@@ -3357,7 +3370,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:49
msgid ""
"Preserve the aspect ratio of the cover, instead of stretching it to fill the "
-"ull first page of the generated pdf."
+"full first page of the generated pdf."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:55
@@ -3394,13 +3407,13 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:25
#: /home/kovid/work/calibre/src/calibre/ebooks/tcr/output.py:23
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:31
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:30
msgid ""
"Specify the character encoding of the output document. The default is utf-8."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:29
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:38
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:37
msgid ""
"The maximum number of characters per line. This splits on the first space "
"before the specified value. If no space is found the line will be broken at "
@@ -3478,35 +3491,39 @@ msgstr ""
msgid "Do not insert a Table of Contents into the output text."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:25
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:24
msgid ""
"Type of newline to use. Options are %s. Default is 'system'. Use 'old_mac' "
"for compatibility with Mac OS 9 and earlier. For Mac OS X use 'unix'. "
"'system' will default to the newline type used by this OS."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:45
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:44
msgid ""
"Force splitting on the max-line-length value when no space is present. Also "
"allows max-line-length to be below the minimum"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:49
-msgid "Produce Markdown formatted text."
+msgid ""
+"Formatting used within the document.\n"
+"* plain: Produce plain text.\n"
+"* markdown: Produce Markdown formatted text.\n"
+"* textile: Produce Textile formatted text."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:52
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:55
msgid ""
"Do not remove links within the document. This is only useful when paired "
-"with the markdown-format option because links are always removed with plain "
-"text output."
+"with a txt-output-formatting option that is not none because links are "
+"always removed with plain text output."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:57
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:60
msgid ""
"Do not remove image references within the document. This is only useful when "
-"paired with the markdown-format option because image references are always "
-"removed with plain text output."
+"paired with a txt-output-formatting option that is not none because links "
+"are always removed with plain text output."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:70
@@ -3587,58 +3604,44 @@ msgstr ""
msgid "Default action to perform when send to device button is clicked"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:124
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:126
msgid "Maximum number of waiting worker processes"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:126
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:128
msgid "Download social metadata (tags/rating/etc.)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:128
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:130
msgid "Overwrite author and title with new metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:130
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:132
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:101
msgid "Automatically download the cover, if available"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:132
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:134
msgid "Limit max simultaneous jobs to number of CPUs"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:134
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:136
msgid "tag browser categories not to display"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:136
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:138
msgid "The layout of the user interface"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:138
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:140
msgid "Show the average rating per item indication in the tag browser"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:140
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:142
msgid "Disable UI animations"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:188
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:520
-msgid "Copied"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:222
-msgid "Copy to Clipboard"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:222
-#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:96
-msgid "Copy"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:475
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:410
msgid "Choose Files"
msgstr ""
@@ -3682,89 +3685,89 @@ msgstr ""
msgid "Add from ISBN"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:233
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:236
msgid "Uploading books to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:189
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:306
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:192
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:308
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:517
msgid "Books"
msgstr "كتب"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:190
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:193
msgid "EPUB Books"
msgstr "كتب EPUB"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:191
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:194
msgid "LRF Books"
msgstr "كتب LRF"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:192
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:195
msgid "HTML Books"
msgstr "كتب HTML"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:193
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:196
msgid "LIT Books"
msgstr "كتب LIT"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:194
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:197
msgid "MOBI Books"
msgstr "كتب MOBI"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:195
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:198
msgid "Topaz books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:196
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:199
msgid "Text books"
msgstr "كتب نصّية"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:197
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:200
msgid "PDF Books"
msgstr "كتب PDF"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:198
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:201
msgid "SNB Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:199
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:202
msgid "Comics"
msgstr "الرسومات"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:200
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:203
msgid "Archives"
msgstr "أرشيفات"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:204
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:207
msgid "Supported books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:246
msgid "Merged some books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:244
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:247
msgid ""
"Some duplicates were found and merged into the following existing books:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:253
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:256
msgid "Failed to read metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:254
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:257
msgid "Failed to read metadata from the following"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:275
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:280
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:299
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:278
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:283
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:302
msgid "Add to library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:280
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:283
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:116
#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:85
@@ -3772,12 +3775,12 @@ msgstr ""
msgid "No book selected"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:293
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:296
msgid ""
"The following books are virtual and cannot be added to the calibre library:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:299
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:302
msgid "No book files found"
msgstr ""
@@ -3795,12 +3798,12 @@ msgid "Fetch annotations (experimental)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:56
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:240
msgid "Use library only"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:57
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:237
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:241
msgid "User annotations generated from main library only"
msgstr ""
@@ -3846,7 +3849,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:20
#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:34
-msgid "Create catalog of books in your calibre library"
+msgid "Create a catalog of the books in your calibre library"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:31
@@ -3869,199 +3872,188 @@ msgstr ""
msgid "Select destination for %s.%s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:112
-msgid "Checking database integrity"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:128
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:594
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:41
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:201
-#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54
-msgid "Error"
-msgstr "خطأ"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:129
-msgid "Failed to check database integrity"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:134
-msgid "Some inconsistencies found"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:135
-msgid ""
-"The following books had formats or covers listed in the database that are "
-"not actually available. The entries for the formats/covers have been "
-"removed. You should check them manually. This can happen if you manipulate "
-"the files in the library folder directly."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:142
-msgid "No errors found"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:143
-msgid "The integrity check completed with no uncorrectable errors found."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:152
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:81
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:54
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:167
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:126
msgid "%d books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:153
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:82
msgid "Choose calibre library to work with"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:162
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:91
msgid "Switch/create library..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:102
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:77
msgid "Quick switch"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:104
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:78
msgid "Rename library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:177
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:106
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:79
msgid "Delete library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:109
msgid "Pick a random book"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:199
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:128
msgid "Library Maintenance"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:200
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:129
msgid "Library metadata backup status"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:204
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:133
msgid "Start backing up metadata of all books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:137
msgid "Check library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:212
-msgid "Check database integrity"
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:141
+msgid "Restore database"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:216
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:363
-msgid "Recover database"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:289
msgid "Rename"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:290
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:217
msgid "Choose a new name for the library %s. "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:291
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:218
msgid "Note that the actual library folder will be renamed."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:298
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:225
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:191
msgid "Already exists"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:299
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:226
msgid "The folder %s already exists. Delete it first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:305
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:232
msgid "Rename failed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:306
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:233
msgid ""
"Failed to rename the library at %s. The most common cause for this is if one "
"of the files in the library is open in another program."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:316
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:243
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:53
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:78
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:360
+#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:424
+#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:430
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:102
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:169
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:275
msgid "Are you sure?"
msgstr "هل أنت متأكّد؟"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:317
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:244
msgid "All files from %s will be permanently deleted. Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:336
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:263
msgid "none"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:337
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:264
msgid "Backup status"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:338
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:265
msgid "Book metadata files remaining to be written: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:344
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:271
msgid "Backup metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:345
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:272
msgid ""
"Metadata will be backed up while calibre is running, at the rate of "
-"approximately 1 book per second."
+"approximately 1 book every three seconds."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:364
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:304
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:106
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:111
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:286
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:340
+msgid "Success"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:305
msgid ""
-"This command rebuilds your calibre database from the information stored by "
-"calibre in the OPF files.This function is not currently available in the "
-"GUI. You can recover your database using the 'calibredb restore_database' "
-"command line function."
+"Found no errors in your calibre library database. Do you want calibre to "
+"check if the files in your library match the information in the database?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:378
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:310
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:672
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:877
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:101
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:190
+msgid "Failed"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:311
+msgid "Database integrity check failed, click Show details for details."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:316
+msgid "No problems found"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:317
+msgid "The files in your library match the information in the database."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:326
msgid "No library found"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:379
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:327
msgid ""
"No existing calibre library was found at %s. It will be removed from the "
"list of known libraries."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:418
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:423
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:380
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:385
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:167
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:780
msgid "Not allowed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:419
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:381
msgid ""
"You cannot change libraries while using the environment variable "
"CALIBRE_OVERRIDE_DATABASE_PATH."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:424
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:386
msgid "You cannot change libraries while jobs are running."
msgstr ""
@@ -4082,7 +4074,7 @@ msgid "Bulk convert"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:86
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:489
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:506
msgid "Cannot convert"
msgstr "لا يمكن تحويله"
@@ -4127,13 +4119,6 @@ msgstr ""
msgid "Could not copy books: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:674
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:854
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:190
-msgid "Failed"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:153
msgid "Copied %d books to %s"
msgstr ""
@@ -4703,7 +4688,7 @@ msgid "Selected books have no formats"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:101
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:79
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:127
msgid "Choose the format to view"
msgstr ""
@@ -4754,7 +4739,7 @@ msgid "The specified directory could not be processed."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:250
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:823
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:821
msgid "No books"
msgstr ""
@@ -4883,8 +4868,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:79
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:80
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:86
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:485
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:490
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:530
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417
@@ -4894,10 +4879,11 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:462
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:92
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:161
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:164
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:168
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:171
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:170
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:183
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:131
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:133
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:136
@@ -4990,7 +4976,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:78
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:83
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:294
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:316
msgid "None"
msgstr "بدون"
@@ -5018,7 +5004,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input.py:13
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:17
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:16
msgid "Options specific to"
msgstr ""
@@ -5034,11 +5020,11 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/pml_output.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output.py:15
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:17
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:16
msgid "output"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:86
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:89
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:42
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:295
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_tab_template_ui.py:32
@@ -5069,7 +5055,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/xpath_wizard_ui.py:72
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:77
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:40
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:111
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:114
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:136
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:81
@@ -5079,7 +5065,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:139
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:60
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:113
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:58
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:86
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:46
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:67
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:68
@@ -5091,64 +5077,68 @@ msgstr ""
msgid "Form"
msgstr "استمارة"
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:87
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:90
msgid "Bib file encoding:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:88
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:91
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:43
msgid "Fields to include in output:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:89
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:92
msgid "ascii/LaTeX"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:90
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:93
msgid "Encoding configuration (change if you have errors) :"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:91
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:94
msgid "strict"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:92
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:95
msgid "replace"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:93
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:96
msgid "ignore"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:94
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:97
msgid "backslashreplace"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:95
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:98
msgid "BibTeX entry type:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:96
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:99
msgid "mixed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:97
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:100
msgid "misc"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:98
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:101
msgid "book"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:99
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:102
msgid "Create a citation tag?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:100
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:103
+msgid "Add files path with formats?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:104
msgid "Expression to form the BibTeX citation tag:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:101
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:105
msgid ""
"Some explanation about this template:\n"
" -The fields availables are 'author_sort', 'authors', 'id',\n"
@@ -5388,6 +5378,10 @@ msgstr ""
msgid "Remove formatting"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:96
+msgid "Copy"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:97
msgid "Paste"
msgstr ""
@@ -5485,18 +5479,18 @@ msgstr ""
msgid "HTML Source"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:38
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:40
msgid ""
"For settings that cannot be specified in this dialog, use the values saved "
"in a previous conversion (if they exist) instead of using the defaults "
"specified in the Preferences"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:72
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:74
msgid "Bulk Convert"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:87
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:89
#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:189
msgid "Options specific to the output format."
msgstr ""
@@ -6020,7 +6014,7 @@ msgid "Change the title of this book"
msgstr "تغيير عنوان هذا الكتاب"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:166
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:450
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:495
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420
msgid "&Author(s): "
msgstr "ال&مؤلف: "
@@ -6036,7 +6030,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:169
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:460
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:505
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:428
msgid "&Publisher: "
msgstr "&الناشر: "
@@ -6047,7 +6041,7 @@ msgid "Ta&gs: "
msgstr "الو&سوم: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:171
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:462
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:507
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:430
msgid ""
"Tags categorize the book. This is particularly useful while searching. "
@@ -6057,7 +6051,7 @@ msgstr ""
"مجموعة كلمات، مفرقة بفاصلة."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:469
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:514
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:433
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:214
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:289
@@ -6066,8 +6060,8 @@ msgstr "&سلسلات:"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:173
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:470
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:471
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:515
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:516
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:434
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:435
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:288
@@ -6228,35 +6222,59 @@ msgstr ""
msgid "RB Output"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:85
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:133
msgid "No formats available"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:86
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:134
msgid "Cannot build regex using the GUI builder without a book."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:105
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:153
msgid "Open book"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:57
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:90
msgid "Regex Builder"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:58
-msgid "Preview"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:59
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:91
msgid "Regex:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:60
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:122
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:92
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:125
msgid "Test"
msgstr "تجربة"
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:93
+msgid "Occurrences:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:94
+msgid "0"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:95
+msgid "Goto:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:96
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:81
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:89
+msgid "&Previous"
+msgstr "ال&سابق"
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:97
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:82
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:88
+msgid "&Next"
+msgstr "ال&تالي"
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:98
+msgid "Preview"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:15
msgid ""
"Search\n"
@@ -6270,13 +6288,13 @@ msgstr ""
msgid "&Search Regular Expression"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:52
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:86
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:71
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:99
msgid "Invalid regular expression"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:53
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:87
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:72
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:100
msgid "Invalid regular expression: %s"
msgstr ""
@@ -6318,6 +6336,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:117
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:76
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:96
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box_ui.py:52
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:53
msgid "Dialog"
msgstr "حوار"
@@ -6464,7 +6483,7 @@ msgstr ""
msgid "Do not insert Table of Contents into output text when using markdown"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:16
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:15
msgid "TXT Output"
msgstr ""
@@ -6593,75 +6612,86 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:87
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:111
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:148
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:167
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:273
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:495
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:302
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:306
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:499
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:500
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:182
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:289
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:546
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:576
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:599
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:650
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:303
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:308
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:501
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:502
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:114
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:134
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:235
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:268
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:272
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:975
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:973
msgid "Undefined"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:125
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:607
msgid "star(s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:126
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:608
msgid "Unrated"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:159
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:169
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:637
msgid "Set '%s' to today"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:269
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:171
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:639
+msgid "Clear '%s'"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:285
msgid " index:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:335
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:351
msgid ""
"The enumeration \"{0}\" contains an invalid value that will be set to the "
"default"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:495
-msgid "Do not change"
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:505
+msgid "Apply changes"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:544
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:683
msgid "Remove series"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:547
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:686
msgid "Automatically number books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:550
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:689
msgid "Force numbers to start with "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:608
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:758
msgid ""
"The enumeration \"{0}\" contains invalid values that will not appear in the "
"list"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:664
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:800
msgid "Remove all tags"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:685
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:820
msgid "tags to add"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:690
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:826
msgid "tags to remove"
msgstr ""
@@ -6743,93 +6773,108 @@ msgstr ""
msgid "Eject device"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:594
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:41
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:304
+#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54
+msgid "Error"
+msgstr "خطأ"
+
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:595
msgid "Error communicating with device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:611
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1100
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1114
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:297
msgid "No suitable formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:629
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:627
msgid "Select folder to open as device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:680
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:678
msgid "Error talking to device"
msgstr "خطأ في الاتصال بالجهاز"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:681
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:679
msgid ""
"There was a temporary error talking to the device. Please unplug and "
"reconnect the device and or reboot."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:724
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:722
msgid "Device: "
msgstr "الجهاز: "
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:726
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:724
msgid " detected."
msgstr " تم كشفه."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:824
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:822
msgid "selected to send"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:829
-msgid "Choose format to send to device"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:838
-msgid "No device"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:839
-msgid "Cannot send: No device is connected"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:842
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:846
-msgid "No card"
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:841
+msgid "%i of %i Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:843
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:847
+msgid "0 of %i Books"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:844
+msgid "Choose format to send to device"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:852
+msgid "No device"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:853
+msgid "Cannot send: No device is connected"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:856
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:860
+msgid "No card"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:857
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:861
msgid "Cannot send: Device has no storage card"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:893
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:976
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1094
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:907
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:990
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1108
msgid "Auto convert the following books before uploading to the device?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:922
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:936
msgid "Sending catalogs to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1007
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1021
msgid "Sending news to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1061
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1075
msgid "Sending books to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1101
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1115
msgid ""
"Could not upload the following books to the device, as no suitable formats "
"were found. Convert the book(s) to a format supported by your device first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1165
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1179
msgid "No space on device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1166
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1180
msgid ""
"
Cannot upload books to device there is no more free space available "
msgstr ""
@@ -6913,107 +6958,197 @@ msgstr ""
msgid "Fit &cover within view"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:81
-msgid "&Previous"
-msgstr "ال&سابق"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:82
-msgid "&Next"
-msgstr "ال&تالي"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog.py:38
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog.py:33
msgid "My Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:80
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:92
#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:309
msgid "Generate catalog"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:81
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:93
msgid "Generate catalog for {0} books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:82
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:94
msgid "Catalog &format:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:83
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:95
msgid ""
"Catalog &title (existing catalog with the same title will be replaced):"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:84
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:96
msgid "&Send catalog to device automatically"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:85
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:97
msgid "Catalog options"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:25
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:26
-msgid "Check Library"
+msgid "Checking database integrity"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:35
-msgid "&Run the check"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:55
+msgid "Dumping database to SQL"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:38
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:81
+msgid "Loading database from SQL"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:148
+msgid "Check Library -- Problems Found"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:158
+msgid ""
+"
Help
\n"
+"\n"
+" calibre stores the list of your books and their metadata in a\n"
+" database. The actual book files and covers are stored as normal\n"
+" files in the calibre library folder. The database contains a list of "
+"the files\n"
+" and covers belonging to each book entry. This tool checks that the\n"
+" actual files in the library folder on your computer match the\n"
+" information in the database.
\n"
+"\n"
+" The result of each type of check is shown to the left. The "
+"various\n"
+" checks are:\n"
+"
\n"
+" \n"
+" - Invalid titles: These are files and folders appearing\n"
+" in the library where books titles should, but that do not have the\n"
+" correct form to be a book title.
\n"
+" - Extra titles: These are extra files in your calibre\n"
+" library that appear to be correctly-formed titles, but have no "
+"corresponding\n"
+" entries in the database
\n"
+" - Invalid authors: These are files appearing\n"
+" in the library where only author folders should be.
\n"
+" - Extra authors: These are folders in the\n"
+" calibre library that appear to be authors but that do not have "
+"entries\n"
+" in the database
\n"
+" - Missing book formats: These are book formats that are in\n"
+" the database but have no corresponding format file in the book's "
+"folder.\n"
+"
- Extra book formats: These are book format files found in\n"
+" the book's folder but not in the database.\n"
+"
- Unknown files in books: These are extra files in the\n"
+" folder of each book that do not correspond to a known format or "
+"cover\n"
+" file.
\n"
+" - Missing cover files: These represent books that are "
+"marked\n"
+" in the database as having covers but the actual cover files are\n"
+" missing.
\n"
+" - Cover files not in database: These are books that have\n"
+" cover files but are marked as not having covers in the "
+"database.
\n"
+" - Folder raising exception: These represent folders in the\n"
+" calibre library that could not be processed/understood by this\n"
+" tool.
\n"
+"
\n"
+"\n"
+" There are two kinds of automatic fixes possible: Delete\n"
+" marked and Fix marked.
\n"
+" Delete marked is used to remove extra files/folders/covers "
+"that\n"
+" have no entries in the database. Check the box next to the item you "
+"want\n"
+" to delete. Use with caution.
\n"
+" Fix marked is applicable only to covers (the two lines "
+"marked\n"
+" 'fixable'). In the case of missing cover files, checking the "
+"fixable\n"
+" box and pushing this button will remove the cover mark from the\n"
+" database for all the files in that category. In the case of extra\n"
+" cover files, checking the fixable box and pushing this button will\n"
+" add the cover mark to the database for all the files in that\n"
+" category.
\n"
+" "
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:218
+msgid "&Run the check again"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:221
msgid "Copy &to clipboard"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:45
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:228
msgid "Delete marked files (checked subitems)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:51
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:234
msgid "Fix marked sections (checked fixable items)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:61
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:244
msgid "Names to ignore:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:66
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:249
msgid ""
"Enter comma-separated standard file name wildcards, such as synctoy*.dat"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:69
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:252
msgid "Extensions to ignore"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:74
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:257
msgid ""
"Enter comma-separated extensions without a leading dot. Used only in book "
"folders"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:111
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:306
msgid "(fixable)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:134
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:329
msgid "Path from library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:134
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:329
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:89
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:253
msgid "Name"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:158
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:354
msgid ""
"The marked files and folders will be permanently deleted. Are you "
"sure?"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:45
msgid "Choose Format"
msgstr "إختيار التهيئة"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:49
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1162
+msgid "Format"
+msgstr "التهيئة"
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:50
+msgid "Existing"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:51
+msgid "Convertible"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library.py:43
msgid "Choose location for calibre library"
msgstr ""
@@ -7051,7 +7186,7 @@ msgid "No location selected"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library.py:89
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:665
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:670
msgid "Bad location"
msgstr ""
@@ -7179,11 +7314,6 @@ msgstr ""
msgid "Date"
msgstr "تاريخ"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1162
-msgid "Format"
-msgstr "التهيئة"
-
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device_ui.py:55
msgid "Delete from device"
msgstr ""
@@ -7204,12 +7334,12 @@ msgid "Author sort"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:117
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:837
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:893
msgid "Invalid author name"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:118
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:838
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:894
msgid "Author names cannot contain & characters."
msgstr ""
@@ -7339,82 +7469,103 @@ msgstr ""
msgid "Stop &all non device jobs"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:57
-msgid "Title/Author"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:43
+msgid "&Copy to clipboard"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:48
+msgid "Show &details"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:49
+msgid "Hide &details"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:53
+msgid "Show detailed information about this error"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:92
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:525
+msgid "Copied"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:58
-msgid "Standard metadata"
+msgid "Title/Author"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:59
-msgid "Custom metadata"
+msgid "Standard metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:60
+msgid "Custom metadata"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:61
msgid "Search/Replace"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:64
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:65
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress.py:76
msgid "Working"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:256
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:361
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:257
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:384
msgid "Lower Case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:257
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:360
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:258
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:383
msgid "Upper Case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:258
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:363
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:259
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:386
msgid "Title Case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:259
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:364
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:260
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:387
msgid "Capitalize"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263
msgid "Character match"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:264
msgid "Regular Expression"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:266
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:267
msgid "Replace field"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:267
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:268
msgid "Prepend to field"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:268
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:269
msgid "Append to field"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:278
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:279
msgid "Editing meta information for %d books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:318
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:320
msgid ""
"Immediately make all changes without closing the dialog. This operation "
"cannot be canceled or undone"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:369
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:378
msgid "Book %d:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:384
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:393
msgid ""
"You can destroy your library using this feature. Changes are "
"permanent. There is no undo function. You are strongly encouraged to back up "
@@ -7422,7 +7573,7 @@ msgid ""
"character matching or regular expressions. "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:392
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:401
msgid ""
"In character mode, the field is searched for the entered search text. The "
"text is replaced by the specified replacement text everywhere it is found in "
@@ -7432,7 +7583,7 @@ msgid ""
"text will match both upper- and lower-case letters"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:403
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:412
msgid ""
"In regular expression mode, the search text is an arbitrary python-"
"compatible regular expression. The replacement text can contain "
@@ -7447,121 +7598,145 @@ msgid ""
"function."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:458
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:476
msgid "S/R TEMPLATE ERROR"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:578
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:596
msgid "You must specify a destination when source is a composite field"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:681
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:689
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:788
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:699
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:707
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:811
msgid "Search/replace invalid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:682
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:700
msgid ""
"Authors cannot be set to the empty string. Book title %s not processed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:690
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:708
msgid "Title cannot be set to the empty string. Book title %s not processed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:789
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:812
msgid "Search pattern is invalid: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:840
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:863
msgid ""
"Applying changes to %d books.\n"
"Phase {0} {1}%%."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:449
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:892
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:555
+msgid "Delete saved search/replace"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:893
+msgid "The selected saved search/replace will be deleted. Are you sure?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:910
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:918
+msgid "Save search/replace"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:911
+msgid "Search/replace name:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:919
+msgid ""
+"That saved search/replace already exists and will be overwritten. Are you "
+"sure?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:494
msgid "Edit Meta information"
msgstr "تحرير معلومات الميتا"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:451
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:496
msgid "A&utomatically set author sort"
msgstr "ضبط& ترتيب المؤلف آلياً"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:452
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:497
msgid "&Swap title and author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:453
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:498
msgid "Author s&ort: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:454
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:499
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:455
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:500
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:424
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:774
msgid "&Rating:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:456
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:457
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:501
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:502
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:425
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:426
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:775
msgid "Rating of this book. 0-5 stars"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:458
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:503
msgid "No change"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:459
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:504
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:427
msgid " stars"
msgstr " نجمة"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:461
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:506
msgid "Add ta&gs: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:463
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:464
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:508
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:509
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:431
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:432
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:138
msgid "Open Tag Editor"
msgstr "فتح محرر الوسوم"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:465
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:510
msgid "&Remove tags:"
msgstr "حذف& الوسوم:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:466
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:511
msgid "Comma separated list of tags to remove from the books. "
msgstr "قائمة من الوسوم مفرقة بالفاصلة لحذفها من الكتب. "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:467
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:512
msgid "Check this box to remove all tags from the books."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:468
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:513
msgid "Remove &all"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:472
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:517
msgid "If checked, the series will be cleared"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:473
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:518
msgid "&Clear series"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:474
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:519
msgid ""
"If not checked, the series number for the books will be set to 1.\n"
"If checked, selected books will be automatically numbered, in the order\n"
@@ -7569,161 +7744,182 @@ msgid ""
"Book A will have series number 1 and Book B series number 2."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:478
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:523
msgid "&Automatically number books in this series"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:479
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:524
msgid ""
"Series will normally be renumbered from the highest number in the database\n"
"for that series. Checking this box will tell calibre to start numbering\n"
"from the value in the box"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:482
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:527
msgid "&Force numbers to start with:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:483
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:528
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:440
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:959
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:957
msgid "&Date:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:484
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:529
msgid "d MMM yyyy"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:486
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:491
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536
msgid "&Apply date"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:487
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532
msgid "&Published:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:489
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:444
msgid "Clear published date"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:492
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:537
msgid "Remove &format:"
msgstr "حذف الت&هيئة:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:493
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:538
msgid ""
"Force the title to be in title case. If both this and swap authors are "
"checked,\n"
"title and author are swapped before the title case is set"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:495
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:540
msgid "Change title to title &case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:496
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:541
msgid ""
"Remove stored conversion settings for the selected books.\n"
"\n"
"Future conversion of these books will use the default settings."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:499
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:544
msgid "Remove &stored conversion settings for the selected books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:500
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:545
msgid "Change &cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:501
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:546
msgid "&Generate default cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:502
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:547
msgid "&Remove cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:503
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:548
msgid "Set from &ebook file(s)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:504
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:549
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:465
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:380
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:509
msgid "&Basic metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:505
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:550
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:466
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:387
msgid "&Custom metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:506
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:551
+msgid "Load searc&h/replace:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:552
+msgid "Select saved search/replace to load."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:553
+msgid "Save current search/replace"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:554
+msgid "Sa&ve"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:556
+#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:64
+msgid "Delete"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:557
msgid "Search &field:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:507
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:558
msgid "The name of the field that you want to search"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:508
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:559
msgid "Search &mode:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:509
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:560
msgid ""
"Choose whether to use basic text matching or advanced regular expression "
"matching"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:510
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:561
msgid "Te&mplate:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:511
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:562
msgid "Enter a template to be used as the source for the search/replace"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:512
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:563
msgid "&Search for:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:513
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:564
msgid ""
"Enter the what you are looking for, either plain text or a regular "
"expression, depending on the mode"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:514
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:565
msgid ""
"Check this box if the search string must match exactly upper and lower case. "
"Uncheck it if case is to be ignored"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:515
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:566
msgid "Cas&e sensitive"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:516
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:567
msgid "&Replace with:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:517
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:568
msgid ""
"The replacement text. The matched search text will be replaced with this "
"string"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:518
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:569
msgid "&Apply function after replace:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:519
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:570
msgid ""
"Specify how the text is to be processed after matching and replacement. In "
"character mode, the entire\n"
@@ -7731,25 +7927,25 @@ msgid ""
"processed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:521
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:572
msgid "&Destination field:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:522
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:573
msgid ""
"The field that the text will be put into after all replacements.\n"
"If blank, the source field is used if the field is modifiable"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:524
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:575
msgid "M&ode:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:525
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:576
msgid "Specify how the text should be copied into the destination."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:526
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:577
msgid ""
"Specifies whether result items should be split into multiple values or\n"
"left as single values. This option has the most effect when the source field "
@@ -7757,41 +7953,41 @@ msgid ""
"not multiple and the destination field is multiple"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:529
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:580
msgid "Split &result"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:530
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:581
msgid "For multiple-valued fields, sho&w"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582
msgid "values starting a&t"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:583
msgid "with values separated b&y"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:533
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:584
msgid ""
"Used when displaying test results to separate values in multiple-valued "
"fields"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:585
msgid "Test text"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:586
msgid "Test result"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:587
msgid "Your test:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:537
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:588
msgid "&Search and replace"
msgstr ""
@@ -7827,115 +8023,115 @@ msgstr ""
msgid "Not a valid picture"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:214
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:685
msgid "Specify title and author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:213
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:215
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:686
msgid "You must specify a title and author before generating a cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:244
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:246
msgid "Downloading cover..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:260
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:265
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:271
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:276
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:267
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:273
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:278
msgid "Cannot fetch cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:261
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:272
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:277
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:274
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:279
msgid "Could not fetch cover.
"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:264
msgid "The download timed out."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:266
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:268
msgid "Could not find cover for this book. Try specifying the ISBN first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:278
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:280
msgid ""
"For the error message from each cover source, click Show details below."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:285
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:287
msgid "Bad cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:286
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:288
msgid "The cover is not a valid picture"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:305
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:307
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:515
msgid "Choose formats for "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:336
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:338
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:547
msgid "No permission"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:337
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:339
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:548
msgid "You do not have permission to read the following files:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:364
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:365
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:366
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:367
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:579
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:580
msgid "No format selected"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:376
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:378
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:591
msgid "Could not read metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:377
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:379
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:592
msgid "Could not read metadata from %s format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:449
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:451
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:225
msgid ""
" The green color indicates that the current author sort matches the current "
"author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:452
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:454
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:228
msgid ""
" The red color indicates that the current author sort does not match the "
"current author. No action is required if this is what you want."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:459
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:461
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:119
msgid ""
" The green color indicates that the current title sort matches the current "
"title"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:462
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:464
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:122
msgid ""
" The red color warns that the current title sort does not match the current "
"title. No action is required if this is what you want."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:468
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:470
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:47
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:102
#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:221
@@ -7943,14 +8139,14 @@ msgstr ""
msgid "Previous"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:471
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:479
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:473
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:481
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:347
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:351
msgid "Save changes and edit the metadata of %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:476
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:478
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:44
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:103
#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:211
@@ -7958,27 +8154,27 @@ msgstr ""
msgid "Next"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:680
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:685
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:899
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:682
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:687
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:897
msgid "This ISBN number is valid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:688
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:906
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:690
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:904
msgid "This ISBN number is invalid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:768
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:770
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:848
msgid "Tags changed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:769
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:771
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:849
msgid ""
"You have changed the tags. In order to use the tags editor, you must either "
-"discard or apply these changes"
+"discard or apply these changes. Apply changes?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:805
@@ -8077,7 +8273,7 @@ msgid "Remove unused series (Series that have no books)"
msgstr "حذف سلسلات غير مستخدمة (سلسلات التي لا تحتوي على كتب)"
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:439
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:872
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:870
msgid "IS&BN:"
msgstr "IS&BN:"
@@ -8086,7 +8282,7 @@ msgid "dd MMM yyyy"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:442
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1010
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1008
msgid "Publishe&d:"
msgstr ""
@@ -8187,6 +8383,41 @@ msgstr "إظهار& كلمة السرّ"
msgid "Aborting..."
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:23
+msgid ""
+"Restoring database from backups, do not interrupt, this will happen in three "
+"stages"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:25
+msgid "Restoring database"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:79
+msgid ""
+"Your list of books, with all their metadata is stored in a single file, "
+"called a database. In addition, metadata for each individual book is stored "
+"in that books' folder, as a backup.This operation will rebuild the "
+"database from the individual book metadata. This is useful if the database "
+"has been corrupted and you get a blank list of books. Note that restoring "
+"only restores books, not any settings stored in the database, or any custom "
+"recipes.
Do you want to restore the database?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:102
+msgid "Restoring database failed, click Show details to see details"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:107
+msgid ""
+"Restoring the database succeeded with some warnings click Show details to "
+"see the details."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:112
+msgid "Restoring database was successful"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:55
msgid ""
"The current saved search will be permanently deleted. Are you sure?"
@@ -8273,11 +8504,11 @@ msgstr ""
msgid "Download all scheduled new sources"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:348
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:353
msgid "No internet connection"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:349
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:354
msgid "Cannot download news as no internet connection is active"
msgstr ""
@@ -8520,63 +8751,78 @@ msgstr "المؤلفون"
msgid "Publishers"
msgstr "الناشرون"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:124
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:128
msgid " (not on any book)"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:197
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:151
+msgid "Name already used"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:176
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:198
+msgid "That name is already used, perhaps with different case."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:211
msgid ""
"The current tag category will be permanently deleted. Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:158
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:166
msgid "User Categories Editor"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:159
-msgid "A&vailable items"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:160
-msgid "Apply tags to current tag category"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:162
-msgid "A&pplied items"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:163
-msgid "Unapply (remove) tag from current tag category"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:165
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:167
msgid "Category name: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:166
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:168
msgid "Select a category to edit"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:167
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:169
msgid "Delete this selected tag category"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:169
-msgid "Enter a new category name. Select the kind before adding it."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:170
-msgid "Add the new category"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:171
+msgid "Enter a category name, then use the add button or the rename button"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:172
+msgid "Add a new category"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:174
+msgid "Rename the current category to the what is in the box"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:176
msgid "Category filter: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:177
msgid "Select the content kind of the new category"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:178
+msgid "A&vailable items"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:179
+msgid "Apply tags to current tag category"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:181
+msgid "A&pplied items"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:182
+msgid "Unapply (remove) tag from current tag category"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor.py:70
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:105
msgid "Are your sure?"
@@ -8637,12 +8883,12 @@ msgid "%s (was %s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:74
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:827
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:883
msgid "Item is blank"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:75
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:828
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:884
msgid "An item cannot be set to nothing. Delete it instead."
msgstr ""
@@ -8711,7 +8957,7 @@ msgid "Send test mail from %s to:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:58
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:120
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:123
msgid "&Test"
msgstr "&تجربة"
@@ -8943,7 +9189,7 @@ msgid "Attached, you will find the e-book"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:247
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:117
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:186
msgid "by"
msgstr ""
@@ -8976,7 +9222,7 @@ msgstr ""
msgid "Sent news to"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:112
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:115
msgid ""
"\n"
@@ -9000,64 +9246,64 @@ msgid ""
"metadata entries are documented in tooltips.
"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:119
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:122
msgid "Regular &expression"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:121
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:124
msgid "File &name:"
msgstr "اسم ال&ملف:"
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:123
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:126
msgid "Title:"
msgstr ":العنوان"
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:124
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:127
msgid "Regular expression (?P<title>)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:125
#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:128
#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:131
#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:134
#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:137
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:93
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:97
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:102
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:107
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:109
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:140
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:106
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:110
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:115
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:120
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:122
msgid "No match"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:126
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:129
msgid "Authors:"
msgstr "المؤلفون:"
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:127
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:130
msgid "Regular expression (?P)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:129
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:132
msgid "Series:"
msgstr "السلسلة:"
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:130
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:133
msgid "Regular expression (?P)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:132
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:135
msgid "Series index:"
msgstr "فهرس السلسلة:"
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:133
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:136
msgid "Regular expression (?P)"
msgstr "Regular expression (?P)"
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:135
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:138
msgid "ISBN:"
msgstr "ISBN:"
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:136
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:139
msgid "Regular expression (?P)"
msgstr ""
@@ -9171,6 +9417,14 @@ msgstr ""
msgid " - Jobs"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:424
+msgid "Do you really want to stop the selected job?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:430
+msgid "Do you really want to stop all non-device jobs?"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:53
msgid "Eject this device"
msgstr ""
@@ -9184,7 +9438,7 @@ msgid "Show books in the main memory of the device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:67
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:847
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:901
msgid "Card A"
msgstr ""
@@ -9193,7 +9447,7 @@ msgid "Show books in storage card A"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:69
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:849
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:903
msgid "Card B"
msgstr ""
@@ -9282,7 +9536,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:738
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1282
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:538
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:589
msgid "The lookup/search name is \"{0}\""
msgstr ""
@@ -9450,7 +9704,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:61
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:673
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:678
msgid "Calibre Library"
msgstr ""
@@ -9484,7 +9738,7 @@ msgid "The database repair failed. Starting with a new empty library."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:197
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:229
msgid "Bad database location"
msgstr ""
@@ -9492,64 +9746,64 @@ msgstr ""
msgid "Bad database location %r. calibre will now quit."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:211
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:210
msgid "Corrupted database"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:211
msgid ""
"Your calibre database appears to be corrupted. Do you want calibre to try "
"and repair it automatically? If you say No, a new empty calibre library will "
"be created."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:218
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:217
msgid ""
"Repairing database. This can take a very long time for a large collection"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:231
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:230
msgid ""
"Bad database location %r. Will start with a new, empty calibre library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:241
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:240
msgid "Starting %s: Loading books..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:321
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:320
msgid "If you are sure it is not running"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:323
-msgid "Cannot Start "
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:324
-msgid "%s is already running."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:327
msgid "may be running in the system tray, in the"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:329
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:325
msgid "upper right region of the screen."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:331
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:327
msgid "lower right region of the screen."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:334
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:330
msgid "try rebooting your computer."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:336
-#: /home/kovid/work/calibre/src/calibre/gui2/main.py:348
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:332
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:346
msgid "try deleting the file"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:335
+msgid "Cannot Start "
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/main.py:336
+msgid "%s is already running."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:20
msgid ""
"Redirect console output to a dialog window (both stdout and stderr). Useful "
@@ -9565,8 +9819,7 @@ msgid "&Quit"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:90
-#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:109
-msgid "ERROR: Unhandled exception"
+msgid "Unhandled exception"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:111
@@ -9603,11 +9856,11 @@ msgid ""
"
They can be any wordsor phrases, separated by commas."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:913
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:911
msgid "&Publisher:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:978
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:976
msgid "Clear date"
msgstr ""
@@ -10398,101 +10651,117 @@ msgstr ""
msgid "Delete plugboard"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:110
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:179
msgid "%(plugin_type)s %(plugins)s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:111
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:180
msgid "plugins"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:120
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:189
msgid ""
"\n"
"Customization: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:162
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:218
+msgid "Search for plugin"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:226
+msgid "No matches"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:227
+msgid "Could not find any matching plugins"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:268
msgid "Add plugin"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:170
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:276
msgid ""
"Installing plugins is a security risk. Plugins can contain a "
"virus/malware. Only install it if you got it from a trusted source. Are you "
"sure you want to proceed?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:180
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:231
-msgid "Success"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:181
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:287
msgid ""
"Plugin {0} successfully installed under {1} plugins. You may "
"have to restart calibre for the plugin to take effect."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:192
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:295
msgid "No valid plugin path"
msgstr "مسار الملحق غير صالح"
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:193
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:296
msgid "%s is not a valid plugin path"
msgstr "%s ليس مسار لملحق صالح"
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:202
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:305
msgid "Select an actual plugin under %s to customize"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:311
msgid "Plugin cannot be disabled"
msgstr "لا يمكن تعطيل الملحق"
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:209
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:312
msgid "The plugin: %s cannot be disabled"
msgstr "الملحق: %s لا يمكن تعطيله"
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:219
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:322
msgid "Plugin not customizable"
msgstr "لا يمكن تخصيص الملحق"
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:220
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:323
msgid "Plugin: %s does not need customization"
msgstr "الملحق: %s لا يحتاج التخصيص"
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:226
-msgid "Plugin {0} successfully removed"
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:329
+msgid "Must restart"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:234
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:330
+msgid ""
+"You must restart calibre before you can configure the %s plugin"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:335
+msgid "Plugin {0} successfully removed"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:343
msgid "Cannot remove builtin plugin"
msgstr "لم يمكن حذف الملحق المضمن"
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:235
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:344
msgid " cannot be removed. It is a builtin plugin. Try disabling it instead."
msgstr " لا يمكن حذفه. هذا ملحق مضمن في البرنامج. حاول تعطيله بدلاً من حذفه."
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:59
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:87
msgid ""
"Here you can customize the behavior of Calibre by controlling what plugins "
"it uses."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:60
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:90
msgid "Enable/&Disable plugin"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:61
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:91
msgid "&Customize plugin"
msgstr "ت&خصيص الملحق"
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:62
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:92
msgid "&Remove plugin"
msgstr "&حذف الملحق"
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:63
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:93
msgid "&Add a new plugin"
msgstr ""
@@ -10608,7 +10877,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:75
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:365
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:381
msgid "Failed to start content server"
msgstr "فشل في تشغيل خادم المحتوى"
@@ -10789,10 +11058,6 @@ msgstr ""
msgid "Function not defined"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:151
-msgid "Name already used"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:155
msgid "Argument count must be -1 or greater than zero"
msgstr ""
@@ -10935,7 +11200,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:94
#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:271
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:593
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:616
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:277
msgid "Search"
msgstr "بحث"
@@ -11028,72 +11293,88 @@ msgstr ""
msgid "&Alternate shortcut:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:251
-msgid "Rename '%s'"
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:273
+msgid "Rename %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:255
-msgid "Edit sort for '%s'"
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:277
+msgid "Edit sort for %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:260
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:282
+msgid "Search for %s"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:287
+msgid "Search for everything but %s"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:293
msgid "Hide category %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:296
msgid "Show category"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:272
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:276
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:304
+msgid "Search for books in category %s"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:308
+msgid "Search for books not in category %s"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:315
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:319
msgid "Manage %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:279
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:322
msgid "Manage Saved Searches"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:286
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:290
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:329
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:333
msgid "Manage User Categories"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:297
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:340
msgid "Show all categories"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:300
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:343
msgid "Change sub-categorization scheme"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:625
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:677
msgid ""
"Changing the authors for several books can take a while. Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:630
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:682
msgid ""
"Changing the metadata for that many books can take a while. Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:687
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:325
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:743
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:350
msgid "Searches"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:842
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:898
msgid "Duplicate search name"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:843
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:899
msgid "The saved search name %s is already used."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1232
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1313
msgid "Find item in tag browser"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1235
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1316
msgid ""
"Search for items. This is a \"contains\" search; items containing the\n"
"text anywhere in the name will be found. You can limit the search\n"
@@ -11103,60 +11384,60 @@ msgid ""
"containing the text \"foo\""
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1244
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1325
msgid "ALT+f"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1248
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1329
msgid "F&ind"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1249
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1330
msgid "Find the first/next matching item"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1256
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1337
msgid "Collapse all categories"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1277
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1358
msgid "No More Matches. Click Find again to go to first match"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1290
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1371
msgid "Sort by name"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1290
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1371
msgid "Sort by popularity"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1291
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1372
msgid "Sort by average rating"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1294
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1375
msgid "Set the sort order for entries in the Tag Browser"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1300
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1381
msgid "Match all"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1300
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1381
msgid "Match any"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1305
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1386
msgid ""
"When selecting multiple entries in the Tag Browser match any or all of them"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1309
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1390
msgid "Manage &user categories"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1312
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1393
msgid "Add your own categories to the Tag Browser"
msgstr ""
@@ -11202,68 +11483,68 @@ msgid ""
"reconvert them?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:182
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:192
msgid "&Restore"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:184
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:194
msgid "&Donate to support calibre"
msgstr "تبرع& لدعم كاليبر"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:188
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:198
msgid "&Eject connected device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:229
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:243
msgid "Calibre Quick Start Guide"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:291
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:305
msgid "Debug mode"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:292
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:306
msgid ""
"You have started calibre in debug mode. After you quit calibre, the debug "
"log will be available in the file: %s
The log will be displayed "
"automatically."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:477
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:494
msgid "Conversion Error"
msgstr "خطأ في التحويل"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:500
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:517
msgid "Recipe Disabled"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:516
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:533
msgid "Failed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:553
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:570
msgid ""
"is the result of the efforts of many volunteers from all over the world. If "
"you find it useful, please consider donating to support its development. "
"Your donation helps keep calibre development going."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:579
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:596
msgid "There are active jobs. Are you sure you want to quit?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:582
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:599
msgid ""
" is communicating with the device!
\n"
" Quitting may cause corruption on the device.
\n"
" Are you sure you want to quit?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:586
-msgid "WARNING: Active jobs"
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:603
+msgid "Active jobs"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:658
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:669
msgid ""
"will keep running in the system tray. To close it, choose Quit in the "
"context menu of the system tray."
@@ -11323,10 +11604,6 @@ msgstr ""
msgid "Edit"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:64
-msgid "Delete"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:65
msgid "Reset"
msgstr ""
@@ -11747,96 +12024,82 @@ msgstr ""
msgid "Print eBook"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:257
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:279
msgid "Copy Image"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:258
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:280
msgid "Paste Image"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:359
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:382
msgid "Change Case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:362
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:385
msgid "Swap Case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:902
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:925
msgid "Drag to resize"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:937
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:960
msgid "Show"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:944
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:967
msgid "Hide"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:981
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:1004
msgid "Toggle"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:411
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:438
msgid ""
-"If you use the WordPlayer e-book app on your Android phone, you can access "
-"your calibre book collection directly on the device. To do this you have to "
-"turn on the content server."
+"Choose you e-book device. If your device is not in the list, choose a \"%s\" "
+"device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:415
-msgid ""
-"Remember to leave calibre running as the server only runs as long as calibre "
-"is running."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:417
-msgid ""
-"You have to add the URL http://myhostname:8080 as your calibre library in "
-"WordPlayer. Here myhostname should be the fully qualified hostname or the IP "
-"address of the computer calibre is running on."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:494
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:499
msgid "Moving library..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:510
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:511
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:515
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:516
msgid "Failed to move library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:565
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:570
msgid "Invalid database"
msgstr "قاعدة البيانات غير صالحة"
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:566
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:571
msgid ""
"
An invalid library already exists at %s, delete it before trying to move "
"the existing library.
Error: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:577
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:582
msgid "Could not move library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:652
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:657
msgid "Select location for books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:666
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:671
msgid ""
"You must choose an empty folder for the calibre library. %s is not empty."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:740
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:745
msgid "welcome wizard"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:53
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:54
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:55
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:47
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:47
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:55
@@ -11844,7 +12107,7 @@ msgstr ""
msgid "Welcome to calibre"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:55
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:56
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:56
@@ -11852,12 +12115,6 @@ msgstr ""
msgid "The one stop solution to all your e-book needs."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:56
-msgid ""
-"Choose your book reader. This will set the conversion options to produce "
-"books optimized for your device."
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:57
msgid "&Manufacturers"
msgstr ""
@@ -12098,64 +12355,65 @@ msgstr ""
msgid "Turn on the &content server"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:299
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:306
msgid "today"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:302
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:309
msgid "yesterday"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:305
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:312
msgid "thismonth"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:308
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:309
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:315
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:316
msgid "daysago"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:498
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:508
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:527
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:537
msgid "unchecked"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:498
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:508
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:527
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:537
#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:185
msgid "no"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:501
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:511
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:530
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:540
msgid "checked"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:501
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:511
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:530
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:540
#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:185
msgid "yes"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:505
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:534
msgid "blank"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:505
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:534
msgid "empty"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:53
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:52
msgid ""
"The fields to output when cataloging books in the database. Should be a "
"comma-separated list of fields.\n"
"Available fields: %s,\n"
"plus user-created custom fields.\n"
+"Example: %s=title,authors,tags\n"
"Default: '%%default'\n"
"Applies to: CSV, XML output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:64
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:65
msgid ""
"Output field to sort on.\n"
"Available fields: author_sort, id, rating, size, timestamp, title.\n"
@@ -12163,16 +12421,17 @@ msgid ""
"Applies to: CSV, XML output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:231
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:232
msgid ""
"The fields to output when cataloging books in the database. Should be a "
"comma-separated list of fields.\n"
"Available fields: %s.\n"
+"Example: %s=title,authors,tags\n"
"Default: '%%default'\n"
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:241
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:244
msgid ""
"Output field to sort on.\n"
"Available fields: author_sort, id, rating, size, timestamp, title.\n"
@@ -12180,7 +12439,7 @@ msgid ""
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:250
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:253
msgid ""
"Create a citation for BibTeX entries.\n"
"Boolean value: True, False\n"
@@ -12188,7 +12447,15 @@ msgid ""
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:259
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:262
+msgid ""
+"Create a file entry if formats is selected for BibTeX entries.\n"
+"Boolean value: True, False\n"
+"Default: '%default'\n"
+"Applies to: BIBTEX output format"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:271
msgid ""
"The template for citation creation from database fields.\n"
" Should be a template with {} enclosed fields.\n"
@@ -12197,7 +12464,7 @@ msgid ""
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:269
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:281
msgid ""
"BibTeX file encoding output.\n"
"Available types: utf8, cp1252, ascii.\n"
@@ -12205,7 +12472,7 @@ msgid ""
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:278
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:290
msgid ""
"BibTeX file encoding flag.\n"
"Available types: strict, replace, ignore, backslashreplace.\n"
@@ -12213,7 +12480,7 @@ msgid ""
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:287
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:299
msgid ""
"Entry type for BibTeX catalog.\n"
"Available types: book, misc, mixed.\n"
@@ -12221,14 +12488,14 @@ msgid ""
"Applies to: BIBTEX output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:572
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:598
msgid ""
"Title of generated catalog used as title in metadata.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:579
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:605
msgid ""
"Save the output from different stages of the conversion pipeline to the "
"specified directory. Useful if you are unsure at which stage of the "
@@ -12237,7 +12504,7 @@ msgid ""
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:589
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:615
msgid ""
"field:pattern specifying custom field/contents indicating book should be "
"excluded.\n"
@@ -12245,14 +12512,14 @@ msgid ""
"Applies to ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:596
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:622
msgid ""
"Regex describing tags to exclude as genres.\n"
"Default: '%default' excludes bracketed tags, e.g. '[]'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:602
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:628
msgid ""
"Comma-separated list of tag words indicating book should be excluded from "
"output.For example: 'skip' will match 'skip this book' and 'Skip will like "
@@ -12260,56 +12527,56 @@ msgid ""
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:610
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:636
msgid ""
"Include 'Authors' section in catalog.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:617
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:643
msgid ""
"Include 'Descriptions' section in catalog.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:624
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:650
msgid ""
"Include 'Genres' section in catalog.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:631
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:657
msgid ""
"Include 'Titles' section in catalog.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:638
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:664
msgid ""
"Include 'Series' section in catalog.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:645
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:671
msgid ""
"Include 'Recently Added' section in catalog.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:652
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:678
msgid ""
"Custom field containing note text to insert in Description header.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:659
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:685
msgid ""
":[before|after]:[True|False] specifying:\n"
" Custom field containing notes to merge with Comments\n"
@@ -12319,7 +12586,7 @@ msgid ""
"Applies to ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:669
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:695
msgid ""
"Specifies the output profile. In some cases, an output profile is required "
"to optimize the catalog for the device. For example, 'kindle' or "
@@ -12329,14 +12596,14 @@ msgid ""
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:676
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:702
msgid ""
"field:pattern indicating book has been read.\n"
"Default: '%default'\n"
"Applies to ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:682
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:708
msgid ""
"Size hint (in inches) for book covers in catalog.\n"
"Range: 1.0 - 2.0\n"
@@ -12344,22 +12611,22 @@ msgid ""
"Applies to ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:690
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:716
msgid ""
"Tag indicating book to be displayed as wishlist item.\n"
"Default: '%default'\n"
"Applies to: ePub, MOBI output formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1373
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1399
msgid "No enabled genres found to catalog.\n"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1377
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1403
msgid "No books available to catalog"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1451
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1477
msgid ""
"\n"
"Inconsistent Author Sort values for Author '{0}':\n"
@@ -12371,17 +12638,17 @@ msgid ""
"then rebuild the catalog.\n"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1652
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1678
msgid ""
"No books found to catalog.\n"
"Check 'Excluded books' criteria in E-book options.\n"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1654
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1680
msgid "No books available to include in catalog"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:4975
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:5006
msgid ""
"\n"
"*** Adding 'By Authors' Section required for MOBI output ***"
@@ -12416,11 +12683,11 @@ msgid "Unknown files in books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/check_library.py:33
-msgid "Missing covers in books"
+msgid "Missing covers files"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/check_library.py:34
-msgid "Extra covers in books"
+msgid "Cover files not in database"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/check_library.py:35
@@ -12944,34 +13211,22 @@ msgstr ""
msgid "%sAverage rating is %3.1f"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:845
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:899
msgid "Main"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2574
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2631
msgid "Migrating old database to ebook library in %s
"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2603
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2660
msgid "Copying %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2620
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2677
msgid "Compacting database"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2745
-msgid "Checking SQL integrity..."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2783
-msgid "Checking for missing files."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2811
-msgid "Checked id"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:140
msgid "Ratings"
msgstr ""
@@ -13731,42 +13986,86 @@ msgid "English (Pakistan)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:108
-msgid "English (Israel)"
+msgid "English (Croatia)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:109
-msgid "English (Singapore)"
+msgid "English (Israel)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:110
-msgid "English (Yemen)"
+msgid "English (Singapore)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:111
-msgid "English (Ireland)"
+msgid "English (Yemen)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:112
-msgid "English (China)"
+msgid "English (Ireland)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:113
-msgid "Spanish (Paraguay)"
+msgid "English (China)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:114
-msgid "German (AT)"
+msgid "Spanish (Paraguay)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:115
-msgid "French (BE)"
+msgid "Spanish (Uruguay)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:116
-msgid "Dutch (NL)"
+msgid "Spanish (Argentina)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:117
+msgid "Spanish (Mexico)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:118
+msgid "Spanish (Cuba)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:119
+msgid "Spanish (Chile)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:120
+msgid "Spanish (Ecuador)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:121
+msgid "Spanish (Honduras)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:122
+msgid "Spanish (Venezuela)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:123
+msgid "Spanish (Bolivia)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:124
+msgid "Spanish (Nicaragua)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:125
+msgid "German (AT)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:126
+msgid "French (BE)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:127
+msgid "Dutch (NL)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:128
msgid "Dutch (BE)"
msgstr ""
@@ -13774,6 +14073,10 @@ msgstr ""
msgid "Choose theme (needs restart)"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:109
+msgid "ERROR: Unhandled exception"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:188
msgid "No interpreter"
msgstr ""
@@ -14268,6 +14571,9 @@ msgstr ""
#~ msgid "Extract the cover"
#~ msgstr "استخراج الغلاف"
+#~ msgid " not found."
+#~ msgstr " لم يوجد."
+
#~ msgid "No filename specified."
#~ msgstr "لم يتم تحديد اسم الملف."
diff --git a/src/calibre/translations/ast.po b/src/calibre/translations/ast.po
index 8925d04a87..f6216f05a7 100644
--- a/src/calibre/translations/ast.po
+++ b/src/calibre/translations/ast.po
@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: calibre\n"
"Report-Msgid-Bugs-To: FULL NAME \n"
-"POT-Creation-Date: 2011-01-21 23:34+0000\n"
+"POT-Creation-Date: 2011-01-30 19:51+0000\n"
"PO-Revision-Date: 2010-01-31 21:37+0000\n"
"Last-Translator: Kovid Goyal \n"
"Language-Team: Asturian \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-01-23 04:40+0000\n"
+"X-Launchpad-Export-Date: 2011-01-31 04:38+0000\n"
"X-Generator: Launchpad (build 12177)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
@@ -45,7 +45,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1894
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1896
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:24
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:235
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:236
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:31
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:32
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:73
@@ -55,7 +55,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:36
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:61
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:54
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:358
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:365
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:36
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:64
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:66
@@ -71,6 +71,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:91
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:101
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/snb.py:16
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:56
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:14
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:42
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:68
@@ -82,9 +83,9 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:878
#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:49
#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:51
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:952
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:957
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1023
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:958
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:963
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1029
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:143
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:150
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:64
@@ -114,8 +115,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:101
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:329
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:331
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:364
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:371
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:299
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:306
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:100
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:331
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:334
@@ -125,14 +126,14 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:120
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:145
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:147
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1050
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1053
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1064
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1067
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:55
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:67
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:145
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:185
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:724
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:726
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:193
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:236
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:245
@@ -147,18 +148,18 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:191
#: /home/kovid/work/calibre/src/calibre/library/cli.py:215
#: /home/kovid/work/calibre/src/calibre/library/database.py:914
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:402
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:414
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1474
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1575
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2415
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2417
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2548
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:432
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:444
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1529
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1632
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2472
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2474
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2605
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:229
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:158
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:161
#: /home/kovid/work/calibre/src/calibre/library/server/xml.py:79
-#: /home/kovid/work/calibre/src/calibre/utils/localization.py:118
+#: /home/kovid/work/calibre/src/calibre/utils/localization.py:129
#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:46
#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:64
#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:78
@@ -431,7 +432,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:889
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:163
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:269
msgid "Plugins"
msgstr ""
@@ -503,57 +504,57 @@ msgid "This profile is intended for the SONY PRS-900."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:90
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:522
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:538
msgid "This profile is intended for the Microsoft Reader."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:101
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:533
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:549
msgid "This profile is intended for the Mobipocket books."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:114
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:546
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:562
msgid "This profile is intended for the Hanlin V3 and its clones."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:126
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:558
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:574
msgid "This profile is intended for the Hanlin V5 and its clones."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:136
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:566
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:582
msgid "This profile is intended for the Cybook G3."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:149
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:579
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:596
msgid "This profile is intended for the Cybook Opus."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:161
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:592
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:609
msgid "This profile is intended for the Amazon Kindle."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:173
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:642
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:659
msgid "This profile is intended for the Irex Illiad."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:185
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:655
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:672
msgid "This profile is intended for the IRex Digital Reader 1000."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:198
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:669
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:686
msgid "This profile is intended for the IRex Digital Reader 800."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:210
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:683
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:700
msgid "This profile is intended for the B&N Nook."
msgstr ""
@@ -592,24 +593,32 @@ msgid "This profile is intended for the SONY PRS-300."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:502
+msgid "Suitable for use with any e-ink device"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:509
+msgid "Suitable for use with any large screen e-ink device"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:518
msgid "This profile is intended for the 5-inch JetBook."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:511
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:527
msgid ""
"This profile is intended for the SONY PRS line. The 500/505/700 etc, in "
"landscape mode. Mainly useful for comics."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:618
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:635
msgid "This profile is intended for the Amazon Kindle DX."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:695
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:712
msgid "This profile is intended for the B&N Nook Color."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:706
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:723
msgid "This profile is intended for the Sanda Bambook."
msgstr ""
@@ -683,13 +692,13 @@ msgstr ""
msgid "Communicate with Android phones."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:61
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:62
msgid ""
"Comma separated list of directories to send e-books to on the device. The "
"first one that exists will be used"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:107
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:108
msgid "Communicate with S60 phones."
msgstr ""
@@ -758,19 +767,19 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:886
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:892
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:922
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:264
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:219
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:232
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2279
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:262
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:244
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:257
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2336
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:150
msgid "News"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2554
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:65
-#: /home/kovid/work/calibre/src/calibre/library/catalog.py:599
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2242
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2260
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:625
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2299
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2317
msgid "Catalog"
msgstr ""
@@ -882,7 +891,7 @@ msgid "Communicate with the Blackberry smart phone."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:14
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:254
#: /home/kovid/work/calibre/src/calibre/devices/nuut2/driver.py:18
#: /home/kovid/work/calibre/src/calibre/devices/prs500/driver.py:90
msgid "Kovid Goyal"
@@ -896,23 +905,23 @@ msgstr ""
msgid "Communicate with the Cybook Orizon eBook reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:24
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:25
msgid "Communicate with the EB600 eBook reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:193
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:194
msgid "Communicate with the Astak Mentor EB600"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:216
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:217
msgid "Communicate with the PocketBook 301 reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:233
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:234
msgid "Communicate with the PocketBook 602/603/902/903 reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:252
+#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253
msgid "Communicate with the PocketBook 701"
msgstr ""
@@ -1041,7 +1050,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:446
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:292
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:295
msgid "Not Implemented"
msgstr ""
@@ -1901,27 +1910,27 @@ msgstr ""
msgid "Replacement to replace the text found with sr3-search."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:671
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:673
msgid "Could not find an ebook inside the archive"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:729
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:731
msgid "Values of series index and rating must be numbers. Ignoring"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:736
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:738
msgid "Failed to parse date/time"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:891
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:893
msgid "Converting input to HTML..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:918
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:920
msgid "Running transforms on ebook..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1006
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1008
msgid "Creating"
msgstr ""
@@ -2398,13 +2407,13 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:544
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:62
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:495
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:546
msgid "No"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:544
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:62
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:495
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:546
msgid "Yes"
msgstr ""
@@ -2599,7 +2608,7 @@ msgid "Download covers from openlibrary.org"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:108
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:137
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:140
msgid "ISBN: %s not found"
msgstr ""
@@ -2607,28 +2616,32 @@ msgstr ""
msgid "Download covers from librarything.com"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:129
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:82
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:130
msgid "LibraryThing.com timed out. Try again later."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:136
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:89
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:139
msgid ""
"Could not fetch cover as server is experiencing high load. Please try again "
"later."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:140
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:93
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:143
msgid "LibraryThing.com server error. Try again later."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:226
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:177
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:270
+msgid ""
+"To use librarything.com you must sign up for a %sfree account%s and enter "
+"your username and password separated by a : below."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:240
msgid "Download covers from Douban.com"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:235
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:249
msgid "Douban.com API timed out. Try again later."
msgstr ""
@@ -2679,7 +2692,7 @@ msgid "Downloads social metadata from amazon.com"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:254
-msgid "Downloads series/tags/rating information from librarything.com"
+msgid "Downloads series/covers/rating information from librarything.com"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:25
@@ -2789,11 +2802,7 @@ msgstr ""
msgid "The publisher of the book to search for."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:90
-msgid " not found."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:100
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:75
msgid ""
"\n"
"%prog [options] ISBN\n"
@@ -2866,10 +2875,14 @@ msgid "Cover saved to file "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1308
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1442
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1448
msgid "Cover"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:14
+msgid "Metadata source"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:22
msgid "Modify images to meet Palm device size limitations."
msgstr ""
@@ -2912,70 +2925,70 @@ msgstr ""
msgid "This is an Amazon Topaz book. It cannot be processed."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1443
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1449
msgid "Title Page"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1444
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1450
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:54
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:199
msgid "Table of Contents"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1445
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1451
msgid "Index"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1446
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1452
msgid "Glossary"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1447
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1453
msgid "Acknowledgements"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1448
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1454
msgid "Bibliography"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1449
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1455
msgid "Colophon"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1450
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1456
msgid "Copyright"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1451
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1457
msgid "Dedication"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1452
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1458
msgid "Epigraph"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1453
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1459
msgid "Foreword"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1454
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1460
msgid "List of Illustrations"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1455
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1461
msgid "List of Tables"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1456
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1462
msgid "Notes"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1457
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1463
msgid "Preface"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1458
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1464
msgid "Main Text"
msgstr ""
@@ -2984,8 +2997,8 @@ msgid "%s format books are not supported"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/cover.py:98
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:218
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:220
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:691
msgid "Book %s of %s"
msgstr ""
@@ -3043,7 +3056,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/output.py:32
#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:37
#: /home/kovid/work/calibre/src/calibre/ebooks/rb/output.py:21
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:35
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:34
msgid "Add Table of Contents to beginning of the book."
msgstr ""
@@ -3262,7 +3275,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:49
msgid ""
"Preserve the aspect ratio of the cover, instead of stretching it to fill the "
-"ull first page of the generated pdf."
+"full first page of the generated pdf."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:55
@@ -3299,13 +3312,13 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:25
#: /home/kovid/work/calibre/src/calibre/ebooks/tcr/output.py:23
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:31
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:30
msgid ""
"Specify the character encoding of the output document. The default is utf-8."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:29
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:38
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:37
msgid ""
"The maximum number of characters per line. This splits on the first space "
"before the specified value. If no space is found the line will be broken at "
@@ -3383,35 +3396,39 @@ msgstr ""
msgid "Do not insert a Table of Contents into the output text."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:25
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:24
msgid ""
"Type of newline to use. Options are %s. Default is 'system'. Use 'old_mac' "
"for compatibility with Mac OS 9 and earlier. For Mac OS X use 'unix'. "
"'system' will default to the newline type used by this OS."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:45
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:44
msgid ""
"Force splitting on the max-line-length value when no space is present. Also "
"allows max-line-length to be below the minimum"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:49
-msgid "Produce Markdown formatted text."
+msgid ""
+"Formatting used within the document.\n"
+"* plain: Produce plain text.\n"
+"* markdown: Produce Markdown formatted text.\n"
+"* textile: Produce Textile formatted text."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:52
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:55
msgid ""
"Do not remove links within the document. This is only useful when paired "
-"with the markdown-format option because links are always removed with plain "
-"text output."
+"with a txt-output-formatting option that is not none because links are "
+"always removed with plain text output."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:57
+#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:60
msgid ""
"Do not remove image references within the document. This is only useful when "
-"paired with the markdown-format option because image references are always "
-"removed with plain text output."
+"paired with a txt-output-formatting option that is not none because links "
+"are always removed with plain text output."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:70
@@ -3492,58 +3509,44 @@ msgstr ""
msgid "Default action to perform when send to device button is clicked"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:124
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:126
msgid "Maximum number of waiting worker processes"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:126
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:128
msgid "Download social metadata (tags/rating/etc.)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:128
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:130
msgid "Overwrite author and title with new metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:130
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:132
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:101
msgid "Automatically download the cover, if available"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:132
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:134
msgid "Limit max simultaneous jobs to number of CPUs"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:134
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:136
msgid "tag browser categories not to display"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:136
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:138
msgid "The layout of the user interface"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:138
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:140
msgid "Show the average rating per item indication in the tag browser"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:140
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:142
msgid "Disable UI animations"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:188
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:520
-msgid "Copied"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:222
-msgid "Copy to Clipboard"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:222
-#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:96
-msgid "Copy"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:475
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:410
msgid "Choose Files"
msgstr ""
@@ -3587,89 +3590,89 @@ msgstr ""
msgid "Add from ISBN"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:233
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:236
msgid "Uploading books to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:189
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:306
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:192
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:308
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:517
msgid "Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:190
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:193
msgid "EPUB Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:191
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:194
msgid "LRF Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:192
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:195
msgid "HTML Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:193
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:196
msgid "LIT Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:194
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:197
msgid "MOBI Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:195
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:198
msgid "Topaz books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:196
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:199
msgid "Text books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:197
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:200
msgid "PDF Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:198
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:201
msgid "SNB Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:199
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:202
msgid "Comics"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:200
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:203
msgid "Archives"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:204
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:207
msgid "Supported books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:246
msgid "Merged some books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:244
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:247
msgid ""
"Some duplicates were found and merged into the following existing books:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:253
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:256
msgid "Failed to read metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:254
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:257
msgid "Failed to read metadata from the following"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:275
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:280
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:299
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:278
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:283
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:302
msgid "Add to library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:280
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:283
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:116
#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:85
@@ -3677,12 +3680,12 @@ msgstr ""
msgid "No book selected"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:293
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:296
msgid ""
"The following books are virtual and cannot be added to the calibre library:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:299
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:302
msgid "No book files found"
msgstr ""
@@ -3700,12 +3703,12 @@ msgid "Fetch annotations (experimental)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:56
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:240
msgid "Use library only"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:57
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:237
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:241
msgid "User annotations generated from main library only"
msgstr ""
@@ -3751,7 +3754,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:20
#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:34
-msgid "Create catalog of books in your calibre library"
+msgid "Create a catalog of the books in your calibre library"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:31
@@ -3774,199 +3777,188 @@ msgstr ""
msgid "Select destination for %s.%s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:112
-msgid "Checking database integrity"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:128
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:594
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:41
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:201
-#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54
-msgid "Error"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:129
-msgid "Failed to check database integrity"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:134
-msgid "Some inconsistencies found"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:135
-msgid ""
-"The following books had formats or covers listed in the database that are "
-"not actually available. The entries for the formats/covers have been "
-"removed. You should check them manually. This can happen if you manipulate "
-"the files in the library folder directly."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:142
-msgid "No errors found"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:143
-msgid "The integrity check completed with no uncorrectable errors found."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:152
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:81
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:54
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:167
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:126
msgid "%d books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:153
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:82
msgid "Choose calibre library to work with"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:162
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:91
msgid "Switch/create library..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:102
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:77
msgid "Quick switch"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:104
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:78
msgid "Rename library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:177
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:106
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:79
msgid "Delete library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:109
msgid "Pick a random book"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:199
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:128
msgid "Library Maintenance"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:200
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:129
msgid "Library metadata backup status"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:204
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:133
msgid "Start backing up metadata of all books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:137
msgid "Check library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:212
-msgid "Check database integrity"
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:141
+msgid "Restore database"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:216
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:363
-msgid "Recover database"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:289
msgid "Rename"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:290
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:217
msgid "Choose a new name for the library %s. "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:291
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:218
msgid "Note that the actual library folder will be renamed."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:298
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:225
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:191
msgid "Already exists"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:299
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:226
msgid "The folder %s already exists. Delete it first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:305
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:232
msgid "Rename failed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:306
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:233
msgid ""
"Failed to rename the library at %s. The most common cause for this is if one "
"of the files in the library is open in another program."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:316
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:243
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:53
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:78
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:360
+#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:424
+#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:430
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:102
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:169
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:275
msgid "Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:317
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:244
msgid "All files from %s will be permanently deleted. Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:336
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:263
msgid "none"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:337
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:264
msgid "Backup status"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:338
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:265
msgid "Book metadata files remaining to be written: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:344
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:271
msgid "Backup metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:345
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:272
msgid ""
"Metadata will be backed up while calibre is running, at the rate of "
-"approximately 1 book per second."
+"approximately 1 book every three seconds."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:364
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:304
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:106
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:111
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:286
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:340
+msgid "Success"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:305
msgid ""
-"This command rebuilds your calibre database from the information stored by "
-"calibre in the OPF files.This function is not currently available in the "
-"GUI. You can recover your database using the 'calibredb restore_database' "
-"command line function."
+"Found no errors in your calibre library database. Do you want calibre to "
+"check if the files in your library match the information in the database?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:378
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:310
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:672
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:877
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:101
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:190
+msgid "Failed"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:311
+msgid "Database integrity check failed, click Show details for details."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:316
+msgid "No problems found"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:317
+msgid "The files in your library match the information in the database."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:326
msgid "No library found"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:379
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:327
msgid ""
"No existing calibre library was found at %s. It will be removed from the "
"list of known libraries."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:418
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:423
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:380
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:385
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:167
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:780
msgid "Not allowed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:419
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:381
msgid ""
"You cannot change libraries while using the environment variable "
"CALIBRE_OVERRIDE_DATABASE_PATH."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:424
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:386
msgid "You cannot change libraries while jobs are running."
msgstr ""
@@ -3987,7 +3979,7 @@ msgid "Bulk convert"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:86
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:489
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:506
msgid "Cannot convert"
msgstr ""
@@ -4032,13 +4024,6 @@ msgstr ""
msgid "Could not copy books: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:674
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:854
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:190
-msgid "Failed"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:153
msgid "Copied %d books to %s"
msgstr ""
@@ -4608,7 +4593,7 @@ msgid "Selected books have no formats"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:101
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:79
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:127
msgid "Choose the format to view"
msgstr ""
@@ -4659,7 +4644,7 @@ msgid "The specified directory could not be processed."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:250
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:823
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:821
msgid "No books"
msgstr ""
@@ -4788,8 +4773,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:79
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:80
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:86
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:485
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:490
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:530
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417
@@ -4799,10 +4784,11 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:462
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:92
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:161
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:164
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:168
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:171
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:170
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:183
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:131
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:133
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:136
@@ -4895,7 +4881,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:78
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:83
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:294
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:316
msgid "None"
msgstr ""
@@ -4923,7 +4909,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input.py:13
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:17
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:16
msgid "Options specific to"
msgstr ""
@@ -4939,11 +4925,11 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/pml_output.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output.py:15
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:17
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:16
msgid "output"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:86
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:89
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:42
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:295
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_tab_template_ui.py:32
@@ -4974,7 +4960,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/xpath_wizard_ui.py:72
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:77
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:40
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:111
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:114
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:136
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:81
@@ -4984,7 +4970,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:139
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:60
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:113
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:58
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:86
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:46
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:67
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:68
@@ -4996,64 +4982,68 @@ msgstr ""
msgid "Form"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:87
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:90
msgid "Bib file encoding:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:88
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:91
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:43
msgid "Fields to include in output:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:89
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:92
msgid "ascii/LaTeX"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:90
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:93
msgid "Encoding configuration (change if you have errors) :"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:91
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:94
msgid "strict"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:92
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:95
msgid "replace"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:93
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:96
msgid "ignore"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:94
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:97
msgid "backslashreplace"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:95
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:98
msgid "BibTeX entry type:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:96
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:99
msgid "mixed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:97
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:100
msgid "misc"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:98
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:101
msgid "book"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:99
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:102
msgid "Create a citation tag?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:100
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:103
+msgid "Add files path with formats?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:104
msgid "Expression to form the BibTeX citation tag:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:101
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:105
msgid ""
"Some explanation about this template:\n"
" -The fields availables are 'author_sort', 'authors', 'id',\n"
@@ -5293,6 +5283,10 @@ msgstr ""
msgid "Remove formatting"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:96
+msgid "Copy"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:97
msgid "Paste"
msgstr ""
@@ -5390,18 +5384,18 @@ msgstr ""
msgid "HTML Source"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:38
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:40
msgid ""
"For settings that cannot be specified in this dialog, use the values saved "
"in a previous conversion (if they exist) instead of using the defaults "
"specified in the Preferences"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:72
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:74
msgid "Bulk Convert"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:87
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:89
#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:189
msgid "Options specific to the output format."
msgstr ""
@@ -5925,7 +5919,7 @@ msgid "Change the title of this book"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:166
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:450
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:495
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420
msgid "&Author(s): "
msgstr ""
@@ -5941,7 +5935,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:169
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:460
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:505
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:428
msgid "&Publisher: "
msgstr ""
@@ -5952,7 +5946,7 @@ msgid "Ta&gs: "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:171
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:462
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:507
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:430
msgid ""
"Tags categorize the book. This is particularly useful while searching. "
@@ -5960,7 +5954,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:469
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:514
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:433
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:214
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:289
@@ -5969,8 +5963,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:173
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:470
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:471
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:515
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:516
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:434
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:435
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:288
@@ -6131,35 +6125,59 @@ msgstr ""
msgid "RB Output"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:85
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:133
msgid "No formats available"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:86
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:134
msgid "Cannot build regex using the GUI builder without a book."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:105
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:153
msgid "Open book"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:57
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:90
msgid "Regex Builder"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:58
-msgid "Preview"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:59
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:91
msgid "Regex:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:60
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:122
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:92
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:125
msgid "Test"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:93
+msgid "Occurrences:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:94
+msgid "0"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:95
+msgid "Goto:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:96
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:81
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:89
+msgid "&Previous"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:97
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:82
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:88
+msgid "&Next"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:98
+msgid "Preview"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:15
msgid ""
"Search\n"
@@ -6173,13 +6191,13 @@ msgstr ""
msgid "&Search Regular Expression"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:52
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:86
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:71
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:99
msgid "Invalid regular expression"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:53
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:87
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:72
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:100
msgid "Invalid regular expression: %s"
msgstr ""
@@ -6221,6 +6239,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:117
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:76
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:96
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box_ui.py:52
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:53
msgid "Dialog"
msgstr ""
@@ -6367,7 +6386,7 @@ msgstr ""
msgid "Do not insert Table of Contents into output text when using markdown"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:16
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:15
msgid "TXT Output"
msgstr ""
@@ -6496,75 +6515,86 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:87
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:111
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:148
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:167
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:273
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:495
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:302
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:306
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:499
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:500
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:182
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:289
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:546
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:576
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:599
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:650
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:303
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:308
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:501
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:502
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:114
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:134
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:235
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:268
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:272
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:975
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:973
msgid "Undefined"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:125
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:607
msgid "star(s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:126
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:608
msgid "Unrated"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:159
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:169
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:637
msgid "Set '%s' to today"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:269
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:171
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:639
+msgid "Clear '%s'"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:285
msgid " index:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:335
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:351
msgid ""
"The enumeration \"{0}\" contains an invalid value that will be set to the "
"default"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:495
-msgid "Do not change"
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:505
+msgid "Apply changes"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:544
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:683
msgid "Remove series"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:547
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:686
msgid "Automatically number books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:550
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:689
msgid "Force numbers to start with "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:608
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:758
msgid ""
"The enumeration \"{0}\" contains invalid values that will not appear in the "
"list"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:664
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:800
msgid "Remove all tags"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:685
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:820
msgid "tags to add"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:690
+#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:826
msgid "tags to remove"
msgstr ""
@@ -6646,93 +6676,108 @@ msgstr ""
msgid "Eject device"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:594
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:41
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:304
+#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54
+msgid "Error"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:595
msgid "Error communicating with device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:611
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1100
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1114
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:297
msgid "No suitable formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:629
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:627
msgid "Select folder to open as device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:680
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:678
msgid "Error talking to device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:681
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:679
msgid ""
"There was a temporary error talking to the device. Please unplug and "
"reconnect the device and or reboot."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:724
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:722
msgid "Device: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:726
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:724
msgid " detected."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:824
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:822
msgid "selected to send"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:829
-msgid "Choose format to send to device"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:838
-msgid "No device"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:839
-msgid "Cannot send: No device is connected"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:842
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:846
-msgid "No card"
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:841
+msgid "%i of %i Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:843
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:847
+msgid "0 of %i Books"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:844
+msgid "Choose format to send to device"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:852
+msgid "No device"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:853
+msgid "Cannot send: No device is connected"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:856
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:860
+msgid "No card"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:857
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:861
msgid "Cannot send: Device has no storage card"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:893
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:976
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1094
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:907
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:990
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1108
msgid "Auto convert the following books before uploading to the device?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:922
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:936
msgid "Sending catalogs to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1007
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1021
msgid "Sending news to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1061
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1075
msgid "Sending books to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1101
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1115
msgid ""
"Could not upload the following books to the device, as no suitable formats "
"were found. Convert the book(s) to a format supported by your device first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1165
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1179
msgid "No space on device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1166
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1180
msgid ""
"
Cannot upload books to device there is no more free space available "
msgstr ""
@@ -6816,107 +6861,197 @@ msgstr ""
msgid "Fit &cover within view"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:81
-msgid "&Previous"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:82
-msgid "&Next"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog.py:38
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog.py:33
msgid "My Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:80
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:92
#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:309
msgid "Generate catalog"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:81
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:93
msgid "Generate catalog for {0} books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:82
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:94
msgid "Catalog &format:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:83
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:95
msgid ""
"Catalog &title (existing catalog with the same title will be replaced):"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:84
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:96
msgid "&Send catalog to device automatically"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:85
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:97
msgid "Catalog options"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:25
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:26
-msgid "Check Library"
+msgid "Checking database integrity"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:35
-msgid "&Run the check"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:55
+msgid "Dumping database to SQL"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:38
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:81
+msgid "Loading database from SQL"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:148
+msgid "Check Library -- Problems Found"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:158
+msgid ""
+"
Help
\n"
+"\n"
+" calibre stores the list of your books and their metadata in a\n"
+" database. The actual book files and covers are stored as normal\n"
+" files in the calibre library folder. The database contains a list of "
+"the files\n"
+" and covers belonging to each book entry. This tool checks that the\n"
+" actual files in the library folder on your computer match the\n"
+" information in the database.
\n"
+"\n"
+" The result of each type of check is shown to the left. The "
+"various\n"
+" checks are:\n"
+"
\n"
+" \n"
+" - Invalid titles: These are files and folders appearing\n"
+" in the library where books titles should, but that do not have the\n"
+" correct form to be a book title.
\n"
+" - Extra titles: These are extra files in your calibre\n"
+" library that appear to be correctly-formed titles, but have no "
+"corresponding\n"
+" entries in the database
\n"
+" - Invalid authors: These are files appearing\n"
+" in the library where only author folders should be.
\n"
+" - Extra authors: These are folders in the\n"
+" calibre library that appear to be authors but that do not have "
+"entries\n"
+" in the database
\n"
+" - Missing book formats: These are book formats that are in\n"
+" the database but have no corresponding format file in the book's "
+"folder.\n"
+"
- Extra book formats: These are book format files found in\n"
+" the book's folder but not in the database.\n"
+"
- Unknown files in books: These are extra files in the\n"
+" folder of each book that do not correspond to a known format or "
+"cover\n"
+" file.
\n"
+" - Missing cover files: These represent books that are "
+"marked\n"
+" in the database as having covers but the actual cover files are\n"
+" missing.
\n"
+" - Cover files not in database: These are books that have\n"
+" cover files but are marked as not having covers in the "
+"database.
\n"
+" - Folder raising exception: These represent folders in the\n"
+" calibre library that could not be processed/understood by this\n"
+" tool.
\n"
+"
\n"
+"\n"
+" There are two kinds of automatic fixes possible: Delete\n"
+" marked and Fix marked.
\n"
+" Delete marked is used to remove extra files/folders/covers "
+"that\n"
+" have no entries in the database. Check the box next to the item you "
+"want\n"
+" to delete. Use with caution.
\n"
+" Fix marked is applicable only to covers (the two lines "
+"marked\n"
+" 'fixable'). In the case of missing cover files, checking the "
+"fixable\n"
+" box and pushing this button will remove the cover mark from the\n"
+" database for all the files in that category. In the case of extra\n"
+" cover files, checking the fixable box and pushing this button will\n"
+" add the cover mark to the database for all the files in that\n"
+" category.
\n"
+" "
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:218
+msgid "&Run the check again"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:221
msgid "Copy &to clipboard"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:45
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:228
msgid "Delete marked files (checked subitems)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:51
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:234
msgid "Fix marked sections (checked fixable items)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:61
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:244
msgid "Names to ignore:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:66
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:249
msgid ""
"Enter comma-separated standard file name wildcards, such as synctoy*.dat"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:69
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:252
msgid "Extensions to ignore"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:74
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:257
msgid ""
"Enter comma-separated extensions without a leading dot. Used only in book "
"folders"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:111
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:306
msgid "(fixable)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:134
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:329
msgid "Path from library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:134
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:329
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:89
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:253
msgid "Name"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:158
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:354
msgid ""
"The marked files and folders will be permanently deleted. Are you "
"sure?"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:45
msgid "Choose Format"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:49
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1162
+msgid "Format"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:50
+msgid "Existing"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:51
+msgid "Convertible"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library.py:43
msgid "Choose location for calibre library"
msgstr ""
@@ -6954,7 +7089,7 @@ msgid "No location selected"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library.py:89
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:665
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:670
msgid "Bad location"
msgstr ""
@@ -7082,11 +7217,6 @@ msgstr ""
msgid "Date"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1162
-msgid "Format"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device_ui.py:55
msgid "Delete from device"
msgstr ""
@@ -7107,12 +7237,12 @@ msgid "Author sort"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:117
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:837
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:893
msgid "Invalid author name"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:118
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:838
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:894
msgid "Author names cannot contain & characters."
msgstr ""
@@ -7242,82 +7372,103 @@ msgstr ""
msgid "Stop &all non device jobs"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:57
-msgid "Title/Author"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:43
+msgid "&Copy to clipboard"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:48
+msgid "Show &details"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:49
+msgid "Hide &details"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:53
+msgid "Show detailed information about this error"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:92
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:525
+msgid "Copied"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:58
-msgid "Standard metadata"
+msgid "Title/Author"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:59
-msgid "Custom metadata"
+msgid "Standard metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:60
+msgid "Custom metadata"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:61
msgid "Search/Replace"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:64
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:65
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress.py:76
msgid "Working"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:256
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:361
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:257
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:384
msgid "Lower Case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:257
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:360
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:258
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:383
msgid "Upper Case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:258
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:363
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:259
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:386
msgid "Title Case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:259
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:364
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:260
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:387
msgid "Capitalize"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263
msgid "Character match"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:264
msgid "Regular Expression"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:266
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:267
msgid "Replace field"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:267
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:268
msgid "Prepend to field"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:268
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:269
msgid "Append to field"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:278
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:279
msgid "Editing meta information for %d books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:318
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:320
msgid ""
"Immediately make all changes without closing the dialog. This operation "
"cannot be canceled or undone"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:369
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:378
msgid "Book %d:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:384
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:393
msgid ""
"You can destroy your library using this feature. Changes are "
"permanent. There is no undo function. You are strongly encouraged to back up "
@@ -7325,7 +7476,7 @@ msgid ""
"character matching or regular expressions. "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:392
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:401
msgid ""
"In character mode, the field is searched for the entered search text. The "
"text is replaced by the specified replacement text everywhere it is found in "
@@ -7335,7 +7486,7 @@ msgid ""
"text will match both upper- and lower-case letters"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:403
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:412
msgid ""
"In regular expression mode, the search text is an arbitrary python-"
"compatible regular expression. The replacement text can contain "
@@ -7350,121 +7501,145 @@ msgid ""
"function."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:458
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:476
msgid "S/R TEMPLATE ERROR"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:578
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:596
msgid "You must specify a destination when source is a composite field"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:681
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:689
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:788
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:699
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:707
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:811
msgid "Search/replace invalid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:682
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:700
msgid ""
"Authors cannot be set to the empty string. Book title %s not processed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:690
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:708
msgid "Title cannot be set to the empty string. Book title %s not processed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:789
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:812
msgid "Search pattern is invalid: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:840
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:863
msgid ""
"Applying changes to %d books.\n"
"Phase {0} {1}%%."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:449
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:892
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:555
+msgid "Delete saved search/replace"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:893
+msgid "The selected saved search/replace will be deleted. Are you sure?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:910
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:918
+msgid "Save search/replace"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:911
+msgid "Search/replace name:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:919
+msgid ""
+"That saved search/replace already exists and will be overwritten. Are you "
+"sure?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:494
msgid "Edit Meta information"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:451
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:496
msgid "A&utomatically set author sort"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:452
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:497
msgid "&Swap title and author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:453
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:498
msgid "Author s&ort: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:454
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:499
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:455
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:500
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:424
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:774
msgid "&Rating:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:456
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:457
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:501
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:502
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:425
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:426
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:775
msgid "Rating of this book. 0-5 stars"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:458
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:503
msgid "No change"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:459
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:504
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:427
msgid " stars"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:461
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:506
msgid "Add ta&gs: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:463
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:464
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:508
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:509
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:431
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:432
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:138
msgid "Open Tag Editor"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:465
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:510
msgid "&Remove tags:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:466
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:511
msgid "Comma separated list of tags to remove from the books. "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:467
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:512
msgid "Check this box to remove all tags from the books."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:468
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:513
msgid "Remove &all"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:472
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:517
msgid "If checked, the series will be cleared"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:473
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:518
msgid "&Clear series"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:474
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:519
msgid ""
"If not checked, the series number for the books will be set to 1.\n"
"If checked, selected books will be automatically numbered, in the order\n"
@@ -7472,161 +7647,182 @@ msgid ""
"Book A will have series number 1 and Book B series number 2."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:478
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:523
msgid "&Automatically number books in this series"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:479
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:524
msgid ""
"Series will normally be renumbered from the highest number in the database\n"
"for that series. Checking this box will tell calibre to start numbering\n"
"from the value in the box"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:482
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:527
msgid "&Force numbers to start with:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:483
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:528
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:440
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:959
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:957
msgid "&Date:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:484
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:529
msgid "d MMM yyyy"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:486
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:491
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536
msgid "&Apply date"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:487
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532
msgid "&Published:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:489
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:444
msgid "Clear published date"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:492
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:537
msgid "Remove &format:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:493
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:538
msgid ""
"Force the title to be in title case. If both this and swap authors are "
"checked,\n"
"title and author are swapped before the title case is set"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:495
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:540
msgid "Change title to title &case"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:496
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:541
msgid ""
"Remove stored conversion settings for the selected books.\n"
"\n"
"Future conversion of these books will use the default settings."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:499
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:544
msgid "Remove &stored conversion settings for the selected books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:500
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:545
msgid "Change &cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:501
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:546
msgid "&Generate default cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:502
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:547
msgid "&Remove cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:503
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:548
msgid "Set from &ebook file(s)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:504
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:549
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:465
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:380
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:509
msgid "&Basic metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:505
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:550
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:466
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:387
msgid "&Custom metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:506
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:551
+msgid "Load searc&h/replace:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:552
+msgid "Select saved search/replace to load."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:553
+msgid "Save current search/replace"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:554
+msgid "Sa&ve"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:556
+#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:64
+msgid "Delete"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:557
msgid "Search &field:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:507
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:558
msgid "The name of the field that you want to search"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:508
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:559
msgid "Search &mode:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:509
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:560
msgid ""
"Choose whether to use basic text matching or advanced regular expression "
"matching"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:510
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:561
msgid "Te&mplate:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:511
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:562
msgid "Enter a template to be used as the source for the search/replace"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:512
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:563
msgid "&Search for:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:513
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:564
msgid ""
"Enter the what you are looking for, either plain text or a regular "
"expression, depending on the mode"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:514
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:565
msgid ""
"Check this box if the search string must match exactly upper and lower case. "
"Uncheck it if case is to be ignored"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:515
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:566
msgid "Cas&e sensitive"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:516
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:567
msgid "&Replace with:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:517
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:568
msgid ""
"The replacement text. The matched search text will be replaced with this "
"string"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:518
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:569
msgid "&Apply function after replace:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:519
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:570
msgid ""
"Specify how the text is to be processed after matching and replacement. In "
"character mode, the entire\n"
@@ -7634,25 +7830,25 @@ msgid ""
"processed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:521
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:572
msgid "&Destination field:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:522
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:573
msgid ""
"The field that the text will be put into after all replacements.\n"
"If blank, the source field is used if the field is modifiable"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:524
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:575
msgid "M&ode:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:525
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:576
msgid "Specify how the text should be copied into the destination."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:526
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:577
msgid ""
"Specifies whether result items should be split into multiple values or\n"
"left as single values. This option has the most effect when the source field "
@@ -7660,41 +7856,41 @@ msgid ""
"not multiple and the destination field is multiple"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:529
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:580
msgid "Split &result"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:530
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:581
msgid "For multiple-valued fields, sho&w"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582
msgid "values starting a&t"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:583
msgid "with values separated b&y"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:533
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:584
msgid ""
"Used when displaying test results to separate values in multiple-valued "
"fields"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:585
msgid "Test text"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:586
msgid "Test result"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:587
msgid "Your test:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:537
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:588
msgid "&Search and replace"
msgstr ""
@@ -7730,115 +7926,115 @@ msgstr ""
msgid "Not a valid picture"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:214
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:685
msgid "Specify title and author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:213
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:215
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:686
msgid "You must specify a title and author before generating a cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:244
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:246
msgid "Downloading cover..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:260
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:265
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:271
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:276
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:267
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:273
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:278
msgid "Cannot fetch cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:261
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:272
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:277
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:274
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:279
msgid "Could not fetch cover.
"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:264
msgid "The download timed out."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:266
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:268
msgid "Could not find cover for this book. Try specifying the ISBN first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:278
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:280
msgid ""
"For the error message from each cover source, click Show details below."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:285
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:287
msgid "Bad cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:286
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:288
msgid "The cover is not a valid picture"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:305
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:307
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:515
msgid "Choose formats for "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:336
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:338
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:547
msgid "No permission"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:337
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:339
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:548
msgid "You do not have permission to read the following files:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:364
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:365
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:366
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:367
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:579
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:580
msgid "No format selected"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:376
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:378
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:591
msgid "Could not read metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:377
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:379
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:592
msgid "Could not read metadata from %s format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:449
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:451
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:225
msgid ""
" The green color indicates that the current author sort matches the current "
"author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:452
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:454
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:228
msgid ""
" The red color indicates that the current author sort does not match the "
"current author. No action is required if this is what you want."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:459
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:461
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:119
msgid ""
" The green color indicates that the current title sort matches the current "
"title"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:462
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:464
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:122
msgid ""
" The red color warns that the current title sort does not match the current "
"title. No action is required if this is what you want."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:468
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:470
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:47
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:102
#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:221
@@ -7846,14 +8042,14 @@ msgstr ""
msgid "Previous"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:471
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:479
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:473
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:481
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:347
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:351
msgid "Save changes and edit the metadata of %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:476
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:478
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:44
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:103
#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:211
@@ -7861,27 +8057,27 @@ msgstr ""
msgid "Next"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:680
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:685
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:899
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:682
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:687
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:897
msgid "This ISBN number is valid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:688
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:906
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:690
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:904
msgid "This ISBN number is invalid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:768
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:770
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:848
msgid "Tags changed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:769
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:771
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:849
msgid ""
"You have changed the tags. In order to use the tags editor, you must either "
-"discard or apply these changes"
+"discard or apply these changes. Apply changes?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:805
@@ -7980,7 +8176,7 @@ msgid "Remove unused series (Series that have no books)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:439
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:872
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:870
msgid "IS&BN:"
msgstr ""
@@ -7989,7 +8185,7 @@ msgid "dd MMM yyyy"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:442
-#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1010
+#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1008
msgid "Publishe&d:"
msgstr ""
@@ -8090,6 +8286,41 @@ msgstr ""
msgid "Aborting..."
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:23
+msgid ""
+"Restoring database from backups, do not interrupt, this will happen in three "
+"stages"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:25
+msgid "Restoring database"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:79
+msgid ""
+"Your list of books, with all their metadata is stored in a single file, "
+"called a database. In addition, metadata for each individual book is stored "
+"in that books' folder, as a backup.This operation will rebuild the "
+"database from the individual book metadata. This is useful if the database "
+"has been corrupted and you get a blank list of books. Note that restoring "
+"only restores books, not any settings stored in the database, or any custom "
+"recipes.
Do you want to restore the database?"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:102
+msgid "Restoring database failed, click Show details to see details"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:107
+msgid ""
+"Restoring the database succeeded with some warnings click Show details to "
+"see the details."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:112
+msgid "Restoring database was successful"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:55
msgid ""
"The current saved search will be permanently deleted. Are you sure?"
@@ -8176,11 +8407,11 @@ msgstr ""
msgid "Download all scheduled new sources"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:348
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:353
msgid "No internet connection"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:349
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:354
msgid "Cannot download news as no internet connection is active"
msgstr ""
@@ -8422,63 +8653,78 @@ msgstr ""
msgid "Publishers"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:124
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:128
msgid " (not on any book)"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:197
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:151
+msgid "Name already used"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:176
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:198
+msgid "That name is already used, perhaps with different case."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:211
msgid ""
"The current tag category will be permanently deleted. Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:158
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:166
msgid "User Categories Editor"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:159
-msgid "A&vailable items"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:160
-msgid "Apply tags to current tag category"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:162
-msgid "A&pplied items"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:163
-msgid "Unapply (remove) tag from current tag category"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:165
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:167
msgid "Category name: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:166
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:168
msgid "Select a category to edit"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:167
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:169
msgid "Delete this selected tag category"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:169
-msgid "Enter a new category name. Select the kind before adding it."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:170
-msgid "Add the new category"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:171
+msgid "Enter a category name, then use the add button or the rename button"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:172
+msgid "Add a new category"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:174
+msgid "Rename the current category to the what is in the box"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:176
msgid "Category filter: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:177
msgid "Select the content kind of the new category"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:178
+msgid "A&vailable items"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:179
+msgid "Apply tags to current tag category"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:181
+msgid "A&pplied items"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:182
+msgid "Unapply (remove) tag from current tag category"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor.py:70
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:105
msgid "Are your sure?"
@@ -8535,12 +8781,12 @@ msgid "%s (was %s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:74
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:827
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:883
msgid "Item is blank"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:75
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:828
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:884
msgid "An item cannot be set to nothing. Delete it instead."
msgstr ""
@@ -8609,7 +8855,7 @@ msgid "Send test mail from %s to:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:58
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:120
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:123
msgid "&Test"
msgstr ""
@@ -8841,7 +9087,7 @@ msgid "Attached, you will find the e-book"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:247
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:117
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:186
msgid "by"
msgstr ""
@@ -8874,7 +9120,7 @@ msgstr ""
msgid "Sent news to"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:112
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:115
msgid ""
"\n"
@@ -8898,64 +9144,64 @@ msgid ""
"metadata entries are documented in tooltips.