def to_mi(self, mi): val = self.value mi.set(self.field, val) if self.field == 'title': mi.set('title_sort', title_sort(val, lang=mi.language)) elif self.field == 'authors': mi.set('author_sort', authors_to_sort_string(val))
def process_result(self, group_id, result): if result.err: mi = self.report_metadata_failure(group_id, result.traceback) paths = self.file_groups[group_id] has_cover = False duplicate_info = set() if self.add_formats_to_existing else False else: paths, opf, has_cover, duplicate_info = result.value try: mi = OPF(BytesIO(opf), basedir=self.tdir, populate_spine=False, try_to_guess_cover=False).to_book_metadata() mi.read_metadata_failed = False except Exception: mi = self.report_metadata_failure(group_id, traceback.format_exc()) if mi.is_null('title'): for path in paths: mi.title = os.path.splitext(os.path.basename(path))[0] break if mi.application_id == '__calibre_dummy__': mi.application_id = None if gprefs.get('tag_map_on_add_rules'): from calibre.ebooks.metadata.tag_mapper import map_tags mi.tags = map_tags(mi.tags, gprefs['tag_map_on_add_rules']) if self.author_map_rules: from calibre.ebooks.metadata.author_mapper import map_authors new_authors = map_authors(mi.authors, self.author_map_rules) if new_authors != mi.authors: mi.authors = new_authors if self.db is None: mi.author_sort = authors_to_sort_string(mi.authors) else: mi.author_sort = self.db.author_sort_from_authors(mi.authors) self.pd.msg = mi.title cover_path = os.path.join(self.tdir, '%s.cdata' % group_id) if has_cover else None if self.db is None: if paths: self.items.append((mi, cover_path, paths)) return if self.add_formats_to_existing: identical_book_ids = find_identical_books(mi, self.find_identical_books_data) if identical_book_ids: try: self.merge_books(mi, cover_path, paths, identical_book_ids) except Exception: a = self.report.append a(''), a('-' * 70) a(_('Failed to merge the book: ') + mi.title) [a('\t' + f) for f in paths] a(_('With error:')), a(traceback.format_exc()) else: self.add_book(mi, cover_path, paths) else: if duplicate_info or icu_lower(mi.title or _('Unknown')) in self.added_duplicate_info: self.duplicates.append((mi, cover_path, paths)) else: self.add_book(mi, cover_path, paths)
def do_set_metadata(opts, mi, stream, stream_type): mi = MetaInformation(mi) for x in ('guide', 'toc', 'manifest', 'spine'): setattr(mi, x, None) from_opf = getattr(opts, 'from_opf', None) if from_opf is not None: from calibre.ebooks.metadata.opf2 import OPF opf_mi = OPF(open(from_opf, 'rb')).to_book_metadata() mi.smart_update(opf_mi) for pref in config().option_set.preferences: if pref.name in ('to_opf', 'from_opf', 'authors', 'title_sort', 'author_sort', 'get_cover', 'cover', 'tags', 'lrf_bookid', 'identifiers'): continue val = getattr(opts, pref.name, None) if val is not None: setattr(mi, pref.name, val) if getattr(opts, 'authors', None) is not None: mi.authors = string_to_authors(opts.authors) mi.author_sort = authors_to_sort_string(mi.authors) if getattr(opts, 'author_sort', None) is not None: mi.author_sort = opts.author_sort if getattr(opts, 'title_sort', None) is not None: mi.title_sort = opts.title_sort elif getattr(opts, 'title', None) is not None: mi.title_sort = title_sort(opts.title) if getattr(opts, 'tags', None) is not None: mi.tags = [t.strip() for t in opts.tags.split(',')] if getattr(opts, 'series', None) is not None: mi.series = opts.series.strip() if getattr(opts, 'series_index', None) is not None: mi.series_index = float(opts.series_index.strip()) if getattr(opts, 'pubdate', None) is not None: mi.pubdate = parse_date(opts.pubdate, assume_utc=False, as_utc=False) if getattr(opts, 'identifiers', None): val = { k.strip(): v.strip() for k, v in (x.partition(':')[0::2] for x in opts.identifiers) } if val: orig = mi.get_identifiers() orig.update(val) val = {k: v for k, v in orig.iteritems() if k and v} mi.set_identifiers(val) if getattr(opts, 'cover', None) is not None: ext = os.path.splitext(opts.cover)[1].replace('.', '').upper() mi.cover_data = (ext, open(opts.cover, 'rb').read()) with force_identifiers: set_metadata(stream, mi, stream_type)
def to_mi(self, mi): val = unicode(self.text()).strip() ism = self.metadata['is_multiple'] if ism: if not val: val = [] else: val = [x.strip() for x in val.split(ism['list_to_ui']) if x.strip()] mi.set(self.field, val) if self.field == 'title': mi.set('title_sort', title_sort(val, lang=mi.language)) elif self.field == 'authors': mi.set('author_sort', authors_to_sort_string(val))
def to_mi(self, mi): val = unicode(self.text()).strip() ism = self.metadata["is_multiple"] if ism: if not val: val = [] else: val = [x.strip() for x in val.split(ism["list_to_ui"]) if x.strip()] mi.set(self.field, val) if self.field == "title": mi.set("title_sort", title_sort(val, lang=mi.language)) elif self.field == "authors": mi.set("author_sort", authors_to_sort_string(val))
def do_set_metadata(opts, mi, stream, stream_type): mi = MetaInformation(mi) for x in ('guide', 'toc', 'manifest', 'spine'): setattr(mi, x, None) from_opf = getattr(opts, 'from_opf', None) if from_opf is not None: from calibre.ebooks.metadata.opf2 import OPF opf_mi = OPF(open(from_opf, 'rb')).to_book_metadata() mi.smart_update(opf_mi) for pref in config().option_set.preferences: if pref.name in ('to_opf', 'from_opf', 'authors', 'title_sort', 'author_sort', 'get_cover', 'cover', 'tags', 'lrf_bookid', 'identifiers'): continue val = getattr(opts, pref.name, None) if val is not None: setattr(mi, pref.name, val) if getattr(opts, 'authors', None) is not None: mi.authors = string_to_authors(opts.authors) mi.author_sort = authors_to_sort_string(mi.authors) if getattr(opts, 'author_sort', None) is not None: mi.author_sort = opts.author_sort if getattr(opts, 'title_sort', None) is not None: mi.title_sort = opts.title_sort elif getattr(opts, 'title', None) is not None: mi.title_sort = title_sort(opts.title) if getattr(opts, 'tags', None) is not None: mi.tags = [t.strip() for t in opts.tags.split(',')] if getattr(opts, 'series', None) is not None: mi.series = opts.series.strip() if getattr(opts, 'series_index', None) is not None: mi.series_index = float(opts.series_index.strip()) if getattr(opts, 'pubdate', None) is not None: mi.pubdate = parse_date(opts.pubdate, assume_utc=False, as_utc=False) if getattr(opts, 'identifiers', None): val = {k.strip():v.strip() for k, v in (x.partition(':')[0::2] for x in opts.identifiers)} if val: orig = mi.get_identifiers() orig.update(val) val = {k:v for k, v in iteritems(orig) if k and v} mi.set_identifiers(val) if getattr(opts, 'cover', None) is not None: ext = os.path.splitext(opts.cover)[1].replace('.', '').upper() mi.cover_data = (ext, open(opts.cover, 'rb').read()) with force_identifiers: set_metadata(stream, mi, stream_type)
def read_doc_props(raw, mi, XPath): root = fromstring(raw) titles = XPath('//dc:title')(root) if titles: title = titles[0].text if title and title.strip(): mi.title = title.strip() tags = [] for subject in XPath('//dc:subject')(root): if subject.text and subject.text.strip(): tags.append(subject.text.strip().replace(',', '_')) for keywords in XPath('//cp:keywords')(root): if keywords.text and keywords.text.strip(): for x in keywords.text.split(): tags.extend(y.strip() for y in x.split(',') if y.strip()) if tags: mi.tags = tags authors = XPath('//dc:creator')(root) aut = [] for author in authors: if author.text and author.text.strip(): aut.extend(string_to_authors(author.text)) if aut: mi.authors = aut mi.author_sort = authors_to_sort_string(aut) desc = XPath('//dc:description')(root) if desc: raw = etree.tostring(desc[0], method='text', encoding=unicode_type) raw = raw.replace('_x000d_', '') # Word 2007 mangles newlines in the summary mi.comments = raw.strip() langs = [] for lang in XPath('//dc:language')(root): if lang.text and lang.text.strip(): l = canonicalize_lang(lang.text) if l: langs.append(l) if langs: mi.languages = langs
def read_doc_props(raw, mi): root = fromstring(raw) titles = XPath('//dc:title')(root) if titles: title = titles[0].text if title and title.strip(): mi.title = title.strip() tags = [] for subject in XPath('//dc:subject')(root): if subject.text and subject.text.strip(): tags.append(subject.text.strip().replace(',', '_')) for keywords in XPath('//cp:keywords')(root): if keywords.text and keywords.text.strip(): for x in keywords.text.split(): tags.extend(y.strip() for y in x.split(',') if y.strip()) if tags: mi.tags = tags authors = XPath('//dc:creator')(root) aut = [] for author in authors: if author.text and author.text.strip(): aut.extend(string_to_authors(author.text)) if aut: mi.authors = aut mi.author_sort = authors_to_sort_string(aut) desc = XPath('//dc:description')(root) if desc: raw = etree.tostring(desc[0], method='text', encoding=unicode) raw = raw.replace('_x000d_', '') # Word 2007 mangles newlines in the summary mi.comments = raw.strip() langs = [] for lang in XPath('//dc:language')(root): if lang.text and lang.text.strip(): l = canonicalize_lang(lang.text) if l: langs.append(l) if langs: mi.languages = langs
def update_device_books(self, connection, booklist, source_id, plugboard, dbpath): opts = self.settings() upload_covers = opts.extra_customization[self.OPT_UPLOAD_COVERS] refresh_covers = opts.extra_customization[self.OPT_REFRESH_COVERS] use_sony_authors = opts.extra_customization[self.OPT_USE_SONY_AUTHORS] db_books = self.read_device_books(connection, source_id, dbpath) cursor = connection.cursor() for book in booklist: # Run through plugboard if needed if plugboard is not None: newmi = book.deepcopy_metadata() newmi.template_to_attribute(book, plugboard) else: newmi = book # Get Metadata We Want lpath = book.lpath try: if opts.use_author_sort: if newmi.author_sort: author = newmi.author_sort else: author = authors_to_sort_string(newmi.authors) else: if use_sony_authors: author = newmi.authors[0] else: author = authors_to_string(newmi.authors) except: author = _('Unknown') title = newmi.title or _('Unknown') # Get modified date # If there was a detected offset, use that. Otherwise use UTC (same as Sony software) modified_date = os.path.getmtime(book.path) * 1000 if self.device_offset is not None: modified_date = modified_date + self.device_offset if lpath not in db_books: query = ''' INSERT INTO books (title, author, source_id, added_date, modified_date, file_path, file_name, file_size, mime_type, corrupted, prevent_delete) values (?,?,?,?,?,?,?,?,?,0,0) ''' t = (title, author, source_id, int(time.time() * 1000), modified_date, lpath, os.path.basename(lpath), book.size, book.mime) cursor.execute(query, t) book.bookId = self.get_lastrowid(cursor) if upload_covers: self.upload_book_cover(connection, book, source_id) debug_print('Inserted New Book: (%u) '%book.bookId + book.title) else: query = ''' UPDATE books SET title = ?, author = ?, modified_date = ?, file_size = ? WHERE file_path = ? ''' t = (title, author, modified_date, book.size, lpath) cursor.execute(query, t) book.bookId = db_books[lpath] if refresh_covers: self.upload_book_cover(connection, book, source_id) db_books[lpath] = None if self.is_sony_periodical(book): self.periodicalize_book(connection, book) for book, bookId in db_books.items(): if bookId is not None: # Remove From Collections query = 'DELETE FROM collections WHERE content_id = ?' t = (bookId,) cursor.execute(query, t) # Remove from Books query = 'DELETE FROM books where _id = ?' t = (bookId,) cursor.execute(query, t) debug_print('Deleted Book:' + book) connection.commit() cursor.close()
def update_device_books(self, connection, booklist, source_id, plugboard, dbpath): from calibre.ebooks.metadata.meta import path_to_ext from calibre.ebooks.metadata import authors_to_sort_string, authors_to_string opts = self.settings() db_books = self.read_device_books(connection, source_id, dbpath) cursor = connection.cursor() for book in booklist: # Run through plugboard if needed if plugboard is not None: newmi = book.deepcopy_metadata() newmi.template_to_attribute(book, plugboard) else: newmi = book # Get Metadata We Want lpath = book.lpath try: if opts.use_author_sort: if newmi.author_sort: author = newmi.author_sort else: author = authors_to_sort_string(newmi.authors) else: author = authors_to_string(newmi.authors) except Exception: author = _('Unknown') title = newmi.title or _('Unknown') # Get modified date # If there was a detected offset, use that. Otherwise use UTC (same as Sony software) modified_date = os.path.getmtime(book.path) * 1000 if self.device_offset is not None: modified_date = modified_date + self.device_offset if lpath not in db_books: query = ''' INSERT INTO books (bookname, authorname, description, addeddate, seriesname, seriesorder, filename, mimetype) values (?,?,?,?,?,?,?,?) ''' t = (title, author, book.get('comments', None), int(time.time() * 1000), book.get('series', None), book.get('series_index', sys.maxint), lpath, book.mime or mime_type_ext(path_to_ext(lpath))) cursor.execute(query, t) book.bookId = connection.last_insert_rowid() debug_print('Inserted New Book: (%u) '%book.bookId + book.title) else: query = ''' UPDATE books SET bookname = ?, authorname = ?, addeddate = ? WHERE filename = ? ''' t = (title, author, modified_date, lpath) cursor.execute(query, t) book.bookId = db_books[lpath] db_books[lpath] = None for book, bookId in db_books.items(): if bookId is not None: # Remove From Collections query = 'DELETE FROM tags WHERE _id in (select tag_id from booktags where book_id = ?)' t = (bookId,) cursor.execute(query, t) # Remove from Books query = 'DELETE FROM books where _id = ?' t = (bookId,) cursor.execute(query, t) debug_print('Deleted Book:' + book) cursor.close()
def identify(log, abort, # {{{ title=None, authors=None, identifiers={}, timeout=30, allowed_plugins=None): if title == _('Unknown'): title = None if authors == [_('Unknown')]: authors = None start_time = time.time() plugins = [p for p in metadata_plugins(['identify']) if p.is_configured() and (allowed_plugins is None or p.name in allowed_plugins)] kwargs = { 'title': title, 'authors': authors, 'identifiers': identifiers, 'timeout': timeout, } log('Running identify query with parameters:') log(kwargs) log('Using plugins:', ', '.join(['%s %s' % (p.name, p.version) for p in plugins])) log('The log from individual plugins is below') workers = [Worker(p, kwargs, abort) for p in plugins] for w in workers: w.start() first_result_at = None results = {} for p in plugins: results[p] = [] logs = dict([(w.plugin, w.buf) for w in workers]) def get_results(): found = False for w in workers: try: result = w.rq.get_nowait() except Empty: pass else: results[w.plugin].append(result) found = True return found wait_time = msprefs['wait_after_first_identify_result'] while True: time.sleep(0.2) if get_results() and first_result_at is None: first_result_at = time.time() if not is_worker_alive(workers): break if (first_result_at is not None and time.time() - first_result_at > wait_time): log.warn('Not waiting any longer for more results. Still running' ' sources:') for worker in workers: if worker.is_alive(): log.debug('\t' + worker.name) abort.set() break while not abort.is_set() and get_results(): pass sort_kwargs = dict(kwargs) for k in list(sort_kwargs.iterkeys()): if k not in ('title', 'authors', 'identifiers'): sort_kwargs.pop(k) longest, lp = -1, '' for plugin, presults in results.iteritems(): presults.sort(key=plugin.identify_results_keygen(**sort_kwargs)) # Throw away lower priority results from the same source that have exactly the same # title and authors as a higher priority result filter_results = set() filtered_results = [] for r in presults: key = (r.title, tuple(r.authors)) if key not in filter_results: filtered_results.append(r) filter_results.add(key) results[plugin] = presults = filtered_results plog = logs[plugin].getvalue().strip() log('\n'+'*'*30, plugin.name, '%s' % (plugin.version,), '*'*30) log('Found %d results'%len(presults)) time_spent = getattr(plugin, 'dl_time_spent', None) if time_spent is None: log('Downloading was aborted') longest, lp = -1, plugin.name else: log('Downloading from', plugin.name, 'took', time_spent) if time_spent > longest: longest, lp = time_spent, plugin.name for r in presults: log('\n\n---') try: log(unicode(r)) except TypeError: log(repr(r)) if plog: log(plog) log('\n'+'*'*80) dummy = Metadata(_('Unknown')) for i, result in enumerate(presults): for f in plugin.prefs['ignore_fields']: if ':' not in f: setattr(result, f, getattr(dummy, f)) if f == 'series': result.series_index = dummy.series_index result.relevance_in_source = i result.has_cached_cover_url = ( plugin.cached_cover_url_is_reliable and plugin.get_cached_cover_url(result.identifiers) is not None) result.identify_plugin = plugin if msprefs['txt_comments']: if plugin.has_html_comments and result.comments: result.comments = html2text(result.comments) log('The identify phase took %.2f seconds'%(time.time() - start_time)) log('The longest time (%f) was taken by:'%longest, lp) log('Merging results from different sources') start_time = time.time() results = merge_identify_results(results, log) log('We have %d merged results, merging took: %.2f seconds' % (len(results), time.time() - start_time)) tm_rules = msprefs['tag_map_rules'] if tm_rules: from calibre.ebooks.metadata.tag_mapper import map_tags am_rules = msprefs['author_map_rules'] if am_rules: from calibre.ebooks.metadata.author_mapper import map_authors, compile_rules am_rules = compile_rules(am_rules) max_tags = msprefs['max_tags'] for r in results: if tm_rules: r.tags = map_tags(r.tags, tm_rules) r.tags = r.tags[:max_tags] if getattr(r.pubdate, 'year', 2000) <= UNDEFINED_DATE.year: r.pubdate = None if msprefs['swap_author_names']: for r in results: def swap_to_ln_fn(a): if ',' in a: return a parts = a.split(None) if len(parts) <= 1: return a surname = parts[-1] return '%s, %s' % (surname, ' '.join(parts[:-1])) r.authors = [swap_to_ln_fn(a) for a in r.authors] if am_rules: for r in results: new_authors = map_authors(r.authors, am_rules) if new_authors != r.authors: r.authors = new_authors r.author_sort = authors_to_sort_string(r.authors) return results
def build_exth(metadata, prefer_author_sort=False, is_periodical=False, share_not_sync=True, cover_offset=None, thumbnail_offset=None, start_offset=None, mobi_doctype=2, num_of_resources=None, kf8_unknown_count=0, be_kindlegen2=False, kf8_header_index=None, opts=None): exth = BytesIO() nrecs = 0 for term in metadata: if term not in EXTH_CODES: continue code = EXTH_CODES[term] items = metadata[term] if term == 'creator': if prefer_author_sort: creators = [ authors_to_sort_string([unicode(c)]) for c in items ] else: creators = [unicode(c) for c in items] items = creators elif term == 'rights': try: rights = utf8_text(unicode(metadata.rights[0])) except: rights = b'Unknown' exth.write(pack(b'>II', EXTH_CODES['rights'], len(rights) + 8)) exth.write(rights) nrecs += 1 continue for item in items: data = unicode(item) if term != 'description': data = COLLAPSE_RE.sub(' ', data) if term == 'identifier': if data.lower().startswith('urn:isbn:'): data = data[9:] elif item.scheme.lower() == 'isbn': pass else: continue if term == 'language': d2 = usr_lang_as_iso639_1(data) if d2: data = d2 data = utf8_text(data) exth.write(pack(b'>II', code, len(data) + 8)) exth.write(data) nrecs += 1 # Write UUID as ASIN uuid = None from calibre.ebooks.oeb.base import OPF for x in 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') if not share_not_sync: exth.write(pack(b'>II', 113, len(uuid) + 8)) exth.write(uuid) nrecs += 1 # Write UUID as SOURCE c_uuid = b'calibre:%s' % uuid exth.write(pack(b'>II', 112, len(c_uuid) + 8)) exth.write(c_uuid) nrecs += 1 # Write cdetype if not is_periodical: if not share_not_sync: exth.write(pack(b'>II', 501, 12)) exth.write(b'EBOK') nrecs += 1 else: ids = {0x101: b'NWPR', 0x103: b'MAGZ'}.get(mobi_doctype, None) if ids: exth.write(pack(b'>II', 501, 12)) exth.write(ids) nrecs += 1 # Add a publication date entry datestr = None if metadata['date']: datestr = str(metadata['date'][0]) elif metadata['timestamp']: datestr = str(metadata['timestamp'][0]) if not datestr: raise ValueError("missing date or timestamp") datestr = bytes(datestr) exth.write(pack(b'>II', EXTH_CODES['pubdate'], len(datestr) + 8)) exth.write(datestr) nrecs += 1 if is_periodical: exth.write(pack(b'>II', EXTH_CODES['lastupdatetime'], len(datestr) + 8)) exth.write(datestr) nrecs += 1 if be_kindlegen2: vals = {204: 201, 205: 2, 206: 5, 207: 0} elif is_periodical: # Pretend to be amazon's super secret periodical generator vals = {204: 201, 205: 2, 206: 0, 207: 101} else: # Pretend to be kindlegen 1.2 vals = {204: 201, 205: 1, 206: 2, 207: 33307} for code, val in vals.iteritems(): exth.write(pack(b'>III', code, 12, val)) nrecs += 1 if cover_offset is not None: exth.write(pack(b'>III', EXTH_CODES['coveroffset'], 12, cover_offset)) exth.write(pack(b'>III', EXTH_CODES['hasfakecover'], 12, 0)) nrecs += 2 if thumbnail_offset is not None: exth.write( pack(b'>III', EXTH_CODES['thumboffset'], 12, thumbnail_offset)) thumbnail_uri_str = bytes( 'kindle:embed:%s' % (to_base(thumbnail_offset, base=32, min_num_digits=4))) exth.write( pack(b'>II', EXTH_CODES['kf8_thumbnail_uri'], len(thumbnail_uri_str) + 8)) exth.write(thumbnail_uri_str) nrecs += 2 if start_offset is not None: try: len(start_offset) except TypeError: start_offset = [start_offset] for so in start_offset: if so is not None: exth.write(pack(b'>III', EXTH_CODES['startreading'], 12, so)) nrecs += 1 if kf8_header_index is not None: exth.write( pack(b'>III', EXTH_CODES['kf8_header_index'], 12, kf8_header_index)) nrecs += 1 if num_of_resources is not None: exth.write( pack(b'>III', EXTH_CODES['num_of_resources'], 12, num_of_resources)) nrecs += 1 if kf8_unknown_count is not None: exth.write( pack(b'>III', EXTH_CODES['kf8_unknown_count'], 12, kf8_unknown_count)) nrecs += 1 #Extra metadata for fullscrenn if opts and opts.book_mode == 'comic': #added for kindleear [insert0003 2017-09-03] exth.write(pack(b'>II', EXTH_CODES['RegionMagnification'], 13)) exth.write(b'false') exth.write(pack(b'>II', EXTH_CODES['book-type'], 13)) exth.write(b'comic') exth.write(pack(b'>II', EXTH_CODES['zero-gutter'], 12)) exth.write(b'true') exth.write(pack(b'>II', EXTH_CODES['zero-margin'], 12)) exth.write(b'true') exth.write(pack(b'>II', EXTH_CODES['primary-writing-mode'], 21)) exth.write(b'horizontal-lr') exth.write(pack(b'>II', EXTH_CODES['fixed-layout'], 12)) exth.write(b'true') exth.write(pack(b'>II', EXTH_CODES['orientation-lock'], 16)) exth.write(b'portrait') original_resolution = b'%dx%d' % opts.dest.comic_screen_size #sth like comic_screen_size = (1072, 1430) exth.write( pack(b'>II', EXTH_CODES['original-resolution'], len(original_resolution) + 8)) exth.write(original_resolution) nrecs += 8 exth = exth.getvalue() trail = len(exth) % 4 pad = b'\0' * (4 - trail) # Always pad w/ at least 1 byte exth = [b'EXTH', pack(b'>II', len(exth) + 12, nrecs), exth, pad] return b''.join(exth)
def update_state_and_val(self): # Handle case change if the authors box changed aus = authors_to_sort_string(self.authors_edit.current_val) if strcmp(aus, self.current_val) == 0: self.current_val = aus self.update_state()
def __init__(self, book): tableItem.__init__(self, authors_to_string(book.authors)) if book.author_sort is not None: self.sort = book.author_sort.lower() else: self.sort = authors_to_sort_string(book.authors).lower()
def build_exth(metadata, prefer_author_sort=False, is_periodical=False, share_not_sync=True, cover_offset=None, thumbnail_offset=None, start_offset=None, mobi_doctype=2, num_of_resources=None, kf8_unknown_count=0, be_kindlegen2=False, kf8_header_index=None, page_progression_direction=None, primary_writing_mode=None): exth = BytesIO() nrecs = 0 for term in metadata: if term not in EXTH_CODES: continue code = EXTH_CODES[term] items = metadata[term] if term == 'creator': if prefer_author_sort: creators = [authors_to_sort_string([unicode_type(c)]) for c in items] else: creators = [unicode_type(c) for c in items] items = creators elif term == 'rights': try: rights = utf8_text(unicode_type(metadata.rights[0])) except: rights = b'Unknown' exth.write(pack(b'>II', EXTH_CODES['rights'], len(rights) + 8)) exth.write(rights) nrecs += 1 continue for item in items: data = unicode_type(item) if term != 'description': data = COLLAPSE_RE.sub(' ', data) if term == 'identifier': if data.lower().startswith('urn:isbn:'): data = data[9:] elif item.scheme.lower() == 'isbn': pass else: continue if term == 'language': d2 = lang_as_iso639_1(data) if d2: data = d2 data = utf8_text(data) exth.write(pack(b'>II', code, len(data) + 8)) exth.write(data) nrecs += 1 # Write UUID as ASIN uuid = None from calibre.ebooks.oeb.base import OPF for x in metadata['identifier']: if (x.get(OPF('scheme'), None).lower() == 'uuid' or unicode_type(x).startswith('urn:uuid:')): uuid = unicode_type(x).split(':')[-1] break if uuid is None: from uuid import uuid4 uuid = str(uuid4()) if isinstance(uuid, unicode_type): uuid = uuid.encode('utf-8') if not share_not_sync: exth.write(pack(b'>II', 113, len(uuid) + 8)) exth.write(uuid) nrecs += 1 # Write UUID as SOURCE c_uuid = b'calibre:%s' % uuid exth.write(pack(b'>II', 112, len(c_uuid) + 8)) exth.write(c_uuid) nrecs += 1 # Write cdetype if not is_periodical: if not share_not_sync: exth.write(pack(b'>II', 501, 12)) exth.write(b'EBOK') nrecs += 1 else: ids = {0x101:b'NWPR', 0x103:b'MAGZ'}.get(mobi_doctype, None) if ids: exth.write(pack(b'>II', 501, 12)) exth.write(ids) nrecs += 1 # Add a publication date entry if metadata['date']: datestr = str(metadata['date'][0]) elif metadata['timestamp']: datestr = str(metadata['timestamp'][0]) if datestr is None: raise ValueError("missing date or timestamp") datestr = bytes(datestr) exth.write(pack(b'>II', EXTH_CODES['pubdate'], len(datestr) + 8)) exth.write(datestr) nrecs += 1 if is_periodical: exth.write(pack(b'>II', EXTH_CODES['lastupdatetime'], len(datestr) + 8)) exth.write(datestr) nrecs += 1 if be_kindlegen2: mv = 200 if iswindows else 202 if isosx else 201 vals = {204:mv, 205:2, 206:9, 207:0} elif is_periodical: # Pretend to be amazon's super secret periodical generator vals = {204:201, 205:2, 206:0, 207:101} else: # Pretend to be kindlegen 1.2 vals = {204:201, 205:1, 206:2, 207:33307} for code, val in vals.iteritems(): exth.write(pack(b'>III', code, 12, val)) nrecs += 1 if be_kindlegen2: revnum = b'0730-890adc2' exth.write(pack(b'>II', 535, 8 + len(revnum)) + revnum) nrecs += 1 if cover_offset is not None: exth.write(pack(b'>III', EXTH_CODES['coveroffset'], 12, cover_offset)) exth.write(pack(b'>III', EXTH_CODES['hasfakecover'], 12, 0)) nrecs += 2 if thumbnail_offset is not None: exth.write(pack(b'>III', EXTH_CODES['thumboffset'], 12, thumbnail_offset)) thumbnail_uri_str = bytes('kindle:embed:%s' %(to_base(thumbnail_offset, base=32, min_num_digits=4))) exth.write(pack(b'>II', EXTH_CODES['kf8_thumbnail_uri'], len(thumbnail_uri_str) + 8)) exth.write(thumbnail_uri_str) nrecs += 2 if start_offset is not None: try: len(start_offset) except TypeError: start_offset = [start_offset] for so in start_offset: if so is not None: exth.write(pack(b'>III', EXTH_CODES['startreading'], 12, so)) nrecs += 1 if kf8_header_index is not None: exth.write(pack(b'>III', EXTH_CODES['kf8_header_index'], 12, kf8_header_index)) nrecs += 1 if num_of_resources is not None: exth.write(pack(b'>III', EXTH_CODES['num_of_resources'], 12, num_of_resources)) nrecs += 1 if kf8_unknown_count is not None: exth.write(pack(b'>III', EXTH_CODES['kf8_unknown_count'], 12, kf8_unknown_count)) nrecs += 1 if primary_writing_mode: pwm = primary_writing_mode.encode('utf-8') exth.write(pack(b'>II', EXTH_CODES['primary_writing_mode'], len(pwm) + 8)) exth.write(pwm) nrecs += 1 if page_progression_direction in {'rtl', 'ltr', 'default'}: ppd = bytes(page_progression_direction) exth.write(pack(b'>II', EXTH_CODES['page_progression_direction'], len(ppd) + 8)) exth.write(ppd) nrecs += 1 exth = exth.getvalue() trail = len(exth) % 4 pad = b'\0' * (4 - trail) # Always pad w/ at least 1 byte exth = [b'EXTH', pack(b'>II', len(exth) + 12, nrecs), exth, pad] return b''.join(exth)
def identify( log, abort, # {{{ title=None, authors=None, identifiers={}, timeout=30, allowed_plugins=None): if title == _('Unknown'): title = None if authors == [_('Unknown')]: authors = None start_time = time.time() plugins = [ p for p in metadata_plugins(['identify']) if p.is_configured() and ( allowed_plugins is None or p.name in allowed_plugins) ] kwargs = { 'title': title, 'authors': authors, 'identifiers': identifiers, 'timeout': timeout, } log('Running identify query with parameters:') log(kwargs) log('Using plugins:', ', '.join(['%s %s' % (p.name, p.version) for p in plugins])) log('The log from individual plugins is below') workers = [Worker(p, kwargs, abort) for p in plugins] for w in workers: w.start() first_result_at = None results = {} for p in plugins: results[p] = [] logs = dict([(w.plugin, w.buf) for w in workers]) def get_results(): found = False for w in workers: try: result = w.rq.get_nowait() except Empty: pass else: results[w.plugin].append(result) found = True return found wait_time = msprefs['wait_after_first_identify_result'] while True: time.sleep(0.2) if get_results() and first_result_at is None: first_result_at = time.time() if not is_worker_alive(workers): break if (first_result_at is not None and time.time() - first_result_at > wait_time): log.warn('Not waiting any longer for more results. Still running' ' sources:') for worker in workers: if worker.is_alive(): log.debug('\t' + worker.name) abort.set() break while not abort.is_set() and get_results(): pass sort_kwargs = dict(kwargs) for k in list(sort_kwargs.iterkeys()): if k not in ('title', 'authors', 'identifiers'): sort_kwargs.pop(k) longest, lp = -1, '' for plugin, presults in results.iteritems(): presults.sort(key=plugin.identify_results_keygen(**sort_kwargs)) # Throw away lower priority results from the same source that have exactly the same # title and authors as a higher priority result filter_results = set() filtered_results = [] for r in presults: key = (r.title, tuple(r.authors)) if key not in filter_results: filtered_results.append(r) filter_results.add(key) results[plugin] = presults = filtered_results plog = logs[plugin].getvalue().strip() log('\n' + '*' * 30, plugin.name, '%s' % (plugin.version, ), '*' * 30) log('Found %d results' % len(presults)) time_spent = getattr(plugin, 'dl_time_spent', None) if time_spent is None: log('Downloading was aborted') longest, lp = -1, plugin.name else: log('Downloading from', plugin.name, 'took', time_spent) if time_spent > longest: longest, lp = time_spent, plugin.name for r in presults: log('\n\n---') try: log(unicode(r)) except TypeError: log(repr(r)) if plog: log(plog) log('\n' + '*' * 80) dummy = Metadata(_('Unknown')) for i, result in enumerate(presults): for f in plugin.prefs['ignore_fields']: if ':' not in f: setattr(result, f, getattr(dummy, f)) if f == 'series': result.series_index = dummy.series_index result.relevance_in_source = i result.has_cached_cover_url = (plugin.cached_cover_url_is_reliable and plugin.get_cached_cover_url( result.identifiers) is not None) result.identify_plugin = plugin if msprefs['txt_comments']: if plugin.has_html_comments and result.comments: result.comments = html2text(result.comments) log('The identify phase took %.2f seconds' % (time.time() - start_time)) log('The longest time (%f) was taken by:' % longest, lp) log('Merging results from different sources') start_time = time.time() results = merge_identify_results(results, log) log('We have %d merged results, merging took: %.2f seconds' % (len(results), time.time() - start_time)) tm_rules = msprefs['tag_map_rules'] if tm_rules: from calibre.ebooks.metadata.tag_mapper import map_tags am_rules = msprefs['author_map_rules'] if am_rules: from calibre.ebooks.metadata.author_mapper import map_authors, compile_rules am_rules = compile_rules(am_rules) max_tags = msprefs['max_tags'] for r in results: if tm_rules: r.tags = map_tags(r.tags, tm_rules) r.tags = r.tags[:max_tags] if getattr(r.pubdate, 'year', 2000) <= UNDEFINED_DATE.year: r.pubdate = None if msprefs['swap_author_names']: for r in results: def swap_to_ln_fn(a): if ',' in a: return a parts = a.split(None) if len(parts) <= 1: return a surname = parts[-1] return '%s, %s' % (surname, ' '.join(parts[:-1])) r.authors = [swap_to_ln_fn(a) for a in r.authors] if am_rules: for r in results: new_authors = map_authors(r.authors, am_rules) if new_authors != r.authors: r.authors = new_authors r.author_sort = authors_to_sort_string(r.authors) return results
def build_exth(metadata, prefer_author_sort=False, is_periodical=False, share_not_sync=True, cover_offset=None, thumbnail_offset=None, start_offset=None, mobi_doctype=2, num_of_resources=None, kf8_unknown_count=0, be_kindlegen2=False, kf8_header_index=None, opts=None): exth = BytesIO() nrecs = 0 for term in metadata: if term not in EXTH_CODES: continue code = EXTH_CODES[term] items = metadata[term] if term == 'creator': if prefer_author_sort: creators = [authors_to_sort_string([unicode(c)]) for c in items] else: creators = [unicode(c) for c in items] items = creators elif term == 'rights': try: rights = utf8_text(unicode(metadata.rights[0])) except: rights = b'Unknown' exth.write(pack(b'>II', EXTH_CODES['rights'], len(rights) + 8)) exth.write(rights) nrecs += 1 continue for item in items: data = unicode(item) if term != 'description': data = COLLAPSE_RE.sub(' ', data) if term == 'identifier': if data.lower().startswith('urn:isbn:'): data = data[9:] elif item.scheme.lower() == 'isbn': pass else: continue if term == 'language': d2 = usr_lang_as_iso639_1(data) if d2: data = d2 data = utf8_text(data) exth.write(pack(b'>II', code, len(data) + 8)) exth.write(data) nrecs += 1 # Write UUID as ASIN uuid = None from calibre.ebooks.oeb.base import OPF for x in 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') if not share_not_sync: exth.write(pack(b'>II', 113, len(uuid) + 8)) exth.write(uuid) nrecs += 1 # Write UUID as SOURCE c_uuid = b'calibre:%s' % uuid exth.write(pack(b'>II', 112, len(c_uuid) + 8)) exth.write(c_uuid) nrecs += 1 # Write cdetype if not is_periodical: if not share_not_sync: exth.write(pack(b'>II', 501, 12)) exth.write(b'EBOK') nrecs += 1 else: ids = {0x101:b'NWPR', 0x103:b'MAGZ'}.get(mobi_doctype, None) if ids: exth.write(pack(b'>II', 501, 12)) exth.write(ids) nrecs += 1 # Add a publication date entry datestr = None if metadata['date']: datestr = str(metadata['date'][0]) elif metadata['timestamp']: datestr = str(metadata['timestamp'][0]) if not datestr: raise ValueError("missing date or timestamp") datestr = bytes(datestr) exth.write(pack(b'>II', EXTH_CODES['pubdate'], len(datestr) + 8)) exth.write(datestr) nrecs += 1 if is_periodical: exth.write(pack(b'>II', EXTH_CODES['lastupdatetime'], len(datestr) + 8)) exth.write(datestr) nrecs += 1 if be_kindlegen2: vals = {204:201, 205:2, 206:5, 207:0} elif is_periodical: # Pretend to be amazon's super secret periodical generator vals = {204:201, 205:2, 206:0, 207:101} else: # Pretend to be kindlegen 1.2 vals = {204:201, 205:1, 206:2, 207:33307} for code, val in vals.iteritems(): exth.write(pack(b'>III', code, 12, val)) nrecs += 1 if cover_offset is not None: exth.write(pack(b'>III', EXTH_CODES['coveroffset'], 12, cover_offset)) exth.write(pack(b'>III', EXTH_CODES['hasfakecover'], 12, 0)) nrecs += 2 if thumbnail_offset is not None: exth.write(pack(b'>III', EXTH_CODES['thumboffset'], 12, thumbnail_offset)) thumbnail_uri_str = bytes('kindle:embed:%s' %(to_base(thumbnail_offset, base=32, min_num_digits=4))) exth.write(pack(b'>II', EXTH_CODES['kf8_thumbnail_uri'], len(thumbnail_uri_str) + 8)) exth.write(thumbnail_uri_str) nrecs += 2 if start_offset is not None: try: len(start_offset) except TypeError: start_offset = [start_offset] for so in start_offset: if so is not None: exth.write(pack(b'>III', EXTH_CODES['startreading'], 12, so)) nrecs += 1 if kf8_header_index is not None: exth.write(pack(b'>III', EXTH_CODES['kf8_header_index'], 12, kf8_header_index)) nrecs += 1 if num_of_resources is not None: exth.write(pack(b'>III', EXTH_CODES['num_of_resources'], 12, num_of_resources)) nrecs += 1 if kf8_unknown_count is not None: exth.write(pack(b'>III', EXTH_CODES['kf8_unknown_count'], 12, kf8_unknown_count)) nrecs += 1 #Extra metadata for fullscrenn if opts and opts.book_mode == 'comic': #added for kindleear [insert0003 2017-09-03] exth.write(pack(b'>II', EXTH_CODES['RegionMagnification'], 13)) exth.write(b'false') exth.write(pack(b'>II', EXTH_CODES['book-type'], 13)) exth.write(b'comic') exth.write(pack(b'>II', EXTH_CODES['zero-gutter'], 12)) exth.write(b'true') exth.write(pack(b'>II', EXTH_CODES['zero-margin'], 12)) exth.write(b'true') exth.write(pack(b'>II', EXTH_CODES['primary-writing-mode'], 21)) exth.write(b'horizontal-lr') exth.write(pack(b'>II', EXTH_CODES['fixed-layout'], 12)) exth.write(b'true') exth.write(pack(b'>II', EXTH_CODES['orientation-lock'], 16)) exth.write(b'portrait') original_resolution = b'%dx%d' % opts.dest.comic_screen_size #sth like comic_screen_size = (1072, 1430) exth.write(pack(b'>II', EXTH_CODES['original-resolution'], len(original_resolution) + 8)) exth.write(original_resolution) nrecs += 8 exth = exth.getvalue() trail = len(exth) % 4 pad = b'\0' * (4 - trail) # Always pad w/ at least 1 byte exth = [b'EXTH', pack(b'>II', len(exth) + 12, nrecs), exth, pad] return b''.join(exth)
def update_text_record(self, record, book, path, bl_index, gtz_count, ltz_count, use_tz_var): """ Update the Sony database from the book. This is done if the timestamp in the db differs from the timestamp on the file. """ # It seems that a Sony device can sometimes know what timezone it is in, # and apparently converts the dates to GMT when it writes them to its # DB. We can detect that a device is timezone-aware because there is a # 'tz' variable in the Sony DB, which we can set to "0" to tell the # device to ignore its own timezone when comparing mtime to the date in # the DB. # Unfortunately, if there is no tz variable in the DB, then we can't # tell when the device applies a timezone conversion. We use a horrible # heuristic to work around this problem. First, set dates only for new # books, trying to avoid upsetting the sony. Second, voting: if a book # is not new, compare its Sony DB date against localtime and gmtime. # Count the matches. When we must set a date, use the one with the most # matches. Use localtime if the case of a tie, and hope it is right. try: timestamp = os.path.getmtime(path) except: debug_print("Failed to get timestamp for:", path) timestamp = time.time() rec_date = record.get("date", None) def clean(x): if isbytestring(x): x = x.decode(preferred_encoding, "replace") x.replace(u"\0", "") return x def record_set(k, v): try: record.set(k, clean(v)) except: # v is not suitable for XML, ignore pass if not getattr(book, "_new_book", False): # book is not new if record.get("tz", None) is not None: use_tz_var = True if strftime(timestamp, zone=time.gmtime) == rec_date: gtz_count += 1 elif strftime(timestamp, zone=time.localtime) == rec_date: ltz_count += 1 else: # book is new. Set the time using the current votes if use_tz_var: tz = time.localtime record.set("tz", "0") debug_print("Use localtime TZ and tz='0' for new book", book.lpath) elif ltz_count >= gtz_count: tz = time.localtime debug_print("Use localtime TZ for new book", book.lpath) else: tz = time.gmtime debug_print("Use GMT TZ for new book", book.lpath) date = strftime(timestamp, zone=tz) record.set("date", clean(date)) try: record.set("size", clean(str(os.stat(path).st_size))) except: record.set("size", "0") title = book.title if book.title else _("Unknown") record_set("title", title) ts = book.title_sort if not ts: ts = title_sort(title) record_set("titleSorter", ts) if self.use_author_sort: if book.author_sort: aus = book.author_sort else: debug_print("Author_sort is None for book", book.lpath) aus = authors_to_sort_string(book.authors) record_set("author", aus) else: record_set("author", authors_to_string(book.authors)) ext = os.path.splitext(path)[1] if ext: ext = ext[1:].lower() mime = MIME_MAP.get(ext, None) if mime is None: mime = guess_type("a." + ext)[0] if mime is not None: record.set("mime", clean(mime)) if "sourceid" not in record.attrib: record.set("sourceid", "1") if "id" not in record.attrib: num = self.max_id(record.getroottree().getroot()) record.set("id", str(num + 1)) return (gtz_count, ltz_count, use_tz_var)
def update_text_record(self, record, book, path, bl_index, gtz_count, ltz_count, use_tz_var): ''' Update the Sony database from the book. This is done if the timestamp in the db differs from the timestamp on the file. ''' # It seems that a Sony device can sometimes know what timezone it is in, # and apparently converts the dates to GMT when it writes them to its # DB. We can detect that a device is timezone-aware because there is a # 'tz' variable in the Sony DB, which we can set to "0" to tell the # device to ignore its own timezone when comparing mtime to the date in # the DB. # Unfortunately, if there is no tz variable in the DB, then we can't # tell when the device applies a timezone conversion. We use a horrible # heuristic to work around this problem. First, set dates only for new # books, trying to avoid upsetting the sony. Second, voting: if a book # is not new, compare its Sony DB date against localtime and gmtime. # Count the matches. When we must set a date, use the one with the most # matches. Use localtime if the case of a tie, and hope it is right. try: timestamp = os.path.getmtime(path) except: debug_print('Failed to get timestamp for:', path) timestamp = time.time() rec_date = record.get('date', None) def clean(x): if isbytestring(x): x = x.decode(preferred_encoding, 'replace') x.replace(u'\0', '') return x def record_set(k, v): try: record.set(k, clean(v)) except: # v is not suitable for XML, ignore pass if not getattr(book, '_new_book', False): # book is not new if record.get('tz', None) is not None: use_tz_var = True if strftime(timestamp, zone=time.gmtime) == rec_date: gtz_count += 1 elif strftime(timestamp, zone=time.localtime) == rec_date: ltz_count += 1 else: # book is new. Set the time using the current votes if use_tz_var: tz = time.localtime record.set('tz', '0') debug_print("Use localtime TZ and tz='0' for new book", book.lpath) elif ltz_count >= gtz_count: tz = time.localtime debug_print("Use localtime TZ for new book", book.lpath) else: tz = time.gmtime debug_print("Use GMT TZ for new book", book.lpath) date = strftime(timestamp, zone=tz) record.set('date', clean(date)) try: record.set('size', clean(str(os.stat(path).st_size))) except: record.set('size', '0') title = book.title if book.title else _('Unknown') record_set('title', title) ts = book.title_sort if not ts: ts = title_sort(title) record_set('titleSorter', ts) if self.use_author_sort: if book.author_sort: aus = book.author_sort else: debug_print('Author_sort is None for book', book.lpath) aus = authors_to_sort_string(book.authors) record_set('author', aus) else: record_set('author', authors_to_string(book.authors)) ext = os.path.splitext(path)[1] if ext: ext = ext[1:].lower() mime = MIME_MAP.get(ext, None) if mime is None: mime = guess_type('a.'+ext)[0] if mime is not None: record.set('mime', clean(mime)) if 'sourceid' not in record.attrib: record.set('sourceid', '1') if 'id' not in record.attrib: num = self.max_id(record.getroottree().getroot()) record.set('id', str(num+1)) return (gtz_count, ltz_count, use_tz_var)
def build_exth(metadata, prefer_author_sort=False, is_periodical=False, share_not_sync=True, cover_offset=None, thumbnail_offset=None, start_offset=None, mobi_doctype=2, num_of_resources=None, kf8_unknown_count=0, be_kindlegen2=False, kf8_header_index=None): exth = BytesIO() nrecs = 0 for term in metadata: if term not in EXTH_CODES: continue code = EXTH_CODES[term] items = metadata[term] if term == 'creator': if prefer_author_sort: creators = [authors_to_sort_string([unicode(c)]) for c in items] else: creators = [unicode(c) for c in items] items = creators elif term == 'rights': try: rights = utf8_text(unicode(metadata.rights[0])) except: rights = b'Unknown' exth.write(pack(b'>II', EXTH_CODES['rights'], len(rights) + 8)) exth.write(rights) nrecs += 1 continue for item in items: data = unicode(item) if term != 'description': data = COLLAPSE_RE.sub(' ', data) if term == 'identifier': if data.lower().startswith('urn:isbn:'): data = data[9:] elif item.scheme.lower() == 'isbn': pass else: continue if term == 'language': d2 = lang_as_iso639_1(data) if d2: data = d2 data = utf8_text(data) exth.write(pack(b'>II', code, len(data) + 8)) exth.write(data) nrecs += 1 # Write UUID as ASIN uuid = None from calibre.ebooks.oeb.base import OPF for x in 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') if not share_not_sync: exth.write(pack(b'>II', 113, len(uuid) + 8)) exth.write(uuid) nrecs += 1 # Write UUID as SOURCE c_uuid = b'calibre:%s' % uuid exth.write(pack(b'>II', 112, len(c_uuid) + 8)) exth.write(c_uuid) nrecs += 1 # Write cdetype if not is_periodical: if not share_not_sync: exth.write(pack(b'>II', 501, 12)) exth.write(b'EBOK') nrecs += 1 else: ids = {0x101:b'NWPR', 0x103:b'MAGZ'}.get(mobi_doctype, None) if ids: exth.write(pack(b'>II', 501, 12)) exth.write(ids) nrecs += 1 # Add a publication date entry if metadata['date']: datestr = str(metadata['date'][0]) elif metadata['timestamp']: datestr = str(metadata['timestamp'][0]) if datestr is None: raise ValueError("missing date or timestamp") datestr = bytes(datestr) exth.write(pack(b'>II', EXTH_CODES['pubdate'], len(datestr) + 8)) exth.write(datestr) nrecs += 1 if is_periodical: exth.write(pack(b'>II', EXTH_CODES['lastupdatetime'], len(datestr) + 8)) exth.write(datestr) nrecs += 1 if be_kindlegen2: mv = 200 if iswindows else 202 if isosx else 201 vals = {204:mv, 205:2, 206:9, 207:0} elif is_periodical: # Pretend to be amazon's super secret periodical generator vals = {204:201, 205:2, 206:0, 207:101} else: # Pretend to be kindlegen 1.2 vals = {204:201, 205:1, 206:2, 207:33307} for code, val in vals.iteritems(): exth.write(pack(b'>III', code, 12, val)) nrecs += 1 if be_kindlegen2: revnum = b'0730-890adc2' exth.write(pack(b'>II', 535, 8 + len(revnum)) + revnum) nrecs += 1 if cover_offset is not None: exth.write(pack(b'>III', EXTH_CODES['coveroffset'], 12, cover_offset)) exth.write(pack(b'>III', EXTH_CODES['hasfakecover'], 12, 0)) nrecs += 2 if thumbnail_offset is not None: exth.write(pack(b'>III', EXTH_CODES['thumboffset'], 12, thumbnail_offset)) thumbnail_uri_str = bytes('kindle:embed:%s' %(to_base(thumbnail_offset, base=32, min_num_digits=4))) exth.write(pack(b'>II', EXTH_CODES['kf8_thumbnail_uri'], len(thumbnail_uri_str) + 8)) exth.write(thumbnail_uri_str) nrecs += 2 if start_offset is not None: try: len(start_offset) except TypeError: start_offset = [start_offset] for so in start_offset: if so is not None: exth.write(pack(b'>III', EXTH_CODES['startreading'], 12, so)) nrecs += 1 if kf8_header_index is not None: exth.write(pack(b'>III', EXTH_CODES['kf8_header_index'], 12, kf8_header_index)) nrecs += 1 if num_of_resources is not None: exth.write(pack(b'>III', EXTH_CODES['num_of_resources'], 12, num_of_resources)) nrecs += 1 if kf8_unknown_count is not None: exth.write(pack(b'>III', EXTH_CODES['kf8_unknown_count'], 12, kf8_unknown_count)) nrecs += 1 exth = exth.getvalue() trail = len(exth) % 4 pad = b'\0' * (4 - trail) # Always pad w/ at least 1 byte exth = [b'EXTH', pack(b'>II', len(exth) + 12, nrecs), exth, pad] return b''.join(exth)