diff --git a/format_docs/pdb/mobi.txt b/format_docs/pdb/mobi.txt index e378e1622b..bf08bd8b71 100644 --- a/format_docs/pdb/mobi.txt +++ b/format_docs/pdb/mobi.txt @@ -210,21 +210,23 @@ record type usual length name comments 114 versionnumber 115 sample 116 startreading -118 retail price (as text) -119 retail price currency (as text) -201 coveroffset -202 thumboffset +117 3 adult Mobipocket Creator adds this if Adult only is checked; contents: "yes" +118 retail price As text, e.g. "4.99" +119 retail price currency As text, e.g. "USD" +201 4 coveroffset Add to first image field in Mobi Header to find PDB record containing the cover image +202 4 thumboffset Add to first image field in Mobi Header to find PDB record containing the thumbnail cover image 203 hasfakecover -204 204 Unknown -205 205 Unknown -206 206 Unknown -207 207 Unknown -208 208 Unknown -300 300 Unknown -401 clippinglimit +204 4 Creator Software Records 204-207 are usually the same for all books from a certain source, e.g. 1-6-2-41 for Baen and 201-1-0-85 for project gutenberg, 200-1-0-85 for amazon when converted to a 32 bit integer. +205 4 Creator Major Version +206 4 Creator Minor Version +207 4 Creator Build Number +208 watermark +209 tamper proof keys Used by the Kindle (and Android app) for generating book-specific PIDs. +300 fontsignature +401 1 clippinglimit 402 publisherlimit -403 403 Unknown -404 404 ttsflag +403 403 Unknown 1 - Text to Speech disabled; 0 - Text to Speech enabled +404 1 404 ttsflag 501 4 cdetype PDOC - Personal Doc; EBOK - ebook; 502 lastupdatetime @@ -287,9 +289,9 @@ content at the beginning of the following record. The trailing entry ends with a byte containing a count of the overlapping bytes plus additional flags. offset bytes content comments -0 0-3 N terminal bytes +0 0-3 N terminal bytes of a multibyte - character + character N 1 Size & flags bits 1-2 encode N, use of bits 3-8 is unknown @@ -328,6 +330,102 @@ programs may ignore them entirely. They are stored at the end of the file itself so the full file needs to be scanned when loaded to find them. +Image Records +------------- + +If the file contains images, they follow the text blocks, with each image using a +single block. The 4096-byte record size in the PalmDoc header applies only to +text records; image records may be larger. + + +Magic Records +------------- + +In some cases, MobiPocket Creator adds a 2-zero-byte record after the text +records in a file. This record is not included in the "record count" of text +records in the PalmDoc header, and is also not used as the "first non-book +index" in the MOBI header. (If the 2-zero-byte record is present, the index of +the following block is used as the "first non-book index".) + +MobiPocket Creator also ends files with three records: 'FLIS', 'FCIS', and +'end-of-file', in that order. The 'FLIS' and 'FCIS' records do not seem to be +necessary for MobiPocket Reader or the Amazon Kindle 2 to read the file. The +'end-of-file' record might be necessary. + + +FLIS Record +----------- + +The FLIS record appears to have a fixed value. The meaning of the values is not known. + +offset bytes content comments +0 4 identifier the characters F L I S (0x46 0x4c 0x49 0x53) +4 4 ? fixed value: 8 +8 2 ? fixed value: 65 +10 2 ? fixed value: 0 +12 4 ? fixed value: 0 +16 4 ? fixed value: -1 +20 2 ? fixed value: 1 +22 2 ? fixed value: 3 +24 4 ? fixed value: 3 +28 4 ? fixed value: 1 +32 4 ? fixed value: -1 + + +FCIS Record +----------- + +The FCIS record appears to have mostly fixed values. + +offset bytes content comments +0 4 identifier the characters F C I S (0x46 0x43 0x49 0x53) +4 4 ? fixed value: 20 +8 4 ? fixed value: 16 +12 4 ? fixed value: 1 +16 4 ? fixed value: 0 +20 4 ? text length (the same value as "text length" in the PalmDoc header) +24 4 ? fixed value: 0 +28 4 ? fixed value: 32 +32 4 ? fixed value: 8 +36 2 ? fixed value: 1 +38 2 ? fixed value: 1 +40 4 ? fixed value: 0 + + +End-of-file Record +------------------ + +The end-of-file record is a fixed 4-byte record. While the last two bytes +appear to be a CRLF marker, the meaning of the first two bytes is unknown. + +offset bytes content comments +0 1 ? fixed value: 233 (0xe9) +1 1 ? fixed value: 142 (0x8e) +2 1 ? fixed value: 13 (0x0d) +3 1 ? fixed value: 10 (0x0a) + + +SRCS Record +----------- + +kindlegen creates a record whose content is a zip archive of all source files +(i.e., .opf, .ncx, .htm, .jpg, ...) given to the command and puts it in the +generated MOBI file. The record begins with the "SRCS" signature and is +located just before the #End-of-file Record. + +MOBI files created with Mobipocket creator, Amazon's Personal Document Service, +or Kindle Direct Publishing (former Amazon DTP) don't include SRCS record. +In a past, kindlegen had an undocumented option to suppress this record, but +the option was removed in 2010. + +offset bytes content comments +0 4 identifier "SRCS" (0x53 0x52 0x43 0x53) +4 4 ? fixed value(?): 0x00000010 +8 4 ? fixed value(?): 0x0000002f +12 4 ? fixed value(?): 0x00000001 +16 zip The zip archive continues to the end of this record + + MBP --- diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py index 2be699e525..fccaad8811 100644 --- a/src/calibre/ebooks/mobi/writer.py +++ b/src/calibre/ebooks/mobi/writer.py @@ -1098,7 +1098,7 @@ class MobiWriter(object): nodeCountValue = 0x80 if nodeCountValue == 0 else nodeCountValue tbSequence += chr(nodeCountValue) else : - tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 + tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) # len @@ -1188,7 +1188,7 @@ class MobiWriter(object): toc = self._oeb.toc nodes = list(toc.iter())[1:] toc_conforms = True - for (i, child) in enumerate(nodes) : + for child in nodes: if child.klass == "periodical" and child.depth() != 3 or \ child.klass == "section" and child.depth() != 2 or \ child.klass == "article" and child.depth() != 1 : @@ -1644,14 +1644,14 @@ class MobiWriter(object): self._oeb.log('Generating INDX ...') self._primary_index_record = None - # Build the NCXEntries and INDX + # Build the NCXEntries and INDX indxt, indxt_count, indices, last_name = self._generate_indxt() if last_name is None: self._oeb.log.warn('Input document has no TOC. No index generated.') return - # Assemble the INDX0[0] and INDX1[0] output streams + # Assemble the INDX0[0] and INDX1[0] output streams indx1 = StringIO() indx1.write('INDX'+pack('>I', 0xc0)) # header length @@ -2310,10 +2310,8 @@ class MobiWriter(object): parentIndex = sectionParent.parentIndex self._write_section_node(indxt, indices, sectionParent.myCtocMapIndex, index, offset, length, c, firstArticle, lastArticle, parentIndex) - last_name = "%04X"%c - # articles - for (i, article) in enumerate(list(sectionParent.articles)) : + for article in list(sectionParent.articles): index = article.myCtocMapIndex offset = article.startAddress length = article.articleLength @@ -2413,7 +2411,6 @@ class MobiWriter(object): # Article(s) child.depth() = 1 # Section 2 - documentType = "unknown" sectionIndices = [] sectionParents = [] currentSection = 0 # Starting section number @@ -2421,7 +2418,6 @@ class MobiWriter(object): indxt, indices, c = StringIO(), StringIO(), 0 indices.write('IDXT') - c = 0 last_name = None # 'book', 'periodical' or None @@ -2449,8 +2445,8 @@ class MobiWriter(object): if self.opts.verbose > 3 : self._oeb.logger.info("unknown document type %12.12s \tdepth:%d" % (child.title, child.depth()) ) - # Original code starts here - # test first node for depth/class + # Original code starts here + # test first node for depth/class entries = list(toc.iter())[1:] for (i, child) in enumerate(entries): if not child.title or not child.title.strip(): diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 9649a79cc1..ae2ac694f2 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -29,6 +29,8 @@ gprefs.defaults['action-layout-toolbar'] = ( 'Connect Share', None, 'Remove Books', None, 'Help', 'Preferences', ) +gprefs.defaults['action-layout-toolbar-child'] = () + gprefs.defaults['action-layout-toolbar-device'] = ( 'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', 'Send To Device', None, None, 'Location Manager', None, None, @@ -52,7 +54,6 @@ gprefs.defaults['show_splash_screen'] = True gprefs.defaults['toolbar_icon_size'] = 'medium' gprefs.defaults['automerge'] = 'ignore' gprefs.defaults['toolbar_text'] = 'auto' -gprefs.defaults['show_child_bar'] = False gprefs.defaults['font'] = None gprefs.defaults['tags_browser_partition_method'] = 'first letter' gprefs.defaults['tags_browser_collapse_at'] = 100 diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py index 8563956b28..c0dd40326d 100644 --- a/src/calibre/gui2/actions/__init__.py +++ b/src/calibre/gui2/actions/__init__.py @@ -75,7 +75,7 @@ class InterfaceAction(QObject): dont_remove_from = frozenset([]) all_locations = frozenset(['toolbar', 'toolbar-device', 'context-menu', - 'context-menu-device']) + 'context-menu-device', 'toolbar-child']) #: Type of action #: 'current' means acts on the current view diff --git a/src/calibre/gui2/actions/add_to_library.py b/src/calibre/gui2/actions/add_to_library.py index 05aea8f1dd..fd686e3833 100644 --- a/src/calibre/gui2/actions/add_to_library.py +++ b/src/calibre/gui2/actions/add_to_library.py @@ -12,7 +12,7 @@ class AddToLibraryAction(InterfaceAction): name = 'Add To Library' action_spec = (_('Add books to library'), 'add_book.png', _('Add books to your calibre library from the connected device'), None) - dont_add_to = frozenset(['toolbar', 'context-menu']) + dont_add_to = frozenset(['toolbar', 'context-menu', 'toolbar-child']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index 0b0492228e..a4ca95a9bb 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -121,7 +121,7 @@ class SendToDeviceAction(InterfaceAction): name = 'Send To Device' action_spec = (_('Send to device'), 'sync.png', None, _('D')) dont_remove_from = frozenset(['toolbar-device']) - dont_add_to = frozenset(['toolbar', 'context-menu']) + dont_add_to = frozenset(['toolbar', 'context-menu', 'toolbar-child']) def genesis(self): self.qaction.triggered.connect(self.do_sync) diff --git a/src/calibre/gui2/actions/edit_collections.py b/src/calibre/gui2/actions/edit_collections.py index 7f5dd76538..c64a3249d4 100644 --- a/src/calibre/gui2/actions/edit_collections.py +++ b/src/calibre/gui2/actions/edit_collections.py @@ -12,7 +12,7 @@ class EditCollectionsAction(InterfaceAction): name = 'Edit Collections' action_spec = (_('Manage collections'), None, _('Manage the collections on this device'), None) - dont_add_to = frozenset(['toolbar', 'context-menu']) + dont_add_to = frozenset(['toolbar', 'context-menu', 'toolbar-child']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index e8a4e79384..41b415e25c 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -278,11 +278,14 @@ class ToolBar(QToolBar): # {{{ pass def build_bar(self): - self.child_bar.setVisible(gprefs['show_child_bar']) self.showing_donate = False showing_device = self.location_manager.has_device - actions = '-device' if showing_device else '' - actions = gprefs['action-layout-toolbar'+actions] + mactions = '-device' if showing_device else '' + mactions = gprefs['action-layout-toolbar'+mactions] + cactions = gprefs['action-layout-toolbar-child'] + + show_child = len(cactions) > 0 + self.child_bar.setVisible(show_child) for ac in self.added_actions: m = ac.menu() @@ -292,44 +295,30 @@ class ToolBar(QToolBar): # {{{ self.clear() self.child_bar.clear() self.added_actions = [] - self.spacers = [Spacer(self.child_bar), Spacer(self.child_bar), - Spacer(self), Spacer(self)] - self.child_bar.addWidget(self.spacers[0]) - if gprefs['show_child_bar']: - self.addWidget(self.spacers[2]) - for what in actions: - if what is None and not gprefs['show_child_bar']: - self.addSeparator() - elif what == 'Location Manager': - for ac in self.location_manager.available_actions: - self.addAction(ac) - self.added_actions.append(ac) - self.setup_tool_button(ac, QToolButton.MenuButtonPopup) - elif what == 'Donate': - self.d_widget = QWidget() - self.d_widget.setLayout(QVBoxLayout()) - self.d_widget.layout().addWidget(self.donate_button) - self.addWidget(self.d_widget) - self.showing_donate = True - elif what in self.gui.iactions: - action = self.gui.iactions[what] - bar = self - if action.action_type == 'current' and gprefs['show_child_bar']: - bar = self.child_bar - bar.addAction(action.qaction) - self.added_actions.append(action.qaction) - self.setup_tool_button(action.qaction, action.popup_type) + for bar, actions in ((self, mactions), (self.child_bar, cactions)): + for what in actions: + if what is None: + bar.addSeparator() + elif what == 'Location Manager': + for ac in self.location_manager.available_actions: + bar.addAction(ac) + bar.added_actions.append(ac) + bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup) + elif what == 'Donate': + self.d_widget = QWidget() + self.d_widget.setLayout(QVBoxLayout()) + self.d_widget.layout().addWidget(self.donate_button) + bar.addWidget(self.d_widget) + self.showing_donate = True + elif what in self.gui.iactions: + action = self.gui.iactions[what] + bar.addAction(action.qaction) + self.added_actions.append(action.qaction) + self.setup_tool_button(bar, action.qaction, action.popup_type) - self.child_bar.addWidget(self.spacers[1]) - if gprefs['show_child_bar']: - self.addWidget(self.spacers[3]) - else: - for s in self.spacers[2:]: - s.setVisible(False) - - def setup_tool_button(self, ac, menu_mode=None): - ch = self.widgetForAction(ac) + def setup_tool_button(self, bar, ac, menu_mode=None): + ch = bar.widgetForAction(ac) if ch is None: ch = self.child_bar.widgetForAction(ac) ch.setCursor(Qt.PointingHandCursor) @@ -345,7 +334,7 @@ class ToolBar(QToolBar): # {{{ style = Qt.ToolButtonIconOnly if p == 'auto' and self.preferred_width > self.width()+35 and \ - not gprefs['show_child_bar']: + not gprefs['action-layout-toolbar-child']: style = Qt.ToolButtonIconOnly self.setToolButtonStyle(style) diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index a2d2236039..523a296a37 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -48,7 +48,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('disable_tray_notification', config) r('use_roman_numerals_for_series_number', config) r('separate_cover_flow', config, restart_required=True) - r('show_child_bar', gprefs) choices = [(_('Small'), 'small'), (_('Medium'), 'medium'), (_('Large'), 'large')] diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index bc965b89fa..996caeb653 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -6,8 +6,8 @@ 0 0 - 670 - 422 + 717 + 444 @@ -244,13 +244,6 @@ then the tags will be displayed each on their own line. - - - - &Split the toolbar into two toolbars - - - diff --git a/src/calibre/gui2/preferences/toolbar.py b/src/calibre/gui2/preferences/toolbar.py index a0d48f3910..93079110a5 100644 --- a/src/calibre/gui2/preferences/toolbar.py +++ b/src/calibre/gui2/preferences/toolbar.py @@ -208,6 +208,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): LOCATIONS = [ ('toolbar', _('The main toolbar')), + ('toolbar-child', _('The optional second toolbar')), ('toolbar-device', _('The main toolbar when a device is connected')), ('context-menu', _('The context menu for the books in the ' 'calibre library')),