def update(self, mi): mi.title = normalize(mi.title) def update_exth_record(rec): recs.append(rec) if rec[0] in self.original_exth_records: self.original_exth_records.pop(rec[0]) if self.type != "BOOKMOBI": raise MobiError("Setting metadata only supported for MOBI files of type 'BOOK'.\n" "\tThis is a %r file of type %r" % (self.type[0:4], self.type[4:8])) recs = [] added_501 = False try: from calibre.ebooks.conversion.config import load_defaults prefs = load_defaults('mobi_output') pas = prefs.get('prefer_author_sort', False) kindle_pdoc = prefs.get('personal_doc', None) share_not_sync = prefs.get('share_not_sync', False) except: pas = False kindle_pdoc = None share_not_sync = False if mi.author_sort and pas: # We want an EXTH field per author... authors = mi.author_sort.split(' & ') for author in authors: update_exth_record((100, normalize(author).encode(self.codec, 'replace'))) elif mi.authors: authors = mi.authors for author in authors: update_exth_record((100, normalize(author).encode(self.codec, 'replace'))) if mi.publisher: update_exth_record((101, normalize(mi.publisher).encode(self.codec, 'replace'))) if mi.comments: # Strip user annotations a_offset = mi.comments.find('<div class="user_annotations">') ad_offset = mi.comments.find('<hr class="annotations_divider" />') if a_offset >= 0: mi.comments = mi.comments[:a_offset] if ad_offset >= 0: mi.comments = mi.comments[:ad_offset] update_exth_record((103, normalize(mi.comments).encode(self.codec, 'replace'))) if mi.isbn: update_exth_record((104, mi.isbn.encode(self.codec, 'replace'))) if mi.tags: # FIXME: Keep a single subject per EXTH field? subjects = '; '.join(mi.tags) update_exth_record((105, normalize(subjects).encode(self.codec, 'replace'))) if kindle_pdoc and kindle_pdoc in mi.tags: added_501 = True update_exth_record((501, b'PDOC')) if mi.pubdate: update_exth_record((106, str(mi.pubdate).encode(self.codec, 'replace'))) elif mi.timestamp: update_exth_record((106, str(mi.timestamp).encode(self.codec, 'replace'))) elif self.timestamp: update_exth_record((106, self.timestamp)) else: update_exth_record((106, nowf().isoformat().encode(self.codec, 'replace'))) if self.cover_record is not None: update_exth_record((201, pack('>I', self.cover_rindex))) update_exth_record((203, pack('>I', 0))) if self.thumbnail_record is not None: update_exth_record((202, pack('>I', self.thumbnail_rindex))) # Add a 113 record if not present to allow Amazon syncing if (113 not in self.original_exth_records and self.original_exth_records.get(501, None) == 'EBOK' and not added_501 and not share_not_sync): from uuid import uuid4 update_exth_record((113, str(uuid4()))) # Add a 112 record with actual UUID if getattr(mi, 'uuid', None): update_exth_record((112, (u"calibre:%s" % mi.uuid).encode(self.codec, 'replace'))) if 503 in self.original_exth_records: update_exth_record((503, mi.title.encode(self.codec, 'replace'))) # Update book producer if getattr(mi, 'book_producer', False): update_exth_record((108, mi.book_producer.encode(self.codec, 'replace'))) # Set langcode in EXTH header if not mi.is_null('language'): lang = canonicalize_lang(mi.language) lang = lang_as_iso639_1(lang) or lang if lang: update_exth_record((524, lang.encode(self.codec, 'replace'))) # Include remaining original EXTH fields for id in sorted(self.original_exth_records): recs.append((id, self.original_exth_records[id])) recs = sorted(recs, key=lambda x:(x[0],x[0])) exth = StringIO() for code, data in recs: exth.write(pack('>II', code, len(data) + 8)) exth.write(data) exth = exth.getvalue() trail = len(exth) % 4 pad = '\0' * (4 - trail) # Always pad w/ at least 1 byte exth = ['EXTH', pack('>II', len(exth) + 12, len(recs)), exth, pad] exth = ''.join(exth) if getattr(self, 'exth', None) is None: raise MobiError('No existing EXTH record. Cannot update metadata.') if not mi.is_null('language'): self.record0[92:96] = iana2mobi(mi.language) self.create_exth(exth=exth, new_title=mi.title) # Fetch updated timestamp, cover_record, thumbnail_record self.fetchEXTHFields() if mi.cover_data[1] or mi.cover: try: data = mi.cover_data[1] if mi.cover_data[1] else open(mi.cover, 'rb').read() except: pass else: if is_image(self.cover_record): size = len(self.cover_record) cover = rescale_image(data, size) if len(cover) <= size: cover += b'\0' * (size - len(cover)) self.cover_record[:] = cover if is_image(self.thumbnail_record): size = len(self.thumbnail_record) thumbnail = rescale_image(data, size, dimen=MAX_THUMB_DIMEN) if len(thumbnail) <= size: thumbnail += b'\0' * (size - len(thumbnail)) self.thumbnail_record[:] = thumbnail return
def update(self, mi, asin=None): mi.title = normalize(mi.title) def update_exth_record(rec): recs.append(rec) if rec[0] in self.original_exth_records: self.original_exth_records.pop(rec[0]) if self.type != b"BOOKMOBI": raise MobiError( "Setting metadata only supported for MOBI files of type 'BOOK'.\n" "\tThis is a %r file of type %r" % (self.type[0:4], self.type[4:8])) recs = [] added_501 = False try: from calibre.ebooks.conversion.config import load_defaults prefs = load_defaults('mobi_output') pas = prefs.get('prefer_author_sort', False) kindle_pdoc = prefs.get('personal_doc', None) share_not_sync = prefs.get('share_not_sync', False) except: pas = False kindle_pdoc = None share_not_sync = False if mi.author_sort and pas: # We want an EXTH field per author... authors = mi.author_sort.split(' & ') for author in authors: update_exth_record( (100, normalize(author).encode(self.codec, 'replace'))) elif mi.authors: authors = mi.authors for author in authors: update_exth_record( (100, normalize(author).encode(self.codec, 'replace'))) if mi.publisher: update_exth_record( (101, normalize(mi.publisher).encode(self.codec, 'replace'))) if mi.comments: # Strip user annotations a_offset = mi.comments.find('<div class="user_annotations">') ad_offset = mi.comments.find('<hr class="annotations_divider" />') if a_offset >= 0: mi.comments = mi.comments[:a_offset] if ad_offset >= 0: mi.comments = mi.comments[:ad_offset] update_exth_record( (103, normalize(mi.comments).encode(self.codec, 'replace'))) if mi.isbn: update_exth_record((104, mi.isbn.encode(self.codec, 'replace'))) if mi.tags: # FIXME: Keep a single subject per EXTH field? subjects = '; '.join(mi.tags) update_exth_record( (105, normalize(subjects).encode(self.codec, 'replace'))) if kindle_pdoc and kindle_pdoc in mi.tags: added_501 = True update_exth_record((501, b'PDOC')) if mi.pubdate: update_exth_record( (106, unicode_type(mi.pubdate).encode(self.codec, 'replace'))) elif mi.timestamp: update_exth_record( (106, unicode_type(mi.timestamp).encode(self.codec, 'replace'))) elif self.timestamp: update_exth_record((106, self.timestamp)) else: update_exth_record( (106, nowf().isoformat().encode(self.codec, 'replace'))) if self.cover_record is not None: update_exth_record((201, pack('>I', self.cover_rindex))) update_exth_record((203, pack('>I', 0))) if self.thumbnail_record is not None: update_exth_record((202, pack('>I', self.thumbnail_rindex))) # Add a 113 record if not present to allow Amazon syncing if (113 not in self.original_exth_records and self.original_exth_records.get(501, None) == 'EBOK' and not added_501 and not share_not_sync): from uuid import uuid4 update_exth_record((113, unicode_type(uuid4()).encode(self.codec))) if asin is not None: update_exth_record((113, asin.encode(self.codec))) update_exth_record((504, asin.encode(self.codec))) # Add a 112 record with actual UUID if getattr(mi, 'uuid', None): update_exth_record( (112, ("calibre:%s" % mi.uuid).encode(self.codec, 'replace'))) if 503 in self.original_exth_records: update_exth_record((503, mi.title.encode(self.codec, 'replace'))) # Update book producer if getattr(mi, 'book_producer', False): update_exth_record( (108, mi.book_producer.encode(self.codec, 'replace'))) # Set langcode in EXTH header if not mi.is_null('language'): lang = canonicalize_lang(mi.language) lang = lang_as_iso639_1(lang) or lang if lang: update_exth_record((524, lang.encode(self.codec, 'replace'))) # Include remaining original EXTH fields for id in sorted(self.original_exth_records): recs.append((id, self.original_exth_records[id])) recs = sorted(recs, key=lambda x: (x[0], x[0])) exth = io.BytesIO() for code, data in recs: exth.write(pack('>II', code, len(data) + 8)) exth.write(data) exth = exth.getvalue() trail = len(exth) % 4 pad = b'\0' * (4 - trail) # Always pad w/ at least 1 byte exth = [b'EXTH', pack('>II', len(exth) + 12, len(recs)), exth, pad] exth = b''.join(exth) if getattr(self, 'exth', None) is None: raise MobiError('No existing EXTH record. Cannot update metadata.') if not mi.is_null('language'): self.record0[92:96] = iana2mobi(mi.language) self.create_exth(exth=exth, new_title=mi.title) # Fetch updated timestamp, cover_record, thumbnail_record self.fetchEXTHFields() if mi.cover_data[1] or mi.cover: try: data = mi.cover_data[1] if not data: with open(mi.cover, 'rb') as f: data = f.read() except: pass else: if is_image(self.cover_record): size = len(self.cover_record) cover = rescale_image(data, size) if len(cover) <= size: cover += b'\0' * (size - len(cover)) self.cover_record[:] = cover if is_image(self.thumbnail_record): size = len(self.thumbnail_record) thumbnail = rescale_image(data, size, dimen=MAX_THUMB_DIMEN) if len(thumbnail) <= size: thumbnail += b'\0' * (size - len(thumbnail)) self.thumbnail_record[:] = thumbnail return
def generate_record0(self): # MOBI header {{{ metadata = self.oeb.metadata bt = 0x002 if self.primary_index_record_idx is not None: if False and self.indexer.is_flat_periodical: # Disabled as setting this to 0x102 causes the Kindle to not # auto archive the issues bt = 0x102 elif self.indexer.is_periodical: # If you change this, remember to change the cdetype in the EXTH # header as well bt = 0x103 if self.indexer.is_flat_periodical else 0x101 from calibre.ebooks.mobi.writer8.exth import build_exth exth = build_exth(metadata, prefer_author_sort=self.opts.prefer_author_sort, is_periodical=self.is_periodical, share_not_sync=self.opts.share_not_sync, cover_offset=self.cover_offset, thumbnail_offset=self.thumbnail_offset, start_offset=self.serializer.start_offset, mobi_doctype=bt ) first_image_record = None if self.resources: used_images = self.serializer.used_images first_image_record = len(self.records) self.resources.serialize(self.records, used_images) last_content_record = len(self.records) - 1 # FCIS/FLIS (Seems to serve no purpose) flis_number = len(self.records) self.records.append(FLIS) fcis_number = len(self.records) self.records.append(fcis(self.text_length)) # EOF record self.records.append(b'\xE9\x8E\x0D\x0A') record0 = io.BytesIO() # The MOBI Header record0.write(pack(b'>HHIHHHH', self.compression, # compression type # compression type 0, # Unused self.text_length, # Text length self.last_text_record_idx, # Number of text records or last tr idx RECORD_SIZE, # Text record size 0, # Unused 0 # Unused )) # 0 - 15 (0x0 - 0xf) uid = random.randint(0, 0xffffffff) title = normalize(unicode_type(metadata.title[0])).encode('utf-8') # 0x0 - 0x3 record0.write(b'MOBI') # 0x4 - 0x7 : Length of header # 0x8 - 0x11 : MOBI type # type meaning # 0x002 MOBI book (chapter - chapter navigation) # 0x101 News - Hierarchical navigation with sections and articles # 0x102 News feed - Flat navigation # 0x103 News magazine - same as 0x101 # 0xC - 0xF : Text encoding (65001 is utf-8) # 0x10 - 0x13 : UID # 0x14 - 0x17 : Generator version record0.write(pack(b'>IIIII', 0xe8, bt, 65001, uid, 6)) # 0x18 - 0x1f : Unknown record0.write(b'\xff' * 8) # 0x20 - 0x23 : Secondary index record sir = 0xffffffff if (self.primary_index_record_idx is not None and self.indexer.secondary_record_offset is not None): sir = (self.primary_index_record_idx + self.indexer.secondary_record_offset) record0.write(pack(b'>I', sir)) # 0x24 - 0x3f : Unknown record0.write(b'\xff' * 28) # 0x40 - 0x43 : Offset of first non-text record record0.write(pack(b'>I', self.first_non_text_record_idx)) # 0x44 - 0x4b : title offset, title length record0.write(pack(b'>II', 0xe8 + 16 + len(exth), len(title))) # 0x4c - 0x4f : Language specifier record0.write(iana2mobi( unicode_type(metadata.language[0]))) # 0x50 - 0x57 : Input language and Output language record0.write(b'\0' * 8) # 0x58 - 0x5b : Format version # 0x5c - 0x5f : First image record number record0.write(pack(b'>II', 6, first_image_record if first_image_record else len(self.records))) # 0x60 - 0x63 : First HUFF/CDIC record number # 0x64 - 0x67 : Number of HUFF/CDIC records # 0x68 - 0x6b : First DATP record number # 0x6c - 0x6f : Number of DATP records record0.write(b'\0' * 16) # 0x70 - 0x73 : EXTH flags # Bit 6 (0b1000000) being set indicates the presence of an EXTH header # Bit 12 being set indicates the presence of embedded fonts # The purpose of the other bits is unknown exth_flags = 0b1010000 if self.is_periodical: exth_flags |= 0b1000 if self.resources.has_fonts: exth_flags |= 0b1000000000000 record0.write(pack(b'>I', exth_flags)) # 0x74 - 0x93 : Unknown record0.write(b'\0' * 32) # 0x94 - 0x97 : DRM offset # 0x98 - 0x9b : DRM count # 0x9c - 0x9f : DRM size # 0xa0 - 0xa3 : DRM flags record0.write(pack(b'>IIII', 0xffffffff, 0xffffffff, 0, 0)) # 0xa4 - 0xaf : Unknown record0.write(b'\0'*12) # 0xb0 - 0xb1 : First content record number # 0xb2 - 0xb3 : last content record number # (Includes Image, DATP, HUFF, DRM) record0.write(pack(b'>HH', 1, last_content_record)) # 0xb4 - 0xb7 : Unknown record0.write(b'\0\0\0\x01') # 0xb8 - 0xbb : FCIS record number record0.write(pack(b'>I', fcis_number)) # 0xbc - 0xbf : Unknown (FCIS record count?) record0.write(pack(b'>I', 1)) # 0xc0 - 0xc3 : FLIS record number record0.write(pack(b'>I', flis_number)) # 0xc4 - 0xc7 : Unknown (FLIS record count?) record0.write(pack(b'>I', 1)) # 0xc8 - 0xcf : Unknown record0.write(b'\0'*8) # 0xd0 - 0xdf : Unknown record0.write(pack(b'>IIII', 0xffffffff, 0, 0xffffffff, 0xffffffff)) # 0xe0 - 0xe3 : Extra record data # Extra record data flags: # - 0b1 : <extra multibyte bytes><size> # - 0b10 : <TBS indexing description of this HTML record><size> # - 0b100: <uncrossable breaks><size> # Setting bit 2 (0x2) disables <guide><reference type="start"> functionality extra_data_flags = 0b1 # Has multibyte overlap bytes if self.primary_index_record_idx is not None: extra_data_flags |= 0b10 if WRITE_UNCROSSABLE_BREAKS: extra_data_flags |= 0b100 record0.write(pack(b'>I', extra_data_flags)) # 0xe4 - 0xe7 : Primary index record record0.write(pack(b'>I', 0xffffffff if self.primary_index_record_idx is None else self.primary_index_record_idx)) record0.write(exth) record0.write(title) record0 = record0.getvalue() # Add some buffer so that Amazon can add encryption information if this # MOBI is submitted for publication record0 += (b'\0' * (1024*8)) self.records[0] = align_block(record0)
def build_records(self, writer, for_joint): metadata = writer.oeb.metadata # The text records for x in ('last_text_record_idx', 'first_non_text_record_idx'): setattr(self, x.rpartition('_')[0], getattr(writer, x)) self.records = writer.records self.text_length = writer.text_length # KF8 Indices self.chunk_index = len(self.records) self.records.extend(writer.chunk_records) self.skel_index = len(self.records) self.records.extend(writer.skel_records) self.guide_index = NULL_INDEX if writer.guide_records: self.guide_index = len(self.records) self.records.extend(writer.guide_records) self.ncx_index = NULL_INDEX if writer.ncx_records: self.ncx_index = len(self.records) self.records.extend(writer.ncx_records) # Resources resources = writer.resources for x in ('cover_offset', 'thumbnail_offset', 'masthead_offset'): setattr(self, x, getattr(resources, x)) self.first_resource_record = NULL_INDEX before = len(self.records) if resources.records: self.first_resource_record = len(self.records) if not for_joint: resources.serialize(self.records, writer.used_images) self.num_of_resources = len(self.records) - before # FDST self.fdst_count = writer.fdst_count self.fdst_record = len(self.records) self.records.extend(writer.fdst_records) # FLIS/FCIS self.flis_record = len(self.records) self.records.append(FLIS) self.fcis_record = len(self.records) self.records.append(fcis(self.text_length)) # EOF self.records.append(b'\xe9\x8e\r\n') # EOF record # Miscellaneous header fields self.compression = writer.compress self.book_type = 0x101 if writer.opts.mobi_periodical else 2 self.full_title = utf8_text(unicode(metadata.title[0])) self.title_length = len(self.full_title) self.extra_data_flags = 0b1 if writer.has_tbs: self.extra_data_flags |= 0b10 self.uid = random.randint(0, 0xffffffff) self.language_code = iana2mobi(str(metadata.language[0])) self.exth_flags = 0b1010000 if writer.opts.mobi_periodical: self.exth_flags |= 0b1000 if resources.has_fonts: self.exth_flags |= 0b1000000000000 self.opts = writer.opts self.start_offset = writer.start_offset self.metadata = metadata self.kuc = 0 if len(resources.records) > 0 else None
def generate_record0(self): # MOBI header {{{ metadata = self.oeb.metadata bt = 0x002 if self.primary_index_record_idx is not None: if False and self.indexer.is_flat_periodical: # Disabled as setting this to 0x102 causes the Kindle to not # auto archive the issues bt = 0x102 elif self.indexer.is_periodical: # If you change this, remember to change the cdetype in the EXTH # header as well bt = 0x103 if self.indexer.is_flat_periodical else 0x101 from calibre.ebooks.mobi.writer8.exth import build_exth exth = build_exth(metadata, prefer_author_sort=self.opts.prefer_author_sort, is_periodical=self.is_periodical, share_not_sync=self.opts.share_not_sync, cover_offset=self.cover_offset, thumbnail_offset=self.thumbnail_offset, start_offset=self.serializer.start_offset, mobi_doctype=bt ) first_image_record = None if self.resources: used_images = self.serializer.used_images first_image_record = len(self.records) self.resources.serialize(self.records, used_images) last_content_record = len(self.records) - 1 # FCIS/FLIS (Seems to serve no purpose) flis_number = len(self.records) self.records.append(FLIS) fcis_number = len(self.records) self.records.append(fcis(self.text_length)) # EOF record self.records.append(b'\xE9\x8E\x0D\x0A') record0 = StringIO() # The MOBI Header record0.write(pack(b'>HHIHHHH', self.compression, # compression type # compression type 0, # Unused self.text_length, # Text length self.last_text_record_idx, # Number of text records or last tr idx RECORD_SIZE, # Text record size 0, # Unused 0 # Unused )) # 0 - 15 (0x0 - 0xf) uid = random.randint(0, 0xffffffff) title = normalize(unicode(metadata.title[0])).encode('utf-8') # 0x0 - 0x3 record0.write(b'MOBI') # 0x4 - 0x7 : Length of header # 0x8 - 0x11 : MOBI type # type meaning # 0x002 MOBI book (chapter - chapter navigation) # 0x101 News - Hierarchical navigation with sections and articles # 0x102 News feed - Flat navigation # 0x103 News magazine - same as 0x101 # 0xC - 0xF : Text encoding (65001 is utf-8) # 0x10 - 0x13 : UID # 0x14 - 0x17 : Generator version record0.write(pack(b'>IIIII', 0xe8, bt, 65001, uid, 6)) # 0x18 - 0x1f : Unknown record0.write(b'\xff' * 8) # 0x20 - 0x23 : Secondary index record sir = 0xffffffff if (self.primary_index_record_idx is not None and self.indexer.secondary_record_offset is not None): sir = (self.primary_index_record_idx + self.indexer.secondary_record_offset) record0.write(pack(b'>I', sir)) # 0x24 - 0x3f : Unknown record0.write(b'\xff' * 28) # 0x40 - 0x43 : Offset of first non-text record record0.write(pack(b'>I', self.first_non_text_record_idx)) # 0x44 - 0x4b : title offset, title length record0.write(pack(b'>II', 0xe8 + 16 + len(exth), len(title))) # 0x4c - 0x4f : Language specifier record0.write(iana2mobi( str(metadata.language[0]))) # 0x50 - 0x57 : Input language and Output language record0.write(b'\0' * 8) # 0x58 - 0x5b : Format version # 0x5c - 0x5f : First image record number record0.write(pack(b'>II', 6, first_image_record if first_image_record else len(self.records))) # 0x60 - 0x63 : First HUFF/CDIC record number # 0x64 - 0x67 : Number of HUFF/CDIC records # 0x68 - 0x6b : First DATP record number # 0x6c - 0x6f : Number of DATP records record0.write(b'\0' * 16) # 0x70 - 0x73 : EXTH flags # Bit 6 (0b1000000) being set indicates the presence of an EXTH header # Bit 12 being set indicates the presence of embedded fonts # The purpose of the other bits is unknown exth_flags = 0b1010000 if self.is_periodical: exth_flags |= 0b1000 if self.resources.has_fonts: exth_flags |= 0b1000000000000 record0.write(pack(b'>I', exth_flags)) # 0x74 - 0x93 : Unknown record0.write(b'\0' * 32) # 0x94 - 0x97 : DRM offset # 0x98 - 0x9b : DRM count # 0x9c - 0x9f : DRM size # 0xa0 - 0xa3 : DRM flags record0.write(pack(b'>IIII', 0xffffffff, 0xffffffff, 0, 0)) # 0xa4 - 0xaf : Unknown record0.write(b'\0'*12) # 0xb0 - 0xb1 : First content record number # 0xb2 - 0xb3 : last content record number # (Includes Image, DATP, HUFF, DRM) record0.write(pack(b'>HH', 1, last_content_record)) # 0xb4 - 0xb7 : Unknown record0.write(b'\0\0\0\x01') # 0xb8 - 0xbb : FCIS record number record0.write(pack(b'>I', fcis_number)) # 0xbc - 0xbf : Unknown (FCIS record count?) record0.write(pack(b'>I', 1)) # 0xc0 - 0xc3 : FLIS record number record0.write(pack(b'>I', flis_number)) # 0xc4 - 0xc7 : Unknown (FLIS record count?) record0.write(pack(b'>I', 1)) # 0xc8 - 0xcf : Unknown record0.write(b'\0'*8) # 0xd0 - 0xdf : Unknown record0.write(pack(b'>IIII', 0xffffffff, 0, 0xffffffff, 0xffffffff)) # 0xe0 - 0xe3 : Extra record data # Extra record data flags: # - 0b1 : <extra multibyte bytes><size> # - 0b10 : <TBS indexing description of this HTML record><size> # - 0b100: <uncrossable breaks><size> # Setting bit 2 (0x2) disables <guide><reference type="start"> functionality extra_data_flags = 0b1 # Has multibyte overlap bytes if self.primary_index_record_idx is not None: extra_data_flags |= 0b10 if WRITE_UNCROSSABLE_BREAKS: extra_data_flags |= 0b100 record0.write(pack(b'>I', extra_data_flags)) # 0xe4 - 0xe7 : Primary index record record0.write(pack(b'>I', 0xffffffff if self.primary_index_record_idx is None else self.primary_index_record_idx)) record0.write(exth) record0.write(title) record0 = record0.getvalue() # Add some buffer so that Amazon can add encryption information if this # MOBI is submitted for publication record0 += (b'\0' * (1024*8)) self.records[0] = align_block(record0)
def build_records(self, writer, for_joint): metadata = writer.oeb.metadata # The text records for x in ('last_text_record_idx', 'first_non_text_record_idx'): setattr(self, x.rpartition('_')[0], getattr(writer, x)) self.records = writer.records self.text_length = writer.text_length # KF8 Indices self.chunk_index = len(self.records) self.records.extend(writer.chunk_records) self.skel_index = len(self.records) self.records.extend(writer.skel_records) self.guide_index = NULL_INDEX if writer.guide_records: self.guide_index = len(self.records) self.records.extend(writer.guide_records) self.ncx_index = NULL_INDEX if writer.ncx_records: self.ncx_index = len(self.records) self.records.extend(writer.ncx_records) # Resources resources = writer.resources for x in ('cover_offset', 'thumbnail_offset', 'masthead_offset'): setattr(self, x, getattr(resources, x)) self.first_resource_record = NULL_INDEX before = len(self.records) if resources.records: self.first_resource_record = len(self.records) if not for_joint: resources.serialize(self.records, writer.used_images) self.num_of_resources = len(self.records) - before # FDST self.fdst_count = writer.fdst_count self.fdst_record = len(self.records) self.records.extend(writer.fdst_records) # FLIS/FCIS self.flis_record = len(self.records) self.records.append(FLIS) self.fcis_record = len(self.records) self.records.append(fcis(self.text_length)) # EOF self.records.append(b'\xe9\x8e\r\n') # EOF record # Miscellaneous header fields self.compression = writer.compress self.book_type = 0x101 if writer.opts.mobi_periodical else 2 self.full_title = utf8_text(unicode_type(metadata.title[0])) self.title_length = len(self.full_title) self.extra_data_flags = 0b1 if writer.has_tbs: self.extra_data_flags |= 0b10 self.uid = random.randint(0, 0xffffffff) self.language_code = iana2mobi(unicode_type(metadata.language[0])) self.exth_flags = 0b1010000 if writer.opts.mobi_periodical: self.exth_flags |= 0b1000 if resources.has_fonts: self.exth_flags |= 0b1000000000000 self.opts = writer.opts self.start_offset = writer.start_offset self.metadata = metadata self.kuc = 0 if len(resources.records) > 0 else None