def fset(self, wl): self.dictionary_list.clear() for langcode, url in sorted( wl.iteritems(), key=lambda (lc, url): sort_key(calibre_langcode_to_name(lc))): i = QListWidgetItem( '%s: %s' % (calibre_langcode_to_name(langcode), url), self.dictionary_list) i.setData(Qt.UserRole, (langcode, url))
def data(self, index, role=Qt.DisplayRole): if role == SORT_ROLE: try: return self.sort_keys[index.row()][index.column()] except IndexError: pass elif role == Qt.DisplayRole: col = index.column() try: entry = self.files[index.row()] except IndexError: return None if col == 0: return entry.word if col == 1: ans = calibre_langcode_to_name(canonicalize_lang(entry.locale.langcode)) or '' if entry.locale.countrycode: ans += ' (%s)' % entry.locale.countrycode return ans if col == 2: return type('')(len(entry.usage)) elif role == Qt.UserRole: try: return self.files[index.row()] except IndexError: pass
def __init__(self, name, table): self.name, self.table = name, table dt = self.metadata['datatype'] self.has_text_data = dt in {'text', 'comments', 'series', 'enumeration'} self.table_type = self.table.table_type self._sort_key = (sort_key if dt in ('text', 'series', 'enumeration') else lambda x: x) # This will be compared to the output of sort_key() which is a # bytestring, therefore it is safer to have it be a bytestring. # Coercing an empty bytestring to unicode will never fail, but the # output of sort_key cannot be coerced to unicode self._default_sort_key = b'' if dt in {'int', 'float', 'rating'}: self._default_sort_key = 0 elif dt == 'bool': self._default_sort_key = None elif dt == 'datetime': self._default_sort_key = UNDEFINED_DATE if self.name == 'languages': self._sort_key = lambda x:sort_key(calibre_langcode_to_name(x)) self.is_multiple = (bool(self.metadata['is_multiple']) or self.name == 'formats') self.sort_sort_key = True if self.is_multiple and '&' in self.metadata['is_multiple']['list_to_ui']: self._sort_key = lambda x: sort_key(author_to_author_sort(x)) self.sort_sort_key = False self.default_value = {} if name == 'identifiers' else () if self.is_multiple else None self.category_formatter = type(u'') if dt == 'rating': self.category_formatter = lambda x:'\u2605'*int(x/2) elif name == 'languages': self.category_formatter = calibre_langcode_to_name self.writer = Writer(self) self.series_field = None
def data(self, index, role=Qt.DisplayRole): if role == SORT_ROLE: try: return self.sort_keys[index.row()][index.column()] except IndexError: pass elif role == Qt.DisplayRole: col = index.column() try: entry = self.files[index.row()] except IndexError: return None if col == 0: return entry.word if col == 1: ans = calibre_langcode_to_name(canonicalize_lang(entry.locale.langcode)) or '' if entry.locale.countrycode: ans += ' (%s)' % entry.locale.countrycode return ans if col == 2: return type('')(len(entry.usage)) elif role == Qt.UserRole: try: return self.files[index.row()] except IndexError: pass
def __init__(self, name, table): self.name, self.table = name, table dt = self.metadata['datatype'] self.has_text_data = dt in {'text', 'comments', 'series', 'enumeration'} self.table_type = self.table.table_type self._sort_key = (sort_key if dt in ('text', 'series', 'enumeration') else lambda x: x) # This will be compared to the output of sort_key() which is a # bytestring, therefore it is safer to have it be a bytestring. # Coercing an empty bytestring to unicode will never fail, but the # output of sort_key cannot be coerced to unicode self._default_sort_key = b'' if dt in {'int', 'float', 'rating'}: self._default_sort_key = 0 elif dt == 'bool': self._default_sort_key = None elif dt == 'datetime': self._default_sort_key = UNDEFINED_DATE if self.name == 'languages': self._sort_key = lambda x:sort_key(calibre_langcode_to_name(x)) self.is_multiple = (bool(self.metadata['is_multiple']) or self.name == 'formats') self.default_value = {} if name == 'identifiers' else () if self.is_multiple else None self.category_formatter = type(u'') if dt == 'rating': self.category_formatter = lambda x:'\u2605'*int(x/2) elif name == 'languages': self.category_formatter = calibre_langcode_to_name self.writer = Writer(self) self.series_field = None
def __init__(self, name, table): self.name, self.table = name, table dt = self.metadata['datatype'] self.has_text_data = dt in { 'text', 'comments', 'series', 'enumeration' } self.table_type = self.table.table_type self._sort_key = (sort_key if dt in ('text', 'series', 'enumeration') else lambda x: x) self._default_sort_key = '' if dt in {'int', 'float', 'rating'}: self._default_sort_key = 0 elif dt == 'bool': self._default_sort_key = None elif dt == 'datetime': self._default_sort_key = UNDEFINED_DATE if self.name == 'languages': self._sort_key = lambda x: sort_key(calibre_langcode_to_name(x)) self.is_multiple = (bool(self.metadata['is_multiple']) or self.name == 'formats') self.category_formatter = type(u'') if dt == 'rating': self.category_formatter = lambda x: '\u2605' * int(x / 2) elif name == 'languages': self.category_formatter = calibre_langcode_to_name self.writer = Writer(self) self.series_field = None
def book_as_json(db, book_id): db = db.new_api with db.safe_read_lock: fmts = db._formats(book_id, verify_formats=False) ans = [] fm = {} for fmt in fmts: m = db.format_metadata(book_id, fmt) if m and m.get('size', 0) > 0: ans.append(fmt) fm[fmt] = m['size'] ans = {'formats': ans, 'format_sizes': fm} if not ans['formats'] and not db.has_id(book_id): return None fm = db.field_metadata for field in fm.all_field_keys(): if field not in IGNORED_FIELDS: add_field(field, db, book_id, ans, fm[field]) ids = ans.get('identifiers') if ids: ans['urls_from_identifiers'] = urls_from_identifiers(ids) langs = ans.get('languages') if langs: ans['lang_names'] = {l:calibre_langcode_to_name(l) for l in langs} return ans
def __init__(self, name, table, bools_are_tristate, get_template_functions): self.name, self.table = name, table dt = self.metadata['datatype'] self.has_text_data = dt in { 'text', 'comments', 'series', 'enumeration' } self.table_type = self.table.table_type self._sort_key = (sort_key if dt in ('text', 'series', 'enumeration') else IDENTITY) # This will be compared to the output of sort_key() which is a # bytestring, therefore it is safer to have it be a bytestring. # Coercing an empty bytestring to unicode will never fail, but the # output of sort_key cannot be coerced to unicode self._default_sort_key = b'' if dt in {'int', 'float', 'rating'}: self._default_sort_key = 0 self._sort_key = numeric_sort_key elif dt == 'bool': self._default_sort_key = None self._sort_key = bool_sort_key(bools_are_tristate) elif dt == 'datetime': self._default_sort_key = UNDEFINED_DATE if tweaks['sort_dates_using_visible_fields']: fmt = None if name in {'timestamp', 'pubdate', 'last_modified'}: fmt = tweaks['gui_%s_display_format' % name] elif self.metadata['is_custom']: fmt = self.metadata.get('display', {}).get('date_format', None) self._sort_key = partial(clean_date_for_sort, fmt=fmt) elif dt == 'comments' or name == 'identifiers': self._default_sort_key = '' if self.name == 'languages': self._sort_key = lambda x: sort_key(calibre_langcode_to_name(x)) self.is_multiple = (bool(self.metadata['is_multiple']) or self.name == 'formats') self.sort_sort_key = True if self.is_multiple and '&' in self.metadata['is_multiple'][ 'list_to_ui']: self._sort_key = lambda x: sort_key(author_to_author_sort(x)) self.sort_sort_key = False self.default_value = {} if name == 'identifiers' else ( ) if self.is_multiple else None self.category_formatter = unicode_type if dt == 'rating': if self.metadata['display'].get('allow_half_stars', False): self.category_formatter = lambda x: rating_to_stars(x, True) else: self.category_formatter = rating_to_stars elif name == 'languages': self.category_formatter = calibre_langcode_to_name self.writer = Writer(self) self.series_field = None self.get_template_functions = get_template_functions
def evaluate(self, formatter, kwargs, mi, locals, lang_codes, localize): retval = [] for c in [c.strip() for c in lang_codes.split(",") if c.strip()]: try: n = calibre_langcode_to_name(c, localize != "0") if n: retval.append(n) except: pass return ", ".join(retval)
def evaluate(self, formatter, kwargs, mi, locals, lang_codes, localize): retval = [] for c in [c.strip() for c in lang_codes.split(',') if c.strip()]: try: n = calibre_langcode_to_name(c, localize != '0') if n: retval.append(n) except: pass return ', '.join(retval)
def __init__(self, name, table, bools_are_tristate, get_template_functions): self.name, self.table = name, table dt = self.metadata['datatype'] self.has_text_data = dt in {'text', 'comments', 'series', 'enumeration'} self.table_type = self.table.table_type self._sort_key = (sort_key if dt in ('text', 'series', 'enumeration') else IDENTITY) # This will be compared to the output of sort_key() which is a # bytestring, therefore it is safer to have it be a bytestring. # Coercing an empty bytestring to unicode will never fail, but the # output of sort_key cannot be coerced to unicode self._default_sort_key = b'' if dt in {'int', 'float', 'rating'}: self._default_sort_key = 0 elif dt == 'bool': self._default_sort_key = None self._sort_key = bool_sort_key(bools_are_tristate) elif dt == 'datetime': self._default_sort_key = UNDEFINED_DATE if tweaks['sort_dates_using_visible_fields']: fmt = None if name in {'timestamp', 'pubdate', 'last_modified'}: fmt = tweaks['gui_%s_display_format' % name] elif self.metadata['is_custom']: fmt = self.metadata.get('display', {}).get('date_format', None) self._sort_key = partial(clean_date_for_sort, fmt=fmt) elif dt == 'comments' or name == 'identifiers': self._default_sort_key = '' if self.name == 'languages': self._sort_key = lambda x:sort_key(calibre_langcode_to_name(x)) self.is_multiple = (bool(self.metadata['is_multiple']) or self.name == 'formats') self.sort_sort_key = True if self.is_multiple and '&' in self.metadata['is_multiple']['list_to_ui']: self._sort_key = lambda x: sort_key(author_to_author_sort(x)) self.sort_sort_key = False self.default_value = {} if name == 'identifiers' else () if self.is_multiple else None self.category_formatter = unicode_type if dt == 'rating': if self.metadata['display'].get('allow_half_stars', False): self.category_formatter = lambda x: rating_to_stars(x, True) else: self.category_formatter = rating_to_stars elif name == 'languages': self.category_formatter = calibre_langcode_to_name self.writer = Writer(self) self.series_field = None self.get_template_functions = get_template_functions
def build_dictionaries(self, reread=False): all_dictionaries = builtin_dictionaries() | custom_dictionaries(reread=reread) languages = defaultdict(lambda : defaultdict(set)) for d in all_dictionaries: for locale in d.locales | {d.primary_locale}: languages[locale.langcode][locale.countrycode].add(d) bf = QFont(self.dictionaries.font()) bf.setBold(True) itf = QFont(self.dictionaries.font()) itf.setItalic(True) self.dictionaries.clear() for lc in sorted(languages, key=lambda x:sort_key(calibre_langcode_to_name(x))): i = QTreeWidgetItem(self.dictionaries, LANG) i.setText(0, calibre_langcode_to_name(lc)) i.setData(0, Qt.UserRole, lc) best_country = getattr(best_locale_for_language(lc), 'countrycode', None) for countrycode in sorted(languages[lc], key=lambda x: country_map()['names'].get(x, x)): j = QTreeWidgetItem(i, COUNTRY) j.setText(0, country_map()['names'].get(countrycode, countrycode)) j.setData(0, Qt.UserRole, countrycode) if countrycode == best_country: j.setData(0, Qt.FontRole, bf) pd = get_dictionary(DictionaryLocale(lc, countrycode)) for dictionary in sorted(languages[lc][countrycode], key=lambda d:d.name): k = QTreeWidgetItem(j, DICTIONARY) pl = calibre_langcode_to_name(dictionary.primary_locale.langcode) if dictionary.primary_locale.countrycode: pl += '-' + dictionary.primary_locale.countrycode.upper() k.setText(0, dictionary.name or (_('<Builtin dictionary for {0}>').format(pl))) k.setData(0, Qt.UserRole, dictionary) if dictionary.name: k.setFlags(k.flags() | Qt.ItemIsEditable) if pd == dictionary: k.setData(0, Qt.FontRole, itf) self.dictionaries.expandAll()
def book_as_json(db, book_id): db = db.new_api with db.safe_read_lock: ans = {'formats': db._formats(book_id)} if not ans['formats'] and not db.has_id(book_id): return None fm = db.field_metadata for field in fm.all_field_keys(): if field not in IGNORED_FIELDS: add_field(field, db, book_id, ans, fm[field]) ids = ans.get('identifiers') if ids: ans['urls_from_identifiers'] = urls_from_identifiers(ids) langs = ans.get('languages') if langs: ans['lang_names'] = {l: calibre_langcode_to_name(l) for l in langs} return ans
def book_as_json(db, book_id): db = db.new_api with db.safe_read_lock: ans = {'formats':db._formats(book_id)} if not ans['formats'] and not db.has_id(book_id): return None fm = db.field_metadata for field in fm.all_field_keys(): if field not in IGNORED_FIELDS: add_field(field, db, book_id, ans, fm[field]) ids = ans.get('identifiers') if ids: ans['urls_from_identifiers'] = urls_from_identifiers(ids) langs = ans.get('languages') if langs: ans['lang_names'] = {l:calibre_langcode_to_name(l) for l in langs} return ans
def __init__(self, name, table): self.name, self.table = name, table self.has_text_data = self.metadata['datatype'] in ('text', 'comments', 'series', 'enumeration') self.table_type = self.table.table_type dt = self.metadata['datatype'] self._sort_key = (sort_key if dt in ('text', 'series', 'enumeration') else lambda x: x) self._default_sort_key = '' if self.metadata['datatype'] in ('int', 'float', 'rating'): self._default_sort_key = 0 elif self.metadata['datatype'] == 'bool': self._default_sort_key = None elif self.metadata['datatype'] == 'datetime': self._default_sort_key = UNDEFINED_DATE if self.name == 'languages': self._sort_key = lambda x:sort_key(calibre_langcode_to_name(x)) self.is_multiple = (bool(self.metadata['is_multiple']) or self.name == 'formats')
def createJSONDictionary(self, metadata): # Create the dictionary that we will convert to JSON text cbi = dict() cbi_container = { 'appID': 'ComicTagger/', 'lastModified': str(datetime.now()), 'ComicBookInfo/1.0': cbi } # helper func def assign(cbi_entry, md_entry): if md_entry is not None: cbi[cbi_entry] = md_entry # helper func def toInt(s): i = None if type(s) in [str, unicode, int]: try: i = int(s) except ValueError: pass return i assign('series', metadata.series) assign('title', metadata.title) assign('issue', metadata.issue) assign('publisher', metadata.publisher) assign('publicationMonth', toInt(metadata.month)) assign('publicationYear', toInt(metadata.year)) assign('numberOfIssues', toInt(metadata.issueCount)) assign('comments', metadata.comments) assign('genre', metadata.genre) assign('volume', toInt(metadata.volume)) assign('numberOfVolumes', toInt(metadata.volumeCount)) assign('language', calibre_langcode_to_name(canonicalize_lang(metadata.language))) assign('country', metadata.country) assign('rating', metadata.criticalRating) assign('credits', metadata.credits) assign('tags', metadata.tags) return cbi_container
def __init__(self, name, table): self.name, self.table = name, table self.has_text_data = self.metadata['datatype'] in ('text', 'comments', 'series', 'enumeration') self.table_type = self.table.table_type dt = self.metadata['datatype'] self._sort_key = (sort_key if dt in ('text', 'series', 'enumeration') else lambda x: x) self._default_sort_key = '' if self.metadata['datatype'] in ('int', 'float', 'rating'): self._default_sort_key = 0 elif self.metadata['datatype'] == 'bool': self._default_sort_key = None elif self.metadata['datatype'] == 'datetime': self._default_sort_key = UNDEFINED_DATE if self.name == 'languages': self._sort_key = lambda x: sort_key(calibre_langcode_to_name(x)) self.is_multiple = (bool(self.metadata['is_multiple']) or self.name == 'formats')
def createJSONDictionary(self, metadata): # Create the dictionary that we will convert to JSON text cbi = dict() cbi_container = {'appID': 'ComicTagger/', 'lastModified': str(datetime.now()), 'ComicBookInfo/1.0': cbi} # helper func def assign(cbi_entry, md_entry): if md_entry is not None: cbi[cbi_entry] = md_entry # helper func def toInt(s): i = None if type(s) in [str, unicode, int]: try: i = int(s) except ValueError: pass return i assign('series', metadata.series) assign('title', metadata.title) assign('issue', metadata.issue) assign('publisher', metadata.publisher) assign('publicationMonth', toInt(metadata.month)) assign('publicationYear', toInt(metadata.year)) assign('numberOfIssues', toInt(metadata.issueCount)) assign('comments', metadata.comments) assign('genre', metadata.genre) assign('volume', toInt(metadata.volume)) assign('numberOfVolumes', toInt(metadata.volumeCount)) assign('language', calibre_langcode_to_name(canonicalize_lang(metadata.language))) assign('country', metadata.country) assign('rating', metadata.criticalRating) assign('credits', metadata.credits) assign('tags', metadata.tags) return cbi_container
def data(self, index, role=Qt.DisplayRole): try: word, locale = self.items[index.row()] except IndexError: return if role == Qt.DisplayRole: col = index.column() if col == 0: return word if col == 1: return '%d' % len(self.words[(word, locale)]) if col == 2: pl = calibre_langcode_to_name(locale.langcode) countrycode = locale.countrycode if countrycode: pl = '%s (%s)' % (pl, countrycode) return pl if col == 3: return '' if self.spell_map[(word, locale)] else '✓' if role == Qt.TextAlignmentRole: return Qt.AlignVCenter | (Qt.AlignLeft if index.column() == 0 else Qt.AlignHCenter)
def __init__(self, name, table): self.name, self.table = name, table dt = self.metadata['datatype'] self.has_text_data = dt in {'text', 'comments', 'series', 'enumeration'} self.table_type = self.table.table_type self._sort_key = (sort_key if dt in ('text', 'series', 'enumeration') else lambda x: x) self._default_sort_key = '' if dt in {'int', 'float', 'rating'}: self._default_sort_key = 0 elif dt == 'bool': self._default_sort_key = None elif dt == 'datetime': self._default_sort_key = UNDEFINED_DATE if self.name == 'languages': self._sort_key = lambda x:sort_key(calibre_langcode_to_name(x)) self.is_multiple = (bool(self.metadata['is_multiple']) or self.name == 'formats') self.category_formatter = type(u'') if dt == 'rating': self.category_formatter = lambda x:'\u2605'*int(x/2) elif name == 'languages': self.category_formatter = calibre_langcode_to_name self.writer = Writer(self) self.series_field = None
def convert_comic_md_to_calibre_md(self, comic_metadata): ''' Maps the entries in the comic_metadata to calibre metadata ''' import unicodedata from calibre.ebooks.metadata import MetaInformation from calibre.utils.date import parse_only_date from datetime import date from calibre.utils.localization import calibre_langcode_to_name if self.comic_md_in_calibre_format: return # synonyms for artists WRITER = ['writer', 'plotter', 'scripter'] PENCILLER = ['artist', 'penciller', 'penciler', 'breakdowns'] INKER = ['inker', 'artist', 'finishes'] COLORIST = ['colorist', 'colourist', 'colorer', 'colourer'] LETTERER = ['letterer'] COVER_ARTIST = ['cover', 'covers', 'coverartist', 'cover artist'] EDITOR = ['editor'] # start with a fresh calibre metadata mi = MetaInformation(None, None) co = comic_metadata # shorten some functions role = partial(get_role, credits=co.credits) update_field = partial(update_calibre_field, target=mi) # Get title, if no title, try to assign series infos if co.title: mi.title = co.title elif co.series: mi.title = co.series if co.issue: mi.title += " " + str(co.issue) else: mi.title = "" # tags if co.tags != [] and prefs['import_tags']: if prefs['overwrite_calibre_tags']: mi.tags = co.tags else: mi.tags = list(set(self.calibre_metadata.tags + co.tags)) # simple metadata update_field("authors", role(WRITER)) update_field("series", co.series) update_field("rating", co.criticalRating) update_field("publisher", co.publisher) # special cases if co.language: update_field("language", calibre_langcode_to_name(co.language)) if co.comments: update_field("comments", co.comments.strip()) # issue if co.issue: if isinstance(co.issue, unicode): mi.series_index = unicodedata.numeric(co.issue) else: mi.series_index = float(co.issue) # pub date puby = co.year pubm = co.month if puby is not None: try: dt = date(int(puby), 6 if pubm is None else int(pubm), 15) dt = parse_only_date(str(dt)) mi.pubdate = dt except: pass # custom columns custom_cols = self.db.field_metadata.custom_field_metadata() update_column = partial(update_custom_column, calibre_metadata=mi, custom_cols=custom_cols) # artists update_column(prefs['penciller_column'], role(PENCILLER)) update_column(prefs['inker_column'], role(INKER)) update_column(prefs['colorist_column'], role(COLORIST)) update_column(prefs['letterer_column'], role(LETTERER)) update_column(prefs['cover_artist_column'], role(COVER_ARTIST)) update_column(prefs['editor_column'], role(EDITOR)) # others update_column(prefs['storyarc_column'], co.storyArc) update_column(prefs['characters_column'], co.characters) update_column(prefs['teams_column'], co.teams) update_column(prefs['locations_column'], co.locations) update_column(prefs['volume_column'], co.volume) update_column(prefs['genre_column'], co.genre) self.comic_md_in_calibre_format = mi
def run(self, path_to_output, opts, db, notification=DummyReporter()): from calibre.library.catalogs.epub_mobi_builder import CatalogBuilder from calibre.utils.logging import default_log as log # If preset specified from the cli, insert stored options from JSON file if hasattr(opts, 'preset') and opts.preset: available_presets = JSONConfig("catalog_presets") if not opts.preset in available_presets: if available_presets: print(_('Error: Preset "%s" not found.' % opts.preset)) print( _('Stored presets: %s' % ', '.join( [p for p in sorted(available_presets.keys())]))) else: print(_('Error: No stored presets.')) return 1 # Copy the relevant preset values to the opts object for item in available_presets[opts.preset]: if not item in [ 'exclusion_rules_tw', 'format', 'prefix_rules_tw' ]: setattr(opts, item, available_presets[opts.preset][item]) # Provide an unconnected device opts.connected_device = { 'is_device_connected': False, 'kind': None, 'name': None, 'save_template': None, 'serial': None, 'storage': None, } # Convert prefix_rules and exclusion_rules from JSON lists to tuples prs = [] for rule in opts.prefix_rules: prs.append(tuple(rule)) opts.prefix_rules = tuple(prs) ers = [] for rule in opts.exclusion_rules: ers.append(tuple(rule)) opts.exclusion_rules = tuple(ers) opts.log = log opts.fmt = self.fmt = path_to_output.rpartition('.')[2] # Add local options opts.creator = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) opts.creator_sort_as = '%s %s' % ('calibre', strftime('%Y-%m-%d')) opts.connected_kindle = False # Finalize output_profile op = opts.output_profile if op is None: op = 'default' if opts.connected_device['name'] and 'kindle' in opts.connected_device[ 'name'].lower(): opts.connected_kindle = True if opts.connected_device['serial'] and \ opts.connected_device['serial'][:4] in ['B004', 'B005']: op = "kindle_dx" else: op = "kindle" opts.description_clip = 380 if op.endswith( 'dx') or 'kindle' not in op else 100 opts.author_clip = 100 if op.endswith( 'dx') or 'kindle' not in op else 60 opts.output_profile = op opts.basename = "Catalog" opts.cli_environment = not hasattr(opts, 'sync') # Hard-wired to always sort descriptions by author, with series after non-series opts.sort_descriptions_by_author = True build_log = [] build_log.append( u"%s('%s'): Generating %s %sin %s environment, locale: '%s'" % (self.name, current_library_name(), self.fmt, 'for %s ' % opts.output_profile if opts.output_profile else '', 'CLI' if opts.cli_environment else 'GUI', calibre_langcode_to_name(canonicalize_lang(get_lang()), localize=False))) # If exclude_genre is blank, assume user wants all tags as genres if opts.exclude_genre.strip() == '': #opts.exclude_genre = '\[^.\]' #build_log.append(" converting empty exclude_genre to '\[^.\]'") opts.exclude_genre = 'a^' build_log.append(" converting empty exclude_genre to 'a^'") if opts.connected_device['is_device_connected'] and \ opts.connected_device['kind'] == 'device': if opts.connected_device['serial']: build_log.append(u" connected_device: '%s' #%s%s " % \ (opts.connected_device['name'], opts.connected_device['serial'][0:4], 'x' * (len(opts.connected_device['serial']) - 4))) for storage in opts.connected_device['storage']: if storage: build_log.append(u" mount point: %s" % storage) else: build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) try: for storage in opts.connected_device['storage']: if storage: build_log.append(u" mount point: %s" % storage) except: build_log.append(u" (no mount points)") else: build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) opts_dict = vars(opts) if opts_dict['ids']: build_log.append(" book count: %d" % len(opts_dict['ids'])) sections_list = [] if opts.generate_authors: sections_list.append('Authors') if opts.generate_titles: sections_list.append('Titles') if opts.generate_series: sections_list.append('Series') if opts.generate_genres: sections_list.append('Genres') if opts.generate_recently_added: sections_list.append('Recently Added') if opts.generate_descriptions: sections_list.append('Descriptions') if not sections_list: if opts.cli_environment: opts.log.warn( '*** No Section switches specified, enabling all Sections ***' ) opts.generate_authors = True opts.generate_titles = True opts.generate_series = True opts.generate_genres = True opts.generate_recently_added = True opts.generate_descriptions = True sections_list = [ 'Authors', 'Titles', 'Series', 'Genres', 'Recently Added', 'Descriptions' ] else: opts.log.warn( '\n*** No enabled Sections, terminating catalog generation ***' ) return [ "No Included Sections", "No enabled Sections.\nCheck E-book options tab\n'Included sections'\n" ] if opts.fmt == 'mobi' and sections_list == ['Descriptions']: warning = _( "\n*** Adding 'By Authors' Section required for MOBI output ***" ) opts.log.warn(warning) sections_list.insert(0, 'Authors') opts.generate_authors = True opts.log(u" Sections: %s" % ', '.join(sections_list)) opts.section_list = sections_list # Limit thumb_width to 1.0" - 2.0" try: if float(opts.thumb_width) < float(self.THUMB_SMALLEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = self.THUMB_SMALLEST if float(opts.thumb_width) > float(self.THUMB_LARGEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_LARGEST)) opts.thumb_width = self.THUMB_LARGEST opts.thumb_width = "%.2f" % float(opts.thumb_width) except: log.error("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = "1.0" # eval prefix_rules if passed from command line if type(opts.prefix_rules) is not tuple: try: opts.prefix_rules = eval(opts.prefix_rules) except: log.error("malformed --prefix-rules: %s" % opts.prefix_rules) raise for rule in opts.prefix_rules: if len(rule) != 4: log.error( "incorrect number of args for --prefix-rules: %s" % repr(rule)) # eval exclusion_rules if passed from command line if type(opts.exclusion_rules) is not tuple: try: opts.exclusion_rules = eval(opts.exclusion_rules) except: log.error("malformed --exclusion-rules: %s" % opts.exclusion_rules) raise for rule in opts.exclusion_rules: if len(rule) != 3: log.error( "incorrect number of args for --exclusion-rules: %s" % repr(rule)) # Display opts keys = sorted(opts_dict.keys()) build_log.append(" opts:") for key in keys: if key in [ 'catalog_title', 'author_clip', 'connected_kindle', 'creator', 'cross_reference_authors', 'description_clip', 'exclude_book_marker', 'exclude_genre', 'exclude_tags', 'exclusion_rules', 'fmt', 'genre_source_field', 'header_note_source_field', 'merge_comments_rule', 'output_profile', 'prefix_rules', 'preset', 'read_book_marker', 'search_text', 'sort_by', 'sort_descriptions_by_author', 'sync', 'thumb_width', 'use_existing_cover', 'wishlist_tag' ]: build_log.append(" %s: %s" % (key, repr(opts_dict[key]))) if opts.verbose: log('\n'.join(line for line in build_log)) # Capture start_time opts.start_time = time.time() self.opts = opts if opts.verbose: log.info(" Begin catalog source generation (%s)" % str( datetime.timedelta(seconds=int(time.time() - opts.start_time)))) # Launch the Catalog builder catalog = CatalogBuilder(db, opts, self, report_progress=notification) try: catalog.build_sources() if opts.verbose: log.info(" Completed catalog source generation (%s)\n" % str( datetime.timedelta(seconds=int(time.time() - opts.start_time)))) except (AuthorSortMismatchException, EmptyCatalogException), e: log.error(" *** Terminated catalog generation: %s ***" % e)
def run(self, path_to_output, opts, db, notification=DummyReporter()): from calibre.library.catalogs.epub_mobi_builder import CatalogBuilder from calibre.utils.logging import default_log as log from calibre.utils.config import JSONConfig # If preset specified from the cli, insert stored options from JSON file if hasattr(opts, 'preset') and opts.preset: available_presets = JSONConfig("catalog_presets") if opts.preset not in available_presets: if available_presets: print(_('Error: Preset "%s" not found.' % opts.preset)) print(_('Stored presets: %s' % ', '.join([p for p in sorted(available_presets.keys())]))) else: print(_('Error: No stored presets.')) return 1 # Copy the relevant preset values to the opts object for item in available_presets[opts.preset]: if item not in ['exclusion_rules_tw', 'format', 'prefix_rules_tw']: setattr(opts, item, available_presets[opts.preset][item]) # Provide an unconnected device opts.connected_device = { 'is_device_connected': False, 'kind': None, 'name': None, 'save_template': None, 'serial': None, 'storage': None, } # Convert prefix_rules and exclusion_rules from JSON lists to tuples prs = [] for rule in opts.prefix_rules: prs.append(tuple(rule)) opts.prefix_rules = tuple(prs) ers = [] for rule in opts.exclusion_rules: ers.append(tuple(rule)) opts.exclusion_rules = tuple(ers) opts.log = log opts.fmt = self.fmt = path_to_output.rpartition('.')[2] # Add local options opts.creator = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) opts.creator_sort_as = '%s %s' % ('calibre', strftime('%Y-%m-%d')) opts.connected_kindle = False # Finalize output_profile op = opts.output_profile if op is None: op = 'default' if opts.connected_device['name'] and 'kindle' in opts.connected_device['name'].lower(): opts.connected_kindle = True if opts.connected_device['serial'] and \ opts.connected_device['serial'][:4] in ['B004', 'B005']: op = "kindle_dx" else: op = "kindle" opts.description_clip = 380 if op.endswith('dx') or 'kindle' not in op else 100 opts.author_clip = 100 if op.endswith('dx') or 'kindle' not in op else 60 opts.output_profile = op opts.basename = "Catalog" opts.cli_environment = not hasattr(opts, 'sync') # Hard-wired to always sort descriptions by author, with series after non-series opts.sort_descriptions_by_author = True build_log = [] build_log.append(u"%s('%s'): Generating %s %sin %s environment, locale: '%s'" % (self.name, current_library_name(), self.fmt, 'for %s ' % opts.output_profile if opts.output_profile else '', 'CLI' if opts.cli_environment else 'GUI', calibre_langcode_to_name(canonicalize_lang(get_lang()), localize=False)) ) # If exclude_genre is blank, assume user wants all tags as genres if opts.exclude_genre.strip() == '': # opts.exclude_genre = '\[^.\]' # build_log.append(" converting empty exclude_genre to '\[^.\]'") opts.exclude_genre = 'a^' build_log.append(" converting empty exclude_genre to 'a^'") if opts.connected_device['is_device_connected'] and \ opts.connected_device['kind'] == 'device': if opts.connected_device['serial']: build_log.append(u" connected_device: '%s' #%s%s " % (opts.connected_device['name'], opts.connected_device['serial'][0:4], 'x' * (len(opts.connected_device['serial']) - 4))) for storage in opts.connected_device['storage']: if storage: build_log.append(u" mount point: %s" % storage) else: build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) try: for storage in opts.connected_device['storage']: if storage: build_log.append(u" mount point: %s" % storage) except: build_log.append(u" (no mount points)") else: build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) opts_dict = vars(opts) if opts_dict['ids']: build_log.append(" book count: %d" % len(opts_dict['ids'])) sections_list = [] if opts.generate_authors: sections_list.append('Authors') if opts.generate_titles: sections_list.append('Titles') if opts.generate_series: sections_list.append('Series') if opts.generate_genres: sections_list.append('Genres') if opts.generate_recently_added: sections_list.append('Recently Added') if opts.generate_descriptions: sections_list.append('Descriptions') if not sections_list: if opts.cli_environment: opts.log.warn('*** No Section switches specified, enabling all Sections ***') opts.generate_authors = True opts.generate_titles = True opts.generate_series = True opts.generate_genres = True opts.generate_recently_added = True opts.generate_descriptions = True sections_list = ['Authors', 'Titles', 'Series', 'Genres', 'Recently Added', 'Descriptions'] else: opts.log.warn('\n*** No enabled Sections, terminating catalog generation ***') return ["No Included Sections", "No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"] if opts.fmt == 'mobi' and sections_list == ['Descriptions']: warning = _("\n*** Adding 'By authors' section required for MOBI output ***") opts.log.warn(warning) sections_list.insert(0, 'Authors') opts.generate_authors = True opts.log(u" Sections: %s" % ', '.join(sections_list)) opts.section_list = sections_list # Limit thumb_width to 1.0" - 2.0" try: if float(opts.thumb_width) < float(self.THUMB_SMALLEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = self.THUMB_SMALLEST if float(opts.thumb_width) > float(self.THUMB_LARGEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_LARGEST)) opts.thumb_width = self.THUMB_LARGEST opts.thumb_width = "%.2f" % float(opts.thumb_width) except: log.error("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = "1.0" # eval prefix_rules if passed from command line if type(opts.prefix_rules) is not tuple: try: opts.prefix_rules = eval(opts.prefix_rules) except: log.error("malformed --prefix-rules: %s" % opts.prefix_rules) raise for rule in opts.prefix_rules: if len(rule) != 4: log.error("incorrect number of args for --prefix-rules: %s" % repr(rule)) # eval exclusion_rules if passed from command line if type(opts.exclusion_rules) is not tuple: try: opts.exclusion_rules = eval(opts.exclusion_rules) except: log.error("malformed --exclusion-rules: %s" % opts.exclusion_rules) raise for rule in opts.exclusion_rules: if len(rule) != 3: log.error("incorrect number of args for --exclusion-rules: %s" % repr(rule)) # Display opts keys = sorted(opts_dict.keys()) build_log.append(" opts:") for key in keys: if key in ['catalog_title', 'author_clip', 'connected_kindle', 'creator', 'cross_reference_authors', 'description_clip', 'exclude_book_marker', 'exclude_genre', 'exclude_tags', 'exclusion_rules', 'fmt', 'genre_source_field', 'header_note_source_field', 'merge_comments_rule', 'output_profile', 'prefix_rules', 'preset', 'read_book_marker', 'search_text', 'sort_by', 'sort_descriptions_by_author', 'sync', 'thumb_width', 'use_existing_cover', 'wishlist_tag']: build_log.append(" %s: %s" % (key, repr(opts_dict[key]))) if opts.verbose: log('\n'.join(line for line in build_log)) # Capture start_time opts.start_time = time.time() self.opts = opts if opts.verbose: log.info(" Begin catalog source generation (%s)" % str(datetime.timedelta(seconds=int(time.time() - opts.start_time)))) # Launch the Catalog builder catalog = CatalogBuilder(db, opts, self, report_progress=notification) try: catalog.build_sources() if opts.verbose: log.info(" Completed catalog source generation (%s)\n" % str(datetime.timedelta(seconds=int(time.time() - opts.start_time)))) except (AuthorSortMismatchException, EmptyCatalogException) as e: log.error(" *** Terminated catalog generation: %s ***" % e) except: log.error(" unhandled exception in catalog generator") raise else: recommendations = [] recommendations.append(('remove_fake_margins', False, OptionRecommendation.HIGH)) recommendations.append(('comments', '', OptionRecommendation.HIGH)) """ >>> Use to debug generated catalog code before pipeline conversion <<< """ GENERATE_DEBUG_EPUB = False if GENERATE_DEBUG_EPUB: catalog_debug_path = os.path.join(os.path.expanduser('~'), 'Desktop', 'Catalog debug') setattr(opts, 'debug_pipeline', os.path.expanduser(catalog_debug_path)) dp = getattr(opts, 'debug_pipeline', None) if dp is not None: recommendations.append(('debug_pipeline', dp, OptionRecommendation.HIGH)) if opts.output_profile and opts.output_profile.startswith("kindle"): recommendations.append(('output_profile', opts.output_profile, OptionRecommendation.HIGH)) recommendations.append(('book_producer', opts.output_profile, OptionRecommendation.HIGH)) if opts.fmt == 'mobi': recommendations.append(('no_inline_toc', True, OptionRecommendation.HIGH)) recommendations.append(('verbose', 2, OptionRecommendation.HIGH)) # Use existing cover or generate new cover cpath = None existing_cover = False try: search_text = 'title:"%s" author:%s' % ( opts.catalog_title.replace('"', '\\"'), 'calibre') matches = db.search(search_text, return_matches=True, sort_results=False) if matches: cpath = db.cover(matches[0], index_is_id=True, as_path=True) if cpath and os.path.exists(cpath): existing_cover = True except: pass if self.opts.use_existing_cover and not existing_cover: log.warning("no existing catalog cover found") if self.opts.use_existing_cover and existing_cover: recommendations.append(('cover', cpath, OptionRecommendation.HIGH)) log.info("using existing catalog cover") else: from calibre.ebooks.covers import calibre_cover2 log.info("replacing catalog cover") new_cover_path = PersistentTemporaryFile(suffix='.jpg') new_cover = calibre_cover2(opts.catalog_title, 'calibre') new_cover_path.write(new_cover) new_cover_path.close() recommendations.append(('cover', new_cover_path.name, OptionRecommendation.HIGH)) # Run ebook-convert from calibre.ebooks.conversion.plumber import Plumber plumber = Plumber(os.path.join(catalog.catalog_path, opts.basename + '.opf'), path_to_output, log, report_progress=notification, abort_after_input_dump=False) plumber.merge_ui_recommendations(recommendations) plumber.run() try: os.remove(cpath) except: pass if GENERATE_DEBUG_EPUB: from calibre.ebooks.epub import initialize_container from calibre.ebooks.tweak import zip_rebuilder from calibre.utils.zipfile import ZipFile input_path = os.path.join(catalog_debug_path, 'input') epub_shell = os.path.join(catalog_debug_path, 'epub_shell.zip') initialize_container(epub_shell, opf_name='content.opf') with ZipFile(epub_shell, 'r') as zf: zf.extractall(path=input_path) os.remove(epub_shell) zip_rebuilder(input_path, os.path.join(catalog_debug_path, 'input.epub')) if opts.verbose: log.info(" Catalog creation complete (%s)\n" % str(datetime.timedelta(seconds=int(time.time() - opts.start_time)))) # returns to gui2.actions.catalog:catalog_generated() return catalog.error
def key(w): locale = w[1] return (calibre_langcode_to_name(locale.langcode), locale.countrycode)
def convert_comic_md_to_calibre_md(self, comic_metadata): ''' Maps the entries in the comic_metadata to calibre metadata ''' import unicodedata from calibre.ebooks.metadata import MetaInformation from calibre.utils.date import parse_only_date from datetime import date from calibre.utils.localization import calibre_langcode_to_name if self.comic_md_in_calibre_format: return # start with a fresh calibre metadata mi = MetaInformation(None, None) co = comic_metadata # shorten some functions role = partial(get_role, credits=co.credits) update_field = partial(update_calibre_field, target=mi) # Get title, if no title, try to assign series infos if co.title: mi.title = co.title elif co.series: mi.title = co.series if co.issue: mi.title += " " + str(co.issue) else: mi.title = "" # tags if co.tags != [] and prefs['import_tags']: if prefs['overwrite_calibre_tags']: mi.tags = co.tags else: mi.tags = list(set(self.calibre_metadata.tags + co.tags)) # simple metadata update_field("authors", role(WRITER)) update_field("series", co.series) update_field("rating", co.criticalRating) update_field("publisher", co.publisher) # special cases if co.language: update_field("language", calibre_langcode_to_name(co.language)) if co.comments: update_field("comments", co.comments.strip()) # issue if co.issue: try: if not python3 and isinstance(co.issue, unicode): mi.series_index = unicodedata.numeric(co.issue) else: mi.series_index = float(co.issue) except ValueError: pass # pub date puby = co.year pubm = co.month if puby is not None: try: dt = date(int(puby), 6 if pubm is None else int(pubm), 15) dt = parse_only_date(str(dt)) mi.pubdate = dt except: pass # custom columns update_column = partial( update_custom_column, calibre_metadata=mi, custom_cols=self.db.field_metadata.custom_field_metadata()) # artists update_column(prefs['penciller_column'], role(PENCILLER)) update_column(prefs['inker_column'], role(INKER)) update_column(prefs['colorist_column'], role(COLORIST)) update_column(prefs['letterer_column'], role(LETTERER)) update_column(prefs['cover_artist_column'], role(COVER_ARTIST)) update_column(prefs['editor_column'], role(EDITOR)) # others update_column(prefs['storyarc_column'], co.storyArc) update_column(prefs['characters_column'], co.characters) update_column(prefs['teams_column'], co.teams) update_column(prefs['locations_column'], co.locations) update_column(prefs['genre_column'], co.genre) ensure_int(co.issueCount, update_column, prefs['count_column'], co.issueCount) ensure_int(co.volume, update_column, prefs['volume_column'], co.volume) if prefs['auto_count_pages']: update_column(prefs['pages_column'], self.count_pages()) else: update_column(prefs['pages_column'], co.pageCount) if prefs['get_image_sizes']: update_column(prefs['image_size_column'], self.get_picture_size()) update_column(prefs['comicvine_column'], '<a href="{}">Comic Vine</a>'.format(co.webLink)) update_column(prefs['manga_column'], co.manga) self.comic_md_in_calibre_format = mi
def run(self, path_to_output, opts, db, notification=DummyReporter()): from calibre.library.catalogs.epub_mobi_builder import CatalogBuilder from calibre.utils.logging import default_log as log opts.log = log opts.fmt = self.fmt = path_to_output.rpartition('.')[2] # Add local options opts.creator = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) opts.creator_sort_as = '%s %s' % ('calibre', strftime('%Y-%m-%d')) opts.connected_kindle = False # Finalize output_profile op = opts.output_profile if op is None: op = 'default' if opts.connected_device['name'] and 'kindle' in opts.connected_device['name'].lower(): opts.connected_kindle = True if opts.connected_device['serial'] and \ opts.connected_device['serial'][:4] in ['B004', 'B005']: op = "kindle_dx" else: op = "kindle" opts.description_clip = 380 if op.endswith('dx') or 'kindle' not in op else 100 opts.author_clip = 100 if op.endswith('dx') or 'kindle' not in op else 60 opts.output_profile = op opts.basename = "Catalog" opts.cli_environment = not hasattr(opts, 'sync') # Hard-wired to always sort descriptions by author, with series after non-series opts.sort_descriptions_by_author = True build_log = [] build_log.append(u"%s('%s'): Generating %s %sin %s environment, locale: '%s'" % (self.name, current_library_name(), self.fmt, 'for %s ' % opts.output_profile if opts.output_profile else '', 'CLI' if opts.cli_environment else 'GUI', calibre_langcode_to_name(canonicalize_lang(get_lang()), localize=False)) ) # If exclude_genre is blank, assume user wants all tags as genres if opts.exclude_genre.strip() == '': #opts.exclude_genre = '\[^.\]' #build_log.append(" converting empty exclude_genre to '\[^.\]'") opts.exclude_genre = 'a^' build_log.append(" converting empty exclude_genre to 'a^'") if opts.connected_device['is_device_connected'] and \ opts.connected_device['kind'] == 'device': if opts.connected_device['serial']: build_log.append(u" connected_device: '%s' #%s%s " % \ (opts.connected_device['name'], opts.connected_device['serial'][0:4], 'x' * (len(opts.connected_device['serial']) - 4))) for storage in opts.connected_device['storage']: if storage: build_log.append(u" mount point: %s" % storage) else: build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) try: for storage in opts.connected_device['storage']: if storage: build_log.append(u" mount point: %s" % storage) except: build_log.append(u" (no mount points)") else: build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) opts_dict = vars(opts) if opts_dict['ids']: build_log.append(" book count: %d" % len(opts_dict['ids'])) sections_list = [] if opts.generate_authors: sections_list.append('Authors') if opts.generate_titles: sections_list.append('Titles') if opts.generate_series: sections_list.append('Series') if opts.generate_genres: sections_list.append('Genres') if opts.generate_recently_added: sections_list.append('Recently Added') if opts.generate_descriptions: sections_list.append('Descriptions') if not sections_list: if opts.cli_environment: opts.log.warn('*** No Section switches specified, enabling all Sections ***') opts.generate_authors = True opts.generate_titles = True opts.generate_series = True opts.generate_genres = True opts.generate_recently_added = True opts.generate_descriptions = True sections_list = ['Authors', 'Titles', 'Series', 'Genres', 'Recently Added', 'Descriptions'] else: opts.log.warn('\n*** No enabled Sections, terminating catalog generation ***') return ["No Included Sections", "No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"] if opts.fmt == 'mobi' and sections_list == ['Descriptions']: warning = _("\n*** Adding 'By Authors' Section required for MOBI output ***") opts.log.warn(warning) sections_list.insert(0, 'Authors') opts.generate_authors = True opts.log(u" Sections: %s" % ', '.join(sections_list)) opts.section_list = sections_list # Limit thumb_width to 1.0" - 2.0" try: if float(opts.thumb_width) < float(self.THUMB_SMALLEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = self.THUMB_SMALLEST if float(opts.thumb_width) > float(self.THUMB_LARGEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_LARGEST)) opts.thumb_width = self.THUMB_LARGEST opts.thumb_width = "%.2f" % float(opts.thumb_width) except: log.error("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = "1.0" # eval prefix_rules if passed from command line if type(opts.prefix_rules) is not tuple: try: opts.prefix_rules = eval(opts.prefix_rules) except: log.error("malformed --prefix-rules: %s" % opts.prefix_rules) raise for rule in opts.prefix_rules: if len(rule) != 4: log.error("incorrect number of args for --prefix-rules: %s" % repr(rule)) # eval exclusion_rules if passed from command line if type(opts.exclusion_rules) is not tuple: try: opts.exclusion_rules = eval(opts.exclusion_rules) except: log.error("malformed --exclusion-rules: %s" % opts.exclusion_rules) raise for rule in opts.exclusion_rules: if len(rule) != 3: log.error("incorrect number of args for --exclusion-rules: %s" % repr(rule)) # Display opts keys = opts_dict.keys() keys.sort() build_log.append(" opts:") for key in keys: if key in ['catalog_title', 'author_clip', 'connected_kindle', 'creator', 'cross_reference_authors', 'description_clip', 'exclude_book_marker', 'exclude_genre', 'exclude_tags', 'exclusion_rules', 'fmt', 'genre_source_field', 'header_note_source_field', 'merge_comments_rule', 'output_profile', 'prefix_rules', 'read_book_marker', 'search_text', 'sort_by', 'sort_descriptions_by_author', 'sync', 'thumb_width', 'use_existing_cover', 'wishlist_tag']: build_log.append(" %s: %s" % (key, repr(opts_dict[key]))) if opts.verbose: log('\n'.join(line for line in build_log)) self.opts = opts # Launch the Catalog builder catalog = CatalogBuilder(db, opts, self, report_progress=notification) if opts.verbose: log.info(" Begin catalog source generation") try: catalog.build_sources() if opts.verbose: log.info(" Completed catalog source generation\n") except (AuthorSortMismatchException, EmptyCatalogException), e: log.error(" *** Terminated catalog generation: %s ***" % e)
def locale_sort_key(loc): try: return lsk_cache[loc] except KeyError: lsk_cache[loc] = (psk(calibre_langcode_to_name(canonicalize_lang(loc[0]))), psk(loc[1] or '')) return lsk_cache[loc]
def test_sorting(self): # {{{ 'Test sorting' cache = self.init_cache() ae = self.assertEqual lmap = {x:cache.field_for('languages', x) for x in (1, 2, 3)} lq = sorted(lmap, key=lambda x: calibre_langcode_to_name((lmap[x] or ('',))[0])) for field, order in iteritems({ 'title' : [2, 1, 3], 'authors': [2, 1, 3], 'series' : [3, 1, 2], 'tags' : [3, 1, 2], 'rating' : [3, 2, 1], # 'identifiers': [3, 2, 1], There is no stable sort since 1 and # 2 have the same identifier keys # 'last_modified': [3, 2, 1], There is no stable sort as two # records have the exact same value 'timestamp': [2, 1, 3], 'pubdate' : [1, 2, 3], 'publisher': [3, 2, 1], 'languages': lq, 'comments': [3, 2, 1], '#enum' : [3, 2, 1], '#authors' : [3, 2, 1], '#date': [3, 1, 2], '#rating':[3, 2, 1], '#series':[3, 2, 1], '#tags':[3, 2, 1], '#yesno':[2, 1, 3], '#comments':[3, 2, 1], 'id': [1, 2, 3], }): x = list(reversed(order)) ae(order, cache.multisort([(field, True)], ids_to_sort=x), 'Ascending sort of %s failed'%field) ae(x, cache.multisort([(field, False)], ids_to_sort=order), 'Descending sort of %s failed'%field) # Test sorting of is_multiple fields. # Author like fields should be sorted by generating sort names from the # actual values in entry order for field in ('authors', '#authors'): ae( cache.set_field(field, {1:('aa bb', 'bb cc', 'cc dd'), 2:('bb aa', 'xx yy'), 3: ('aa bb', 'bb aa')}), {1, 2, 3}) ae([2, 3, 1], cache.multisort([(field, True)], ids_to_sort=(1, 2, 3))) ae([1, 3, 2], cache.multisort([(field, False)], ids_to_sort=(1, 2, 3))) # All other is_multiple fields should be sorted by sorting the values # for each book and using that as the sort key for field in ('tags', '#tags'): ae( cache.set_field(field, {1:('b', 'a'), 2:('c', 'y'), 3: ('b', 'z')}), {1, 2, 3}) ae([1, 3, 2], cache.multisort([(field, True)], ids_to_sort=(1, 2, 3))) ae([2, 3, 1], cache.multisort([(field, False)], ids_to_sort=(1, 2, 3))) # Test tweak to sort dates by visible format from calibre.utils.config_base import Tweak ae(cache.set_field('pubdate', {1:p('2001-3-3'), 2:p('2002-2-3'), 3:p('2003-1-3')}), {1, 2, 3}) ae([1, 2, 3], cache.multisort([('pubdate', True)])) with Tweak('gui_pubdate_display_format', 'MMM'), Tweak('sort_dates_using_visible_fields', True): c2 = self.init_cache() ae([3, 2, 1], c2.multisort([('pubdate', True)])) # Test bool sorting when not tristate cache.set_pref('bools_are_tristate', False) c2 = self.init_cache() ae([2, 3, 1], c2.multisort([('#yesno', True), ('id', False)])) # Test subsorting ae([3, 2, 1], cache.multisort([('identifiers', True), ('title', True)]), 'Subsort failed') from calibre.ebooks.metadata.book.base import Metadata for i in range(7): cache.create_book_entry(Metadata('title%d' % i), apply_import_tags=False) cache.create_custom_column('one', 'CC1', 'int', False) cache.create_custom_column('two', 'CC2', 'int', False) cache.create_custom_column('three', 'CC3', 'int', False) cache.close() cache = self.init_cache() cache.set_field('#one', {(i+(5*m)):m for m in (0, 1) for i in range(1, 6)}) cache.set_field('#two', {i+(m*3):m for m in (0, 1, 2) for i in (1, 2, 3)}) cache.set_field('#two', {10:2}) cache.set_field('#three', {i:i for i in range(1, 11)}) ae(list(range(1, 11)), cache.multisort([('#one', True), ('#two', True)], ids_to_sort=sorted(cache.all_book_ids()))) ae([4, 5, 1, 2, 3, 7,8, 9, 10, 6], cache.multisort([('#one', True), ('#two', False)], ids_to_sort=sorted(cache.all_book_ids()))) ae([5, 4, 3, 2, 1, 10, 9, 8, 7, 6], cache.multisort([('#one', True), ('#two', False), ('#three', False)], ids_to_sort=sorted(cache.all_book_ids())))
def run(self, path_to_output, opts, db, notification=DummyReporter()): from calibre.library.catalogs.epub_mobi_builder import CatalogBuilder from calibre.utils.logging import default_log as log from calibre.utils.config import JSONConfig # If preset specified from the cli, insert stored options from JSON file if hasattr(opts, "preset") and opts.preset: available_presets = JSONConfig("catalog_presets") if not opts.preset in available_presets: if available_presets: print(_('Error: Preset "%s" not found.' % opts.preset)) print(_("Stored presets: %s" % ", ".join([p for p in sorted(available_presets.keys())]))) else: print(_("Error: No stored presets.")) return 1 # Copy the relevant preset values to the opts object for item in available_presets[opts.preset]: if not item in ["exclusion_rules_tw", "format", "prefix_rules_tw"]: setattr(opts, item, available_presets[opts.preset][item]) # Provide an unconnected device opts.connected_device = { "is_device_connected": False, "kind": None, "name": None, "save_template": None, "serial": None, "storage": None, } # Convert prefix_rules and exclusion_rules from JSON lists to tuples prs = [] for rule in opts.prefix_rules: prs.append(tuple(rule)) opts.prefix_rules = tuple(prs) ers = [] for rule in opts.exclusion_rules: ers.append(tuple(rule)) opts.exclusion_rules = tuple(ers) opts.log = log opts.fmt = self.fmt = path_to_output.rpartition(".")[2] # Add local options opts.creator = "%s, %s %s, %s" % (strftime("%A"), strftime("%B"), strftime("%d").lstrip("0"), strftime("%Y")) opts.creator_sort_as = "%s %s" % ("calibre", strftime("%Y-%m-%d")) opts.connected_kindle = False # Finalize output_profile op = opts.output_profile if op is None: op = "default" if opts.connected_device["name"] and "kindle" in opts.connected_device["name"].lower(): opts.connected_kindle = True if opts.connected_device["serial"] and opts.connected_device["serial"][:4] in ["B004", "B005"]: op = "kindle_dx" else: op = "kindle" opts.description_clip = 380 if op.endswith("dx") or "kindle" not in op else 100 opts.author_clip = 100 if op.endswith("dx") or "kindle" not in op else 60 opts.output_profile = op opts.basename = "Catalog" opts.cli_environment = not hasattr(opts, "sync") # Hard-wired to always sort descriptions by author, with series after non-series opts.sort_descriptions_by_author = True build_log = [] build_log.append( "%s('%s'): Generating %s %sin %s environment, locale: '%s'" % ( self.name, current_library_name(), self.fmt, "for %s " % opts.output_profile if opts.output_profile else "", "CLI" if opts.cli_environment else "GUI", calibre_langcode_to_name(canonicalize_lang(get_lang()), localize=False), ) ) # If exclude_genre is blank, assume user wants all tags as genres if opts.exclude_genre.strip() == "": # opts.exclude_genre = '\[^.\]' # build_log.append(" converting empty exclude_genre to '\[^.\]'") opts.exclude_genre = "a^" build_log.append(" converting empty exclude_genre to 'a^'") if opts.connected_device["is_device_connected"] and opts.connected_device["kind"] == "device": if opts.connected_device["serial"]: build_log.append( " connected_device: '%s' #%s%s " % ( opts.connected_device["name"], opts.connected_device["serial"][0:4], "x" * (len(opts.connected_device["serial"]) - 4), ) ) for storage in opts.connected_device["storage"]: if storage: build_log.append(" mount point: %s" % storage) else: build_log.append(" connected_device: '%s'" % opts.connected_device["name"]) try: for storage in opts.connected_device["storage"]: if storage: build_log.append(" mount point: %s" % storage) except: build_log.append(" (no mount points)") else: build_log.append(" connected_device: '%s'" % opts.connected_device["name"]) opts_dict = vars(opts) if opts_dict["ids"]: build_log.append(" book count: %d" % len(opts_dict["ids"])) sections_list = [] if opts.generate_authors: sections_list.append("Authors") if opts.generate_titles: sections_list.append("Titles") if opts.generate_series: sections_list.append("Series") if opts.generate_genres: sections_list.append("Genres") if opts.generate_recently_added: sections_list.append("Recently Added") if opts.generate_descriptions: sections_list.append("Descriptions") if not sections_list: if opts.cli_environment: opts.log.warn("*** No Section switches specified, enabling all Sections ***") opts.generate_authors = True opts.generate_titles = True opts.generate_series = True opts.generate_genres = True opts.generate_recently_added = True opts.generate_descriptions = True sections_list = ["Authors", "Titles", "Series", "Genres", "Recently Added", "Descriptions"] else: opts.log.warn("\n*** No enabled Sections, terminating catalog generation ***") return ["No Included Sections", "No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"] if opts.fmt == "mobi" and sections_list == ["Descriptions"]: warning = _("\n*** Adding 'By Authors' Section required for MOBI output ***") opts.log.warn(warning) sections_list.insert(0, "Authors") opts.generate_authors = True opts.log(" Sections: %s" % ", ".join(sections_list)) opts.section_list = sections_list # Limit thumb_width to 1.0" - 2.0" try: if float(opts.thumb_width) < float(self.THUMB_SMALLEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = self.THUMB_SMALLEST if float(opts.thumb_width) > float(self.THUMB_LARGEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_LARGEST)) opts.thumb_width = self.THUMB_LARGEST opts.thumb_width = "%.2f" % float(opts.thumb_width) except: log.error("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = "1.0" # eval prefix_rules if passed from command line if type(opts.prefix_rules) is not tuple: try: opts.prefix_rules = eval(opts.prefix_rules) except: log.error("malformed --prefix-rules: %s" % opts.prefix_rules) raise for rule in opts.prefix_rules: if len(rule) != 4: log.error("incorrect number of args for --prefix-rules: %s" % repr(rule)) # eval exclusion_rules if passed from command line if type(opts.exclusion_rules) is not tuple: try: opts.exclusion_rules = eval(opts.exclusion_rules) except: log.error("malformed --exclusion-rules: %s" % opts.exclusion_rules) raise for rule in opts.exclusion_rules: if len(rule) != 3: log.error("incorrect number of args for --exclusion-rules: %s" % repr(rule)) # Display opts keys = sorted(opts_dict.keys()) build_log.append(" opts:") for key in keys: if key in [ "catalog_title", "author_clip", "connected_kindle", "creator", "cross_reference_authors", "description_clip", "exclude_book_marker", "exclude_genre", "exclude_tags", "exclusion_rules", "fmt", "genre_source_field", "header_note_source_field", "merge_comments_rule", "output_profile", "prefix_rules", "preset", "read_book_marker", "search_text", "sort_by", "sort_descriptions_by_author", "sync", "thumb_width", "use_existing_cover", "wishlist_tag", ]: build_log.append(" %s: %s" % (key, repr(opts_dict[key]))) if opts.verbose: log("\n".join(line for line in build_log)) # Capture start_time opts.start_time = time.time() self.opts = opts if opts.verbose: log.info( " Begin catalog source generation (%s)" % str(datetime.timedelta(seconds=int(time.time() - opts.start_time))) ) # Launch the Catalog builder catalog = CatalogBuilder(db, opts, self, report_progress=notification) try: catalog.build_sources() if opts.verbose: log.info( " Completed catalog source generation (%s)\n" % str(datetime.timedelta(seconds=int(time.time() - opts.start_time))) ) except (AuthorSortMismatchException, EmptyCatalogException), e: log.error(" *** Terminated catalog generation: %s ***" % e)
def test_sorting(self): # {{{ 'Test sorting' cache = self.init_cache() ae = self.assertEqual lmap = {x: cache.field_for('languages', x) for x in (1, 2, 3)} lq = sorted(lmap, key=lambda x: calibre_langcode_to_name( (lmap[x] or ('', ))[0])) for field, order in iteritems({ 'title': [2, 1, 3], 'authors': [2, 1, 3], 'series': [3, 1, 2], 'tags': [3, 1, 2], 'rating': [3, 2, 1], # 'identifiers': [3, 2, 1], There is no stable sort since 1 and # 2 have the same identifier keys # 'last_modified': [3, 2, 1], There is no stable sort as two # records have the exact same value 'timestamp': [2, 1, 3], 'pubdate': [1, 2, 3], 'publisher': [3, 2, 1], 'languages': lq, 'comments': [3, 2, 1], '#enum': [3, 2, 1], '#authors': [3, 2, 1], '#date': [3, 1, 2], '#rating': [3, 2, 1], '#series': [3, 2, 1], '#tags': [3, 2, 1], '#yesno': [2, 1, 3], '#comments': [3, 2, 1], 'id': [1, 2, 3], }): x = list(reversed(order)) ae(order, cache.multisort([(field, True)], ids_to_sort=x), 'Ascending sort of %s failed' % field) ae(x, cache.multisort([(field, False)], ids_to_sort=order), 'Descending sort of %s failed' % field) # Test sorting of is_multiple fields. # Author like fields should be sorted by generating sort names from the # actual values in entry order for field in ('authors', '#authors'): ae( cache.set_field( field, { 1: ('aa bb', 'bb cc', 'cc dd'), 2: ('bb aa', 'xx yy'), 3: ('aa bb', 'bb aa') }), {1, 2, 3}) ae([2, 3, 1], cache.multisort([(field, True)], ids_to_sort=(1, 2, 3))) ae([1, 3, 2], cache.multisort([(field, False)], ids_to_sort=(1, 2, 3))) # All other is_multiple fields should be sorted by sorting the values # for each book and using that as the sort key for field in ('tags', '#tags'): ae( cache.set_field(field, { 1: ('b', 'a'), 2: ('c', 'y'), 3: ('b', 'z') }), {1, 2, 3}) ae([1, 3, 2], cache.multisort([(field, True)], ids_to_sort=(1, 2, 3))) ae([2, 3, 1], cache.multisort([(field, False)], ids_to_sort=(1, 2, 3))) # Test tweak to sort dates by visible format from calibre.utils.config_base import Tweak ae( cache.set_field('pubdate', { 1: p('2001-3-3'), 2: p('2002-2-3'), 3: p('2003-1-3') }), {1, 2, 3}) ae([1, 2, 3], cache.multisort([('pubdate', True)])) with Tweak('gui_pubdate_display_format', 'MMM'), Tweak('sort_dates_using_visible_fields', True): c2 = self.init_cache() ae([3, 2, 1], c2.multisort([('pubdate', True)])) # Test bool sorting when not tristate cache.set_pref('bools_are_tristate', False) c2 = self.init_cache() ae([2, 3, 1], c2.multisort([('#yesno', True), ('id', False)])) # Test subsorting ae([3, 2, 1], cache.multisort([('identifiers', True), ('title', True)]), 'Subsort failed') from calibre.ebooks.metadata.book.base import Metadata for i in range(7): cache.create_book_entry(Metadata('title%d' % i), apply_import_tags=False) cache.create_custom_column('one', 'CC1', 'int', False) cache.create_custom_column('two', 'CC2', 'int', False) cache.create_custom_column('three', 'CC3', 'int', False) cache.close() cache = self.init_cache() cache.set_field('#one', {(i + (5 * m)): m for m in (0, 1) for i in range(1, 6)}) cache.set_field('#two', {i + (m * 3): m for m in (0, 1, 2) for i in (1, 2, 3)}) cache.set_field('#two', {10: 2}) cache.set_field('#three', {i: i for i in range(1, 11)}) ae( list(range(1, 11)), cache.multisort([('#one', True), ('#two', True)], ids_to_sort=sorted(cache.all_book_ids()))) ae([4, 5, 1, 2, 3, 7, 8, 9, 10, 6], cache.multisort([('#one', True), ('#two', False)], ids_to_sort=sorted(cache.all_book_ids()))) ae([5, 4, 3, 2, 1, 10, 9, 8, 7, 6], cache.multisort([('#one', True), ('#two', False), ('#three', False)], ids_to_sort=sorted(cache.all_book_ids())))
def locale_sort_key(loc): try: return lsk_cache[loc] except KeyError: lsk_cache[loc] = (psk(calibre_langcode_to_name(canonicalize_lang(loc[0]))), psk(loc[1] or '')) return lsk_cache[loc]
def fset(self, wl): self.dictionary_list.clear() for langcode, url in sorted(wl.iteritems(), key=lambda (lc, url): sort_key(calibre_langcode_to_name(lc))): i = QListWidgetItem("%s: %s" % (calibre_langcode_to_name(langcode), url), self.dictionary_list) i.setData(Qt.UserRole, (langcode, url))
def word_lookups(self, wl): self.dictionary_list.clear() for langcode, url in sorted(iteritems(wl), key=lambda lc_url:sort_key(calibre_langcode_to_name(lc_url[0]))): i = QListWidgetItem('%s: %s' % (calibre_langcode_to_name(langcode), url), self.dictionary_list) i.setData(Qt.UserRole, (langcode, url))