From 6192b63e922e28df15e535611e63d6ccd092c3ea Mon Sep 17 00:00:00 2001 From: GRiker Date: Fri, 15 Mar 2013 06:55:53 -0700 Subject: [PATCH 01/18] Local debug override for Apple driver --- src/calibre/devices/apple/driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 5251e701b5..c51c8e8503 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -21,7 +21,8 @@ from calibre.utils.config import config_dir, dynamic, prefs from calibre.utils.date import now, parse_date from calibre.utils.zipfile import ZipFile -DEBUG = CALIBRE_DEBUG +#DEBUG = CALIBRE_DEBUG +DEBUG = False def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None): From 3ddc8d97fa9944c852dfb13a57e835f55fe92b67 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 17 Mar 2013 19:57:31 +0100 Subject: [PATCH 02/18] Fix creating advanced coloring rules --- src/calibre/gui2/preferences/coloring.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py index 1dce44b865..8d27d14e5b 100644 --- a/src/calibre/gui2/preferences/coloring.py +++ b/src/calibre/gui2/preferences/coloring.py @@ -763,22 +763,24 @@ class EditRules(QWidget): # {{{ ' double clicking it.')) self.add_advanced_button.setVisible(False) - def _add_rule(self, dlg): - if dlg.exec_() == dlg.Accepted: - kind, col, r = dlg.rule + def add_rule(self): + d = RuleEditor(self.model.fm, self.pref_name) + d.add_blank_condition() + if d.exec_() == d.Accepted: + kind, col, r = d.rule if kind and r and col: idx = self.model.add_rule(kind, col, r) self.rules_view.scrollTo(idx) self.changed.emit() - def add_rule(self): - d = RuleEditor(self.model.fm, self.pref_name) - d.add_blank_condition() - self._add_rule(d) - def add_advanced(self): td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, color_field='') - self._add_rule(('color', td[0], td[1])) + if td.exec_() == td.Accepted: + col, r = td.rule + if r and col: + idx = self.model.add_rule('color', col, r) + self.rules_view.scrollTo(idx) + self.changed.emit() def edit_rule(self, index): try: From f5849faccafa5176e9ca91bc79dd94afe4815820 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 18 Mar 2013 11:18:16 +0530 Subject: [PATCH 03/18] News download: Fix a regression in 0.9.23 that prevented oldest_article from working with some RSS feeds. --- src/calibre/web/feeds/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/calibre/web/feeds/__init__.py b/src/calibre/web/feeds/__init__.py index 43b404367c..d819aade7a 100644 --- a/src/calibre/web/feeds/__init__.py +++ b/src/calibre/web/feeds/__init__.py @@ -184,7 +184,12 @@ class Feed(object): id = 'internal id#%s'%self.id_counter if id in self.added_articles: return - published = item.get('date_parsed', time.gmtime()) + published = None + for date_field in ('date_parsed', 'published_parsed', + 'updated_parsed'): + published = item.get(date_field, None) + if published is not None: + break if not published: published = time.gmtime() self.added_articles.append(id) From ac450fb04b2a6e21a8f9acd63174cc36dcb94aa8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 18 Mar 2013 14:11:40 +0530 Subject: [PATCH 04/18] ... --- recipes/nytimesbook.recipe | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/recipes/nytimesbook.recipe b/recipes/nytimesbook.recipe index ad20586770..2d8fb69a7e 100644 --- a/recipes/nytimesbook.recipe +++ b/recipes/nytimesbook.recipe @@ -35,7 +35,10 @@ class NewYorkTimesBookReview(BasicNewsRecipe): continue if x['class'] in {'story', 'ledeStory'}: tt = 'h3' if x['class'] == 'story' else 'h1' - a = x.find(tt).find('a', href=True) + try: + a = x.find(tt).find('a', href=True) + except AttributeError: + continue title = self.tag_to_string(a) url = a['href'] + '&pagewanted=all' self.log('\tFound article:', title, url) From 3cb4f4e38253f181051d2b9dd126c09257029c34 Mon Sep 17 00:00:00 2001 From: GRiker Date: Mon, 18 Mar 2013 06:10:01 -0700 Subject: [PATCH 05/18] Add presets to catalog builder, support for presets to cli. --- src/calibre/devices/apple/driver.py | 3 +- src/calibre/gui2/catalog/catalog_epub_mobi.py | 283 ++++++++++++++++-- src/calibre/gui2/catalog/catalog_epub_mobi.ui | 121 +++++++- src/calibre/library/catalogs/epub_mobi.py | 53 +++- 4 files changed, 427 insertions(+), 33 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index c51c8e8503..5251e701b5 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -21,8 +21,7 @@ from calibre.utils.config import config_dir, dynamic, prefs from calibre.utils.date import now, parse_date from calibre.utils.zipfile import ZipFile -#DEBUG = CALIBRE_DEBUG -DEBUG = False +DEBUG = CALIBRE_DEBUG def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None): diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py index 57859ab501..91c769cbf0 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.py +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py @@ -12,20 +12,22 @@ from functools import partial from calibre.ebooks.conversion.config import load_defaults from calibre.gui2 import gprefs, open_url, question_dialog +from calibre.utils.config import JSONConfig from calibre.utils.icu import sort_key from catalog_epub_mobi_ui import Ui_Form +from PyQt4 import QtGui from PyQt4.Qt import (Qt, QAbstractItemView, QCheckBox, QComboBox, - QDoubleSpinBox, QIcon, QLineEdit, QObject, QRadioButton, QSize, QSizePolicy, - QTableWidget, QTableWidgetItem, QTextEdit, QToolButton, QUrl, - QVBoxLayout, QWidget, + QDoubleSpinBox, QIcon, QInputDialog, QLineEdit, QObject, QRadioButton, + QSize, QSizePolicy, QTableWidget, QTableWidgetItem, QTextEdit, QToolButton, + QUrl, QVBoxLayout, QWidget, SIGNAL) class PluginWidget(QWidget,Ui_Form): TITLE = _('E-book options') HELP = _('Options specific to')+' AZW3/EPUB/MOBI '+_('output') - DEBUG = False + DEBUG = True # Output synced to the connected device? sync_enabled = True @@ -212,8 +214,8 @@ class PluginWidget(QWidget,Ui_Form): else: results = _truncated_results(excluded_tags) finally: - if self.DEBUG: - print(results) + if False and self.DEBUG: + print("exclude_genre_changed(): %s" % results) self.exclude_genre_results.clear() self.exclude_genre_results.setText(results) @@ -239,11 +241,11 @@ class PluginWidget(QWidget,Ui_Form): Toggle Description-related controls ''' self.header_note_source_field.setEnabled(enabled) - self.thumb_width.setEnabled(enabled) - self.merge_source_field.setEnabled(enabled) - self.merge_before.setEnabled(enabled) - self.merge_after.setEnabled(enabled) self.include_hr.setEnabled(enabled) + self.merge_after.setEnabled(enabled) + self.merge_before.setEnabled(enabled) + self.merge_source_field.setEnabled(enabled) + self.thumb_width.setEnabled(enabled) def generate_genres_changed(self, enabled): ''' @@ -263,6 +265,22 @@ class PluginWidget(QWidget,Ui_Form): self.genre_source_field_name = genre_source_spec['field'] self.exclude_genre_changed() + def get_format_and_title(self): + current_format = None + current_title = None + self.parentWidget().blockSignals(True) + for peer in self.parentWidget().children(): + if peer == self: + continue + elif peer.children(): + for child in peer.children(): + if child.objectName() == 'format': + current_format = str(child.currentText()).strip() + elif child.objectName() == 'title': + current_title = str(child.text()).strip() + self.parentWidget().blockSignals(False) + return current_format, current_title + def header_note_source_field_changed(self,new_index): ''' Process changes in the header_note_source_field combo box @@ -374,15 +392,20 @@ class PluginWidget(QWidget,Ui_Form): # Initialize exclusion rules self.exclusion_rules_table = ExclusionRules(self.exclusion_rules_gb, - "exclusion_rules_tw",exclusion_rules, self.eligible_custom_fields,self.db) + "exclusion_rules_tw", exclusion_rules, self.eligible_custom_fields, self.db) # Initialize prefix rules self.prefix_rules_table = PrefixRules(self.prefix_rules_gb, - "prefix_rules_tw",prefix_rules, self.eligible_custom_fields,self.db) + "prefix_rules_tw", prefix_rules, self.eligible_custom_fields, self.db) # Initialize excluded genres preview self.exclude_genre_changed() + # Hook Preset signals + self.preset_delete_pb.clicked.connect(self.preset_remove) + self.preset_save_pb.clicked.connect(self.preset_save) + self.preset_field.currentIndexChanged[str].connect(self.preset_change) + def merge_source_field_changed(self,new_index): ''' Process changes in the merge_source_field combo box @@ -404,10 +427,12 @@ class PluginWidget(QWidget,Ui_Form): self.include_hr.setEnabled(False) def options(self): - # Save/return the current options - # exclude_genre stores literally - # Section switches store as True/False - # others store as lists + ''' + Return, optionally save current options + exclude_genre stores literally + Section switches store as True/False + others store as lists + ''' opts_dict = {} prefix_rules_processed = False @@ -469,7 +494,7 @@ class PluginWidget(QWidget,Ui_Form): except: opts_dict['output_profile'] = ['default'] - if self.DEBUG: + if False and self.DEBUG: print "opts_dict" for opt in sorted(opts_dict.keys(), key=sort_key): print " %s: %s" % (opt, repr(opts_dict[opt])) @@ -544,6 +569,214 @@ class PluginWidget(QWidget,Ui_Form): self.genre_source_fields = custom_fields self.genre_source_field.currentIndexChanged.connect(self.genre_source_field_changed) + # Populate the Presets combo box + self.presets = JSONConfig("catalog_presets") + self.preset_field.addItem("") + self.preset_field_values = sorted([p for p in self.presets], key=sort_key) + self.preset_field.addItems(self.preset_field_values) + + def preset_change(self, item_name): + ''' + Update catalog options from current preset + ''' + if not item_name: + return + + current_preset = str(self.preset_field.currentText()) + options = self.presets[current_preset] + + exclusion_rules = [] + prefix_rules = [] + for opt in self.OPTION_FIELDS: + c_name, c_def, c_type = opt + if c_name == 'preset_field': + continue + # Extra entries in options for cli invocation + if c_name in options: + opt_value = options[c_name] + else: + continue + if c_type in ['check_box']: + getattr(self, c_name).setChecked(eval(str(opt_value))) + if c_name == 'generate_genres': + self.genre_source_field.setEnabled(eval(str(opt_value))) + elif c_type in ['combo_box']: + if opt_value is None: + index = 0 + if c_name == 'genre_source_field': + index = self.genre_source_field.findText(_('Tags')) + else: + index = getattr(self,c_name).findText(opt_value) + if index == -1: + if c_name == 'read_source_field': + index = self.read_source_field.findText(_('Tags')) + elif c_name == 'genre_source_field': + index = self.genre_source_field.findText(_('Tags')) + getattr(self,c_name).setCurrentIndex(index) + elif c_type in ['line_edit']: + getattr(self, c_name).setText(opt_value if opt_value else '') + elif c_type in ['radio_button'] and opt_value is not None: + getattr(self, c_name).setChecked(opt_value) + elif c_type in ['spin_box']: + getattr(self, c_name).setValue(float(opt_value)) + if c_type == 'table_widget': + if c_name == 'exclusion_rules_tw': + if opt_value not in exclusion_rules: + exclusion_rules.append(opt_value) + if c_name == 'prefix_rules_tw': + if opt_value not in prefix_rules: + prefix_rules.append(opt_value) + + # Reset exclusion rules + self.exclusion_rules_table.clearLayout() + self.exclusion_rules_table = ExclusionRules(self.exclusion_rules_gb, + "exclusion_rules_tw", exclusion_rules, self.eligible_custom_fields, self.db) + + # Reset prefix rules + self.prefix_rules_table.clearLayout() + self.prefix_rules_table = PrefixRules(self.prefix_rules_gb, + "prefix_rules_tw", prefix_rules, self.eligible_custom_fields, self.db) + + # Reset excluded genres preview + self.exclude_genre_changed() + + # Reset format and title + format = options['format'] + title = options['catalog_title'] + self.set_format_and_title(format, title) + + def preset_remove(self): + print("preset_remove()") + if self.preset_field.currentIndex() == 0: + return + + if not question_dialog(self, _("Delete saved catalog preset"), + _("The selected saved catalog preset will be deleted. " + "Are you sure?")): + return + + item_id = self.preset_field.currentIndex() + item_name = unicode(self.preset_field.currentText()) + + self.preset_field.blockSignals(True) + self.preset_field.removeItem(item_id) + self.preset_field.blockSignals(False) + self.preset_field.setCurrentIndex(0) + + if item_name in self.presets.keys(): + del(self.presets[item_name]) + self.presets.commit() + + def preset_save(self): + names = [''] + names.extend(self.preset_field_values) + try: + dex = names.index(self.preset_search_name) + except: + dex = 0 + name = '' + while not name: + name, ok = QInputDialog.getItem(self, _('Save catalog preset'), + _('Preset name:'), names, dex, True) + if not ok: + return + if not name: + error_dialog(self, _("Save catalog preset"), + _("You must provide a name."), show=True) + new = True + name = unicode(name) + if name in self.presets.keys(): + if not question_dialog(self, _("Save catalog preset"), + _("That saved preset already exists and will be overwritten. " + "Are you sure?")): + return + new = False + + preset = {} + prefix_rules_processed = False + exclusion_rules_processed = False + + for opt in self.OPTION_FIELDS: + c_name, c_def, c_type = opt + if c_name == 'exclusion_rules_tw' and exclusion_rules_processed: + continue + if c_name == 'prefix_rules_tw' and prefix_rules_processed: + continue + + if c_type in ['check_box', 'radio_button']: + opt_value = getattr(self, c_name).isChecked() + elif c_type in ['combo_box']: + if c_name == 'preset_field': + continue + opt_value = unicode(getattr(self,c_name).currentText()).strip() + elif c_type in ['line_edit']: + opt_value = unicode(getattr(self, c_name).text()).strip() + elif c_type in ['spin_box']: + opt_value = unicode(getattr(self, c_name).value()) + elif c_type in ['table_widget']: + if c_name == 'prefix_rules_tw': + opt_value = self.prefix_rules_table.get_data() + prefix_rules_processed = True + if c_name == 'exclusion_rules_tw': + opt_value = self.exclusion_rules_table.get_data() + exclusion_rules_processed = True + + preset[c_name] = opt_value + # Construct listified version of table rules for cli invocation + if c_name in ['exclusion_rules_tw','prefix_rules_tw']: + self.construct_tw_opts_object(c_name, opt_value, preset) + + format, title = self.get_format_and_title() + preset['format'] = format + preset['catalog_title'] = title + + # Additional items needed for cli invocation + # Generate specs for merge_comments, header_note_source_field, genre_source_field + checked = '' + if self.merge_before.isChecked(): + checked = 'before' + elif self.merge_after.isChecked(): + checked = 'after' + include_hr = self.include_hr.isChecked() + preset['merge_comments_rule'] = "%s:%s:%s" % \ + (self.merge_source_field_name, checked, include_hr) + + preset['header_note_source_field'] = self.header_note_source_field_name + + preset['genre_source_field'] = self.genre_source_field_name + + # Append the output profile + try: + preset['output_profile'] = load_defaults('page_setup')['output_profile'] + except: + preset['output_profile'] = 'default' + + self.presets[name] = preset + self.presets.commit() + + if new: + self.preset_field.blockSignals(True) + self.preset_field.clear() + self.preset_field.addItem('') + self.preset_field_values = sorted([q for q in self.presets], key=sort_key) + self.preset_field.addItems(self.preset_field_values) + self.preset_field.blockSignals(False) + self.preset_field.setCurrentIndex(self.preset_field.findText(name)) + + def set_format_and_title(self, format, title): + for peer in self.parentWidget().children(): + if peer == self: + continue + elif peer.children(): + for child in peer.children(): + if child.objectName() == 'format': + index = child.findText(format) + child.blockSignals(True) + child.setCurrentIndex(index) + child.blockSignals(False) + elif child.objectName() == 'title': + child.setText(title) + def show_help(self): ''' Display help file @@ -631,6 +864,7 @@ class GenericRulesTable(QTableWidget): self.last_row_selected = self.currentRow() self.last_rows_selected = self.selectionModel().selectedRows() + # Add the controls self._init_controls() # Hook check_box changes @@ -681,6 +915,21 @@ class GenericRulesTable(QTableWidget): # In case table was empty self.horizontalHeader().setStretchLastSection(True) + def clearLayout(self): + if self.layout is not None: + old_layout = self.layout + + for child in old_layout.children(): + for i in reversed(range(child.count())): + if child.itemAt(i).widget() is not None: + child.itemAt(i).widget().setParent(None) + import sip + sip.delete(child) + + for i in reversed(range(old_layout.count())): + if old_layout.itemAt(i).widget() is not None: + old_layout.itemAt(i).widget().setParent(None) + def delete_row(self): if self.DEBUG: print("%s:delete_row()" % self.objectName()) diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.ui b/src/calibre/gui2/catalog/catalog_epub_mobi.ui index d212b0aa6f..608c5c81aa 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.ui +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.ui @@ -20,6 +20,54 @@ Form + + + + + 0 + 0 + + + + Presets + + + + + + + 0 + 0 + + + + Select catalog preset to load + + + + + + + Save current catalog settings as preset + + + Save + + + + + + + Delete current preset + + + Delete + + + + + + @@ -46,6 +94,9 @@ true + + List of books, sorted by Author + &Authors @@ -54,15 +105,21 @@ - + + + List of books, sorted by Title + &Titles - + + + List of series books, sorted by Series + &Series @@ -72,6 +129,9 @@ + + List of books, sorted by Genre + &Genres @@ -80,13 +140,13 @@ - Field containing Genre information + Field containing Genres - + @@ -96,6 +156,9 @@ 26 + + List of books, sorted by date added to calibre + &Recently Added @@ -103,7 +166,7 @@ - + @@ -113,6 +176,9 @@ 26 + + Individual descriptions of books with cover thumbs, sorted by author + &Descriptions @@ -120,6 +186,41 @@ + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + @@ -347,7 +448,7 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book] - Custom column containing additional content to be merged with Comments metadata. + Custom column containing additional content to be merged with Comments metadata in Descriptions section. @@ -361,7 +462,7 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book] - Merge additional content before Comments metadata. + Merge additional content before Comments in Descriptions section. &Before @@ -374,7 +475,7 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book] - Merge additional content after Comments metadata. + Merge additional content after Comments in Descriptions section. &After @@ -394,7 +495,7 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book] - Separate Comments metadata and additional content with a horizontal rule. + Separate Comments metadata and additional content with a horizontal rule in Descriptions section. Include &Separator @@ -514,7 +615,7 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book] - Custom column source for text to include in Description section. + Custom column source for text to include in Descriptions section. diff --git a/src/calibre/library/catalogs/epub_mobi.py b/src/calibre/library/catalogs/epub_mobi.py index 96290601cd..965394a44e 100644 --- a/src/calibre/library/catalogs/epub_mobi.py +++ b/src/calibre/library/catalogs/epub_mobi.py @@ -11,17 +11,18 @@ import os from collections import namedtuple from calibre import strftime +from calibre.constants import config_dir from calibre.customize import CatalogPlugin from calibre.customize.conversion import OptionRecommendation, DummyReporter from calibre.ebooks import calibre_cover from calibre.library import current_library_name from calibre.library.catalogs import AuthorSortMismatchException, EmptyCatalogException from calibre.ptempfile import PersistentTemporaryFile +from calibre.utils.config import JSONConfig from calibre.utils.localization import calibre_langcode_to_name, canonicalize_lang, get_lang Option = namedtuple('Option', 'option, default, dest, action, help') - class EPUB_MOBI(CatalogPlugin): 'ePub catalog generator' @@ -162,6 +163,14 @@ class EPUB_MOBI(CatalogPlugin): "When multiple rules are defined, the first matching rule will be used.\n" "Default:\n" + '"' + '%default' + '"' + "\n" "Applies to AZW3, ePub, MOBI output formats")), + Option('--preset', + default=None, + dest='preset', + action=None, + help=_("Use a named preset created with the GUI Catalog builder.\n" + "A preset specifies all settings for building a catalog.\n" + "Default: '%default'\n" + "Applies to AZW3, ePub, MOBI output formats")), Option('--use-existing-cover', default=False, dest='use_existing_cover', @@ -184,6 +193,43 @@ class EPUB_MOBI(CatalogPlugin): 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] @@ -329,15 +375,14 @@ class EPUB_MOBI(CatalogPlugin): log.error("incorrect number of args for --exclusion-rules: %s" % repr(rule)) # Display opts - keys = opts_dict.keys() - keys.sort() + 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', 'read_book_marker', + '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]))) From b0c481cba69cc4b0c0f1af030036c1f2b6cdc157 Mon Sep 17 00:00:00 2001 From: GRiker Date: Mon, 18 Mar 2013 06:15:02 -0700 Subject: [PATCH 06/18] Cleanup preset implementation --- src/calibre/gui2/catalog/catalog_epub_mobi.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py index 91c769cbf0..fa90bd85aa 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.py +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py @@ -591,7 +591,7 @@ class PluginWidget(QWidget,Ui_Form): c_name, c_def, c_type = opt if c_name == 'preset_field': continue - # Extra entries in options for cli invocation + # Ignore extra entries in options for cli invocation if c_name in options: opt_value = options[c_name] else: @@ -646,7 +646,6 @@ class PluginWidget(QWidget,Ui_Form): self.set_format_and_title(format, title) def preset_remove(self): - print("preset_remove()") if self.preset_field.currentIndex() == 0: return @@ -722,7 +721,7 @@ class PluginWidget(QWidget,Ui_Form): exclusion_rules_processed = True preset[c_name] = opt_value - # Construct listified version of table rules for cli invocation + # Construct cli version of table rules if c_name in ['exclusion_rules_tw','prefix_rules_tw']: self.construct_tw_opts_object(c_name, opt_value, preset) @@ -745,7 +744,7 @@ class PluginWidget(QWidget,Ui_Form): preset['genre_source_field'] = self.genre_source_field_name - # Append the output profile + # Append the current output profile try: preset['output_profile'] = load_defaults('page_setup')['output_profile'] except: From 79c8ede0a84a841dc1064ab0e46f68ac0e167228 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 20 Mar 2013 11:49:27 +0530 Subject: [PATCH 07/18] Fix bug in imghdr that caused some JPEG files to not be identified --- .../ebooks/conversion/plugins/html_input.py | 5 +- .../ebooks/conversion/plugins/rtf_input.py | 4 +- src/calibre/ebooks/metadata/mobi.py | 5 +- src/calibre/ebooks/mobi/debug/mobi8.py | 5 +- src/calibre/ebooks/mobi/reader/mobi8.py | 5 +- src/calibre/ebooks/mobi/utils.py | 7 +- src/calibre/ebooks/mobi/writer2/resources.py | 5 +- src/calibre/utils/imghdr.py | 156 ++++++++++++++++++ src/calibre/web/fetch/simple.py | 5 +- 9 files changed, 179 insertions(+), 18 deletions(-) create mode 100644 src/calibre/utils/imghdr.py diff --git a/src/calibre/ebooks/conversion/plugins/html_input.py b/src/calibre/ebooks/conversion/plugins/html_input.py index f00ccb9d9b..558b4636b4 100644 --- a/src/calibre/ebooks/conversion/plugins/html_input.py +++ b/src/calibre/ebooks/conversion/plugins/html_input.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import re, tempfile, os, imghdr +import re, tempfile, os from functools import partial from itertools import izip from urllib import quote @@ -17,6 +17,7 @@ from calibre.customize.conversion import (InputFormatPlugin, OptionRecommendation) from calibre.utils.localization import get_lang from calibre.utils.filenames import ascii_filename +from calibre.utils.imghdr import what class HTMLInput(InputFormatPlugin): @@ -250,7 +251,7 @@ class HTMLInput(InputFormatPlugin): if media_type == self.BINARY_MIME: # Check for the common case, images try: - img = imghdr.what(link) + img = what(link) except EnvironmentError: pass else: diff --git a/src/calibre/ebooks/conversion/plugins/rtf_input.py b/src/calibre/ebooks/conversion/plugins/rtf_input.py index 9249ea8d48..45d7f16608 100644 --- a/src/calibre/ebooks/conversion/plugins/rtf_input.py +++ b/src/calibre/ebooks/conversion/plugins/rtf_input.py @@ -105,7 +105,7 @@ class RTFInput(InputFormatPlugin): return f.read() def extract_images(self, picts): - import imghdr + from calibre.utils.imghdr import what self.log('Extracting images...') with open(picts, 'rb') as f: @@ -120,7 +120,7 @@ class RTFInput(InputFormatPlugin): if len(enc) % 2 == 1: enc = enc[:-1] data = enc.decode('hex') - fmt = imghdr.what(None, data) + fmt = what(None, data) if fmt is None: fmt = 'wmf' count += 1 diff --git a/src/calibre/ebooks/metadata/mobi.py b/src/calibre/ebooks/metadata/mobi.py index ab475f33a8..7ad9a01962 100644 --- a/src/calibre/ebooks/metadata/mobi.py +++ b/src/calibre/ebooks/metadata/mobi.py @@ -9,7 +9,7 @@ __copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net and ' \ 'Marshall T. Vandegrift ' __docformat__ = 'restructuredtext en' -import os, cStringIO, imghdr +import os, cStringIO from struct import pack, unpack from cStringIO import StringIO @@ -18,12 +18,13 @@ from calibre.ebooks.mobi import MobiError, MAX_THUMB_DIMEN from calibre.ebooks.mobi.utils import rescale_image from calibre.ebooks.mobi.langcodes import iana2mobi from calibre.utils.date import now as nowf +from calibre.utils.imghdr import what from calibre.utils.localization import canonicalize_lang, lang_as_iso639_1 def is_image(ss): if ss is None: return False - return imghdr.what(None, ss[:200]) is not None + return what(None, ss[:200]) is not None class StreamSlicer(object): diff --git a/src/calibre/ebooks/mobi/debug/mobi8.py b/src/calibre/ebooks/mobi/debug/mobi8.py index 213e15cf85..e1c8ffba44 100644 --- a/src/calibre/ebooks/mobi/debug/mobi8.py +++ b/src/calibre/ebooks/mobi/debug/mobi8.py @@ -8,7 +8,7 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import sys, os, imghdr, struct, textwrap +import sys, os, struct, textwrap from itertools import izip from calibre import CurrentDir @@ -18,6 +18,7 @@ from calibre.ebooks.mobi.debug.index import (SKELIndex, SECTIndex, NCXIndex, from calibre.ebooks.mobi.utils import read_font_record, decode_tbs, RECORD_SIZE from calibre.ebooks.mobi.debug import format_bytes from calibre.ebooks.mobi.reader.headers import NULL_INDEX +from calibre.utils.imghdr import what class FDST(object): @@ -173,7 +174,7 @@ class MOBIFile(object): font['raw_data']) prefix, ext = 'fonts', font['ext'] elif sig not in known_types: - q = imghdr.what(None, rec.raw) + q = what(None, rec.raw) if q: prefix, ext = 'images', q diff --git a/src/calibre/ebooks/mobi/reader/mobi8.py b/src/calibre/ebooks/mobi/reader/mobi8.py index 8938b103d3..a55f6bd7e3 100644 --- a/src/calibre/ebooks/mobi/reader/mobi8.py +++ b/src/calibre/ebooks/mobi/reader/mobi8.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import struct, re, os, imghdr +import struct, re, os from collections import namedtuple from itertools import repeat, izip from urlparse import urldefrag @@ -23,6 +23,7 @@ from calibre.ebooks.metadata.toc import TOC from calibre.ebooks.mobi.utils import read_font_record from calibre.ebooks.oeb.parse_utils import parse_html from calibre.ebooks.oeb.base import XPath, XHTML, xml2text +from calibre.utils.imghdr import what Part = namedtuple('Part', 'num type filename start end aid') @@ -403,7 +404,7 @@ class Mobi8Reader(object): if font['encrypted']: self.encrypted_fonts.append(href) else: - imgtype = imghdr.what(None, data) + imgtype = what(None, data) if imgtype is None: imgtype = 'unknown' href = 'images/%05d.%s'%(fname_idx, imgtype) diff --git a/src/calibre/ebooks/mobi/utils.py b/src/calibre/ebooks/mobi/utils.py index 09e3055a6e..e9bc4f669f 100644 --- a/src/calibre/ebooks/mobi/utils.py +++ b/src/calibre/ebooks/mobi/utils.py @@ -7,11 +7,12 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import struct, string, imghdr, zlib, os +import struct, string, zlib, os from collections import OrderedDict from io import BytesIO from calibre.utils.magick.draw import Image, save_cover_data_to, thumbnail +from calibre.utils.imghdr import what from calibre.ebooks import normalize IMAGE_MAX_SIZE = 10 * 1024 * 1024 @@ -384,9 +385,9 @@ def to_base(num, base=32, min_num_digits=None): def mobify_image(data): 'Convert PNG images to GIF as the idiotic Kindle cannot display some PNG' - what = imghdr.what(None, data) + fmt = what(None, data) - if what == 'png': + if fmt == 'png': im = Image() im.load(data) data = im.export('gif') diff --git a/src/calibre/ebooks/mobi/writer2/resources.py b/src/calibre/ebooks/mobi/writer2/resources.py index bdf20a6f2c..01ce6a0135 100644 --- a/src/calibre/ebooks/mobi/writer2/resources.py +++ b/src/calibre/ebooks/mobi/writer2/resources.py @@ -7,13 +7,12 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import imghdr - from calibre.ebooks.mobi import MAX_THUMB_DIMEN, MAX_THUMB_SIZE from calibre.ebooks.mobi.utils import (rescale_image, mobify_image, write_font_record) from calibre.ebooks import generate_masthead from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES +from calibre.utils.imghdr import what PLACEHOLDER_GIF = b'GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\xff\xff\xff!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00@\x02\x01D\x00;' @@ -84,7 +83,7 @@ class Resources(object): self.image_indices.add(len(self.records)) self.records.append(data) self.item_map[item.href] = index - self.mime_map[item.href] = 'image/%s'%imghdr.what(None, data) + self.mime_map[item.href] = 'image/%s'%what(None, data) index += 1 if cover_href and item.href == cover_href: diff --git a/src/calibre/utils/imghdr.py b/src/calibre/utils/imghdr.py new file mode 100644 index 0000000000..3bd515bac5 --- /dev/null +++ b/src/calibre/utils/imghdr.py @@ -0,0 +1,156 @@ +"""Recognize image file formats based on their first few bytes.""" + +__all__ = ["what"] + +#-------------------------# +# Recognize image headers # +#-------------------------# + +def what(file, h=None): + if h is None: + if isinstance(file, basestring): + f = open(file, 'rb') + h = f.read(32) + else: + location = file.tell() + h = file.read(32) + file.seek(location) + f = None + else: + f = None + try: + for tf in tests: + res = tf(h, f) + if res: + return res + finally: + if f: f.close() + return None + + +#---------------------------------# +# Subroutines per image file type # +#---------------------------------# + +tests = [] + +def test_jpeg(h, f): + """JPEG data in JFIF format (Changed by Kovid to mimic the file utility, + the original code was failing with some jpegs that included ICC_PROFILE + data, for example: http://nationalpostnews.files.wordpress.com/2013/03/budget.jpeg?w=300&h=1571)""" + if (h[6:10] in (b'JFIF', b'Exif')) or (h[:2] == b'\xff\xd8' and b'JFIF' in h[:32]): + return 'jpeg' + +tests.append(test_jpeg) + +def test_png(h, f): + if h[:8] == "\211PNG\r\n\032\n": + return 'png' + +tests.append(test_png) + +def test_gif(h, f): + """GIF ('87 and '89 variants)""" + if h[:6] in ('GIF87a', 'GIF89a'): + return 'gif' + +tests.append(test_gif) + +def test_tiff(h, f): + """TIFF (can be in Motorola or Intel byte order)""" + if h[:2] in ('MM', 'II'): + return 'tiff' + +tests.append(test_tiff) + +def test_rgb(h, f): + """SGI image library""" + if h[:2] == '\001\332': + return 'rgb' + +tests.append(test_rgb) + +def test_pbm(h, f): + """PBM (portable bitmap)""" + if len(h) >= 3 and \ + h[0] == 'P' and h[1] in '14' and h[2] in ' \t\n\r': + return 'pbm' + +tests.append(test_pbm) + +def test_pgm(h, f): + """PGM (portable graymap)""" + if len(h) >= 3 and \ + h[0] == 'P' and h[1] in '25' and h[2] in ' \t\n\r': + return 'pgm' + +tests.append(test_pgm) + +def test_ppm(h, f): + """PPM (portable pixmap)""" + if len(h) >= 3 and \ + h[0] == 'P' and h[1] in '36' and h[2] in ' \t\n\r': + return 'ppm' + +tests.append(test_ppm) + +def test_rast(h, f): + """Sun raster file""" + if h[:4] == '\x59\xA6\x6A\x95': + return 'rast' + +tests.append(test_rast) + +def test_xbm(h, f): + """X bitmap (X10 or X11)""" + s = '#define ' + if h[:len(s)] == s: + return 'xbm' + +tests.append(test_xbm) + +def test_bmp(h, f): + if h[:2] == 'BM': + return 'bmp' + +tests.append(test_bmp) + +#--------------------# +# Small test program # +#--------------------# + +def test(): + import sys + recursive = 0 + if sys.argv[1:] and sys.argv[1] == '-r': + del sys.argv[1:2] + recursive = 1 + try: + if sys.argv[1:]: + testall(sys.argv[1:], recursive, 1) + else: + testall(['.'], recursive, 1) + except KeyboardInterrupt: + sys.stderr.write('\n[Interrupted]\n') + sys.exit(1) + +def testall(list, recursive, toplevel): + import sys + import os + for filename in list: + if os.path.isdir(filename): + print filename + '/:', + if recursive or toplevel: + print 'recursing down:' + import glob + names = glob.glob(os.path.join(filename, '*')) + testall(names, recursive, 0) + else: + print '*** directory (use -r) ***' + else: + print filename + ':', + sys.stdout.flush() + try: + print what(filename) + except IOError: + print '*** not found ***' diff --git a/src/calibre/web/fetch/simple.py b/src/calibre/web/fetch/simple.py index 7cc8bd9309..95b8cf0253 100644 --- a/src/calibre/web/fetch/simple.py +++ b/src/calibre/web/fetch/simple.py @@ -7,7 +7,7 @@ __copyright__ = '2008, Kovid Goyal ' Fetch a webpage and its links recursively. The webpages are saved to disk in UTF-8 encoding with any charset declarations removed. ''' -import sys, socket, os, urlparse, re, time, copy, urllib2, threading, traceback, imghdr +import sys, socket, os, urlparse, re, time, copy, urllib2, threading, traceback from urllib import url2pathname, quote from httplib import responses from base64 import b64decode @@ -21,6 +21,7 @@ from calibre.utils.config import OptionParser from calibre.utils.logging import Log from calibre.utils.magick import Image from calibre.utils.magick.draw import identify_data, thumbnail +from calibre.utils.imghdr import what class FetchError(Exception): pass @@ -413,7 +414,7 @@ class RecursiveFetcher(object): fname = ascii_filename('img'+str(c)) if isinstance(fname, unicode): fname = fname.encode('ascii', 'replace') - itype = imghdr.what(None, data) + itype = what(None, data) if itype is None and b' Date: Wed, 20 Mar 2013 13:20:50 +0530 Subject: [PATCH 08/18] Linux: Update bundled libmtp version --- src/calibre/devices/mtp/unix/devices.c | 12 - .../devices/mtp/unix/upstream/music-players.h | 444 +++++++++++++----- .../devices/mtp/unix/upstream/update.py | 16 +- 3 files changed, 348 insertions(+), 124 deletions(-) diff --git a/src/calibre/devices/mtp/unix/devices.c b/src/calibre/devices/mtp/unix/devices.c index b6d50bac5b..2efe02c38f 100644 --- a/src/calibre/devices/mtp/unix/devices.c +++ b/src/calibre/devices/mtp/unix/devices.c @@ -11,18 +11,6 @@ const calibre_device_entry_t calibre_mtp_device_table[] = { #include "upstream/music-players.h" - // Amazon Kindle Fire HD - , { "Amazon", 0x1949, "Fire HD", 0x0007, DEVICE_FLAGS_ANDROID_BUGS} - , { "Amazon", 0x1949, "Fire HD", 0x0008, DEVICE_FLAGS_ANDROID_BUGS} - , { "Amazon", 0x1949, "Fire HD", 0x000a, DEVICE_FLAGS_ANDROID_BUGS} - - // Nexus 10 - , { "Google", 0x18d1, "Nexus 10", 0x4ee2, DEVICE_FLAGS_ANDROID_BUGS} - , { "Google", 0x18d1, "Nexus 10", 0x4ee1, DEVICE_FLAGS_ANDROID_BUGS} - - // Kobo Arc - , { "Kobo", 0x2237, "Arc", 0xd108, DEVICE_FLAGS_ANDROID_BUGS} - , { NULL, 0xffff, NULL, 0xffff, DEVICE_FLAG_NONE } }; diff --git a/src/calibre/devices/mtp/unix/upstream/music-players.h b/src/calibre/devices/mtp/unix/upstream/music-players.h index 33c7b1f926..c7a9b80bce 100644 --- a/src/calibre/devices/mtp/unix/upstream/music-players.h +++ b/src/calibre/devices/mtp/unix/upstream/music-players.h @@ -294,6 +294,13 @@ DEVICE_FLAG_UNIQUE_FILENAMES | DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST }, // The "YP-R2" (0x04e8/0x512d) is NOT MTP, it is UMS only. + // Guessing on device flags for the MTP mode... + { "Samsung", 0x04e8, "YP-R2", 0x512e, + DEVICE_FLAG_UNLOAD_DRIVER | + DEVICE_FLAG_OGG_IS_UNKNOWN | + DEVICE_FLAG_UNIQUE_FILENAMES | + DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | + DEVICE_FLAG_PLAYLIST_SPL_V1 }, // From Manuel Carro // Copied from Q2 { "Samsung", 0x04e8, "YP-Q3", 0x5130, @@ -309,6 +316,7 @@ DEVICE_FLAG_OGG_IS_UNKNOWN | DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | DEVICE_FLAG_PLAYLIST_SPL_V1 }, + // YP-F3 is NOT MTP - USB mass storage // From a rouge .INF file // this device ID seems to have been recycled for: // the Samsung SGH-A707 Cingular cellphone @@ -393,7 +401,9 @@ DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | DEVICE_FLAG_UNLOAD_DRIVER | DEVICE_FLAG_LONG_TIMEOUT | - DEVICE_FLAG_PROPLIST_OVERRIDES_OI }, + DEVICE_FLAG_PROPLIST_OVERRIDES_OI | + DEVICE_FLAG_OGG_IS_UNKNOWN | + DEVICE_FLAG_FLAC_IS_UNKNOWN }, // Reported by David Goodenough // Guessing on flags. { "Samsung", 0x04e8, "Galaxy Y", 0x685e, @@ -401,14 +411,18 @@ DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | DEVICE_FLAG_UNLOAD_DRIVER | DEVICE_FLAG_LONG_TIMEOUT | - DEVICE_FLAG_PROPLIST_OVERRIDES_OI }, + DEVICE_FLAG_PROPLIST_OVERRIDES_OI | + DEVICE_FLAG_OGG_IS_UNKNOWN | + DEVICE_FLAG_FLAC_IS_UNKNOWN }, { "Samsung", 0x04e8, "Galaxy models (MTP)", 0x6860, DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST_ALL | DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | DEVICE_FLAG_UNLOAD_DRIVER | DEVICE_FLAG_LONG_TIMEOUT | - DEVICE_FLAG_PROPLIST_OVERRIDES_OI }, + DEVICE_FLAG_PROPLIST_OVERRIDES_OI | + DEVICE_FLAG_OGG_IS_UNKNOWN | + DEVICE_FLAG_FLAC_IS_UNKNOWN }, // From: Erik Berglund // Logs indicate this needs DEVICE_FLAG_NO_ZERO_READS // No Samsung platlists on this device. @@ -419,7 +433,9 @@ { "Samsung", 0x04e8, "Galaxy models Kies mode", 0x6877, DEVICE_FLAG_UNLOAD_DRIVER | DEVICE_FLAG_LONG_TIMEOUT | - DEVICE_FLAG_PROPLIST_OVERRIDES_OI }, + DEVICE_FLAG_PROPLIST_OVERRIDES_OI | + DEVICE_FLAG_OGG_IS_UNKNOWN | + DEVICE_FLAG_FLAC_IS_UNKNOWN }, // From: John Gorkos and // Akos Maroy { "Samsung", 0x04e8, "Vibrant SGH-T959/Captivate/Media player mode", 0x68a9, @@ -439,7 +455,6 @@ */ { "Microsoft/Intel", 0x045e, "Bandon Portable Media Center", 0x00c9, DEVICE_FLAG_NONE }, - // Reported by anonymous sourceforge user // HTC Mozart is using the PID, as is Nokia Lumia 800 // May need MTPZ to work { "Microsoft", 0x045e, "Windows Phone", 0x04ec, DEVICE_FLAG_NONE }, @@ -450,12 +465,12 @@ { "Microsoft", 0x045e, "Windows MTP Simulator", 0x0622, DEVICE_FLAG_NONE }, // Reported by Edward Hutchins (used for Zune HDs) { "Microsoft", 0x045e, "Zune HD", 0x063e, DEVICE_FLAG_NONE }, - // Reported by anonymous sourceforge user { "Microsoft", 0x045e, "Kin 1", 0x0640, DEVICE_FLAG_NONE }, - // Reported by anonymous sourceforge user { "Microsoft/Sharp/nVidia", 0x045e, "Kin TwoM", 0x0641, DEVICE_FLAG_NONE }, // Reported by Farooq Zaman (used for all Zunes) { "Microsoft", 0x045e, "Zune", 0x0710, DEVICE_FLAG_NONE }, + // Reported by Olegs Jeremejevs + { "Microsoft/HTC", 0x045e, "HTC 8S", 0xf0ca, DEVICE_FLAG_NONE }, /* * JVC @@ -517,33 +532,52 @@ // From Anonymous SourceForge User { "Philips", 0x0471, "GoGear Vibe/02", 0x20e5, DEVICE_FLAG_UNLOAD_DRIVER }, + // Reported by Philip Rhoades + { "Philips", 0x0471, "GoGear Ariaz/97", 0x2138, + DEVICE_FLAG_UNLOAD_DRIVER }, // from XNJB user { "Philips", 0x0471, "PSA235", 0x7e01, DEVICE_FLAG_NONE }, /* * Acer + * Reporters: + * Franck VDL + * Matthias Arndt + * Arvin Schnell + * Philippe Marzouk + * nE0sIghT + * Maxime de Roucy */ - // Reported by anonymous sourceforge user - { "Acer", 0x0502, "Iconia TAB A500 (ID1)", 0x3325, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by: Franck VDL - { "Acer", 0x0502, "Iconia TAB A500 (ID2)", 0x3341, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by: Matthias Arndt - { "Acer", 0x0502, "Iconia TAB A501", 0x3344, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by: anonymous sourceforge user - { "Acer", 0x0502, "Iconia TAB A100 (ID1)", 0x3348, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by: Arvin Schnell - { "Acer", 0x0502, "Iconia TAB A100 (ID2)", 0x3349, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by Philippe Marzouk - { "Acer", 0x0502, "Iconia TAB A700", 0x3378, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by anonymous sourceforge user - { "Acer", 0x0502, "Iconia TAB A200 (ID1)", 0x337c, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by anonymous sourceforge user - { "Acer", 0x0502, "Iconia TAB A200 (ID2)", 0x337d, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by nE0sIghT - { "Acer", 0x0502, "Iconia TAB A510", 0x338a, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by Maxime de Roucy + { "Acer", 0x0502, "Iconia TAB A500 (ID1)", 0x3325, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Acer", 0x0502, "Iconia TAB A500 (ID2)", 0x3341, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Acer", 0x0502, "Iconia TAB A501 (ID1)", 0x3344, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Acer", 0x0502, "Iconia TAB A501 (ID2)", 0x3345, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Acer", 0x0502, "Iconia TAB A100 (ID1)", 0x3348, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Acer", 0x0502, "Iconia TAB A100 (ID2)", 0x3349, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Acer", 0x0502, "Iconia TAB A700", 0x3378, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Acer", 0x0502, "Iconia TAB A200 (ID1)", 0x337c, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Acer", 0x0502, "Iconia TAB A200 (ID2)", 0x337d, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Acer", 0x0502, "Iconia TAB A510 (ID1)", 0x3389, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Acer", 0x0502, "Iconia TAB A510 (ID2)", 0x338a, + DEVICE_FLAGS_ANDROID_BUGS }, { "Acer", 0x0502, "E350 Liquid Gallant Duo", 0x33c3, DEVICE_FLAGS_ANDROID_BUGS }, + { "Acer", 0x0502, "Iconia TAB A210", 0x33cb, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Acer", 0x0502, "Iconia TAB A110", 0x33d8, + DEVICE_FLAGS_ANDROID_BUGS }, + + /* * SanDisk @@ -743,7 +777,11 @@ { "iRiver", 0x4102, "E50", 0x1151, DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | DEVICE_FLAG_NO_ZERO_READS | DEVICE_FLAG_OGG_IS_UNKNOWN }, - // Reported by Jakub Matraszek + // Reported by anonymous SourceForge user, guessing on flags + { "iRiver", 0x4102, "E150", 0x1152, + DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | DEVICE_FLAG_NO_ZERO_READS | + DEVICE_FLAG_OGG_IS_UNKNOWN }, + // Reported by Jakub Matraszek { "iRiver", 0x4102, "T5", 0x1153, DEVICE_FLAG_UNLOAD_DRIVER | DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | DEVICE_FLAG_NO_ZERO_READS | DEVICE_FLAG_OGG_IS_UNKNOWN }, @@ -877,8 +915,10 @@ { "Archos", 0x0e79, "SPOD (MTP mode)", 0x1341, DEVICE_FLAG_UNLOAD_DRIVER }, { "Archos", 0x0e79, "5S IT (MTP mode)", 0x1351, DEVICE_FLAG_UNLOAD_DRIVER }, { "Archos", 0x0e79, "5H IT (MTP mode)", 0x1357, DEVICE_FLAG_UNLOAD_DRIVER }, - { "Archos", 0x0e79, "Arnova Childpad", 0x1458, DEVICE_FLAG_UNLOAD_DRIVER }, - // Reported by anonymous Sourceforge user + { "Archos", 0x0e79, "Arnova Childpad", 0x1458, DEVICE_FLAGS_ANDROID_BUGS }, + { "Archos", 0x0e79, "Arnova 8c G3", 0x145e, DEVICE_FLAGS_ANDROID_BUGS }, + { "Archos", 0x0e79, "Arnova 10bG3 Tablet", 0x146b, DEVICE_FLAGS_ANDROID_BUGS }, + { "Archos", 0x0e79, "97 Xenon", 0x149a, DEVICE_FLAGS_ANDROID_BUGS }, { "Archos", 0x0e79, "8o G9 (MTP mode)", 0x1508, DEVICE_FLAG_UNLOAD_DRIVER }, // Reported by Clément { "Archos", 0x0e79, "8o G9 Turbo (MTP mode)", 0x1509, @@ -887,14 +927,10 @@ { "Archos", 0x0e79, "80G9", 0x1518, DEVICE_FLAGS_ANDROID_BUGS }, // Reported by Till { "Archos", 0x0e79, "101 G9", 0x1528, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by anonymous sourceforge user { "Archos", 0x0e79, "101 G9 (v2)", 0x1529, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by anonymous sourceforge user { "Archos", 0x0e79, "101 G9 Turbo 250 HD", 0x1538, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by anonymous sourceforge user { "Archos", 0x0e79, "101 G9 Turbo", 0x1539, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by anonymous sourceforge user { "Archos", 0x0e79, "70it2 (mode 1)", 0x1568, DEVICE_FLAGS_ANDROID_BUGS }, // Reported by Sebastien ROHAUT { "Archos", 0x0e79, "70it2 (mode 2)", 0x1569, DEVICE_FLAGS_ANDROID_BUGS }, @@ -1075,6 +1111,10 @@ DEVICE_FLAG_UNLOAD_DRIVER }, // From: Maxin B. John { "Nokia", 0x0421, "N9", 0x051a, DEVICE_FLAG_NONE }, + { "Nokia", 0x0421, "C5-00", 0x0592, DEVICE_FLAG_NONE }, + // Reported by Sampo Savola + // Covers Lumia 920, 820 and probably any WP8 device. + { "Nokia", 0x0421, "Nokia Lumia WP8", 0x0661, DEVICE_FLAG_NONE }, // Reported by Richard Wall { "Nokia", 0x05c6, "5530 Xpressmusic", 0x0229, DEVICE_FLAG_NONE }, // Reported by anonymous SourceForge user @@ -1109,9 +1149,12 @@ { "Thomson / RCA", 0x069b, "Lyra HC308A", 0x3035, DEVICE_FLAG_NONE }, /* - * NTT DoCoMo + * Fujitsu devices */ - { "FOMA", 0x04c5, "F903iX HIGH-SPEED", 0x1140, DEVICE_FLAG_NONE }, + { "Fujitsu, Ltd", 0x04c5, "F903iX HIGH-SPEED", 0x1140, DEVICE_FLAG_NONE }, + // Reported by Thomas Bretthauer + { "Fujitsu, Ltd", 0x04c5, "STYLISTIC M532", 0x133b, + DEVICE_FLAGS_ANDROID_BUGS }, /* * Palm device userland program named Pocket Tunes @@ -1254,7 +1297,6 @@ /* * LG Electronics */ - // From anonymous SourceForge user // Uncertain if this is really the MTP mode device ID... { "LG Electronics Inc.", 0x043e, "T54", 0x7040, DEVICE_FLAG_UNLOAD_DRIVER }, @@ -1271,20 +1313,20 @@ { "LG Electronics Inc.", 0x1004, "GR-500 Music Player", 0x611b, DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | DEVICE_FLAG_ALWAYS_PROBE_DESCRIPTOR }, - // Reported by anonymous sourceforge user { "LG Electronics Inc.", 0x1004, "KM900", 0x6132, DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | DEVICE_FLAG_UNLOAD_DRIVER }, - // Reported by anonymous sourceforge user { "LG Electronics Inc.", 0x1004, "LG8575", 0x619a, DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | DEVICE_FLAG_UNLOAD_DRIVER }, - // Reported by anonymous sourceforge user { "LG Electronics Inc.", 0x1004, "V909 G-Slate", 0x61f9, DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | DEVICE_FLAG_UNLOAD_DRIVER }, + { "LG Electronics Inc.", 0x1004, "LG2 Optimus", 0x6225, + DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | + DEVICE_FLAG_UNLOAD_DRIVER }, // Reported by Brian J. Murrell - { "LG Electronics Inc.", 0x1004, "LG-E617G/P700", 0x631c, + { "LG Electronics Inc.", 0x1004, "LG-E610/E612/E617G/E970/P700", 0x631c, DEVICE_FLAGS_ANDROID_BUGS }, /* @@ -1294,69 +1336,69 @@ * reported to see a pattern here. */ // Reported by Alessandro Radaelli - { "Sony", 0x054c, "Walkman NWZ-A815/NWZ-A818", 0x0325, + { "Sony", 0x054c, "NWZ-A815/NWZ-A818", 0x0325, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by anonymous Sourceforge user. - { "Sony", 0x054c, "Walkman NWZ-S516", 0x0326, + { "Sony", 0x054c, "NWZ-S516", 0x0326, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Endre Oma - { "Sony", 0x054c, "Walkman NWZ-S615F/NWZ-S616F/NWZ-S618F", 0x0327, + { "Sony", 0x054c, "NWZ-S615F/NWZ-S616F/NWZ-S618F", 0x0327, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Jean-Marc Bourguet - { "Sony", 0x054c, "Walkman NWZ-S716F", 0x035a, + { "Sony", 0x054c, "NWZ-S716F", 0x035a, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Anon SF User / Anthon van der Neut - { "Sony", 0x054c, "Walkman NWZ-A826/NWZ-A828/NWZ-A829", 0x035b, + { "Sony", 0x054c, "NWZ-A826/NWZ-A828/NWZ-A829", 0x035b, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Niek Klaverstijn - { "Sony", 0x054c, "Walkman NWZ-A726/NWZ-A728/NWZ-A768", 0x035c, + { "Sony", 0x054c, "NWZ-A726/NWZ-A728/NWZ-A768", 0x035c, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Mehdi AMINI - { "Sony", 0x054c, "Walkman NWZ-B135", 0x036e, + { "Sony", 0x054c, "NWZ-B135", 0x036e, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by - { "Sony", 0x054c, "Walkman NWZ-E436F", 0x0385, + { "Sony", 0x054c, "NWZ-E436F", 0x0385, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Michael Wilkinson - { "Sony", 0x054c, "Walkman NWZ-W202", 0x0388, + { "Sony", 0x054c, "NWZ-W202", 0x0388, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Ondrej Sury - { "Sony", 0x054c, "Walkman NWZ-S739F", 0x038c, + { "Sony", 0x054c, "NWZ-S739F", 0x038c, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Marco Filipe Nunes Soares Abrantes Pereira - { "Sony", 0x054c, "Walkman NWZ-S638F", 0x038e, + { "Sony", 0x054c, "NWZ-S638F", 0x038e, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Elliot - { "Sony", 0x054c, "Walkman NWZ-X1050B/NWZ-X1060B", + { "Sony", 0x054c, "NWZ-X1050B/NWZ-X1060B", 0x0397, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Silvio J. Gutierrez - { "Sony", 0x054c, "Walkman NWZ-X1051/NWZ-X1061", 0x0398, + { "Sony", 0x054c, "NWZ-X1051/NWZ-X1061", 0x0398, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Gregory Boddin - { "Sony", 0x054c, "Walkman NWZ-B142F", 0x03d8, + { "Sony", 0x054c, "NWZ-B142F", 0x03d8, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Rick Warner - { "Sony", 0x054c, "Walkman NWZ-E344", 0x03fc, + { "Sony", 0x054c, "NWZ-E344/E345", 0x03fc, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Jonathan Stowe - { "Sony", 0x054c, "Walkman NWZ-E445", 0x03fd, + { "Sony", 0x054c, "NWZ-E445", 0x03fd, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Anonymous SourceForge user - { "Sony", 0x054c, "Walkman NWZ-S545", 0x03fe, + { "Sony", 0x054c, "NWZ-S545", 0x03fe, DEVICE_FLAGS_SONY_NWZ_BUGS }, - { "Sony", 0x054c, "Walkman NWZ-A845", 0x0404, + { "Sony", 0x054c, "NWZ-A845", 0x0404, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by anonymous SourceForge user - { "Sony", 0x054c, "Walkman NWZ-W252B", 0x04bb, + { "Sony", 0x054c, "NWZ-W252B", 0x04bb, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Suspect this device has strong DRM features // See https://answers.launchpad.net/ubuntu/+source/libmtp/+question/149587 - { "Sony", 0x054c, "Walkman NWZ-B153F", 0x04be, + { "Sony", 0x054c, "NWZ-B153F", 0x04be, DEVICE_FLAGS_SONY_NWZ_BUGS }, - { "Sony", 0x054c, "Walkman NWZ-E354", 0x04cb, + { "Sony", 0x054c, "NWZ-E354", 0x04cb, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Toni Burgarello - { "Sony", 0x054c, "Walkman NWZ-S754", 0x04cc, + { "Sony", 0x054c, "NWZ-S754", 0x04cc, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Hideki Yamane { "Sony", 0x054c, "Sony Tablet P1", 0x04d1, @@ -1364,9 +1406,7 @@ // Reported by dmiceman { "Sony", 0x054c, "NWZ-B163F", 0x059a, DEVICE_FLAGS_SONY_NWZ_BUGS }, - // Reported by anonymous Sourceforge user - // guessing on device flags... - { "Sony", 0x054c, "Walkman NWZ-E464", 0x05a6, + { "Sony", 0x054c, "NWZ-E464", 0x05a6, DEVICE_FLAGS_SONY_NWZ_BUGS }, // Reported by Jan Rheinlaender { "Sony", 0x054c, "NWZ-S765", 0x05a8, @@ -1377,7 +1417,8 @@ // Reported by ghalambaz { "Sony", 0x054c, "Sony Tablet S1", 0x05b4, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by Anonymous SourceForge user + { "Sony", 0x054c, "NWZ-B173F", 0x0689, + DEVICE_FLAGS_SONY_NWZ_BUGS }, { "Sony", 0x054c, "DCR-SR75", 0x1294, DEVICE_FLAGS_SONY_NWZ_BUGS }, @@ -1496,6 +1537,7 @@ * Jean-François B. * Eduard Bloch * Ah Hong + * Eowyn Carter */ { "SonyEricsson", 0x0fce, "LT15i Xperia arc S MTP", 0x014f, DEVICE_FLAG_NONE }, @@ -1503,6 +1545,8 @@ DEVICE_FLAG_NONE }, { "SonyEricsson", 0x0fce, "MK16i Xperia MTP", 0x015a, DEVICE_FLAG_NONE }, + { "SonyEricsson", 0x0fce, "R800/R88i Xperia Play MTP", 0x015d, + DEVICE_FLAG_NONE }, { "SonyEricsson", 0x0fce, "ST18a Xperia Ray MTP", 0x0161, DEVICE_FLAG_NONE }, { "SonyEricsson", 0x0fce, "SK17i Xperia Mini Pro MTP", 0x0166, @@ -1533,12 +1577,26 @@ DEVICE_FLAG_NONE }, { "SONY", 0x0fce, "ST27i/ST27a Xperia go MTP", 0x017e, DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "ST23i Xperia Miro MTP", 0x0180, + DEVICE_FLAG_NONE }, { "SONY", 0x0fce, "SO-05D Xperia SX MTP", 0x0181, DEVICE_FLAG_NONE }, { "SONY", 0x0fce, "LT30p Xperia T MTP", 0x0182, DEVICE_FLAG_NONE }, { "SONY", 0x0fce, "LT25i Xperia V MTP", 0x0186, DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia J MTP", 0x0188, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia ZL MTP", 0x0189, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia E MTP", 0x018c, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia Tablet Z MTP", 0x018D, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia Z MTP", 0x0193, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia Tablet Z MTP", 0x0194, + DEVICE_FLAG_NONE }, /* * MTP+UMS personalities of MTP devices (see above) @@ -1565,12 +1623,26 @@ DEVICE_FLAG_NONE }, { "SONY", 0x0fce, "ST27i/ST27a Xperia go MTP+CDROM", 0x417e, DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "ST23i Xperia Miro MTP+CDROM", 0x4180, + DEVICE_FLAG_NONE }, { "SONY", 0x0fce, "SO-05D Xperia SX MTP+CDROM", 0x4181, DEVICE_FLAG_NONE }, { "SONY", 0x0fce, "LT30p Xperia T MTP+CDROM", 0x4182, DEVICE_FLAG_NONE }, { "SONY", 0x0fce, "LT25i Xperia V MTP+CDROM", 0x4186, DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia J MTP+CDROM", 0x4188, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia ZL MTP", 0x4189, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia E MTP+CDROM", 0x418c, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia Tablet Z MTP", 0x418d, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia Z MTP", 0x4193, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia Tablet Z MTP", 0x4194, + DEVICE_FLAG_NONE }, /* * MTP+ADB personalities of MTP devices (see above) @@ -1579,20 +1651,20 @@ DEVICE_FLAG_NONE }, { "SonyEricsson", 0x0fce, "MT11i Xperia Neo MTP+ADB", 0x5156, DEVICE_FLAG_NONE }, - { "SonyEricsson", 0x0fce, "ST17i Xperia Active MTP+ADB", 0x5168, - DEVICE_FLAG_NONE }, - { "SONY", 0x0fce, "LT26i Xperia S MTP+ADB", 0x5169, - DEVICE_FLAG_NO_ZERO_READS }, { "SonyEricsson", 0x0fce, "MK16i Xperia MTP+ADB", 0x515a, DEVICE_FLAG_NONE }, + { "SonyEricsson", 0x0fce, "R800/R88i Xperia Play MTP+ADB", 0x515d, + DEVICE_FLAG_NONE }, { "SonyEricsson", 0x0fce, "ST18i Xperia Ray MTP+ADB", 0x5161, DEVICE_FLAG_NONE }, { "SonyEricsson", 0x0fce, "SK17i Xperia Mini Pro MTP+ADB", 0x5166, DEVICE_FLAG_NONE }, { "SonyEricsson", 0x0fce, "ST15i Xperia Mini MTP+ADB", 0x5167, DEVICE_FLAG_NONE }, - { "SonyEricsson", 0x0fce, "LT26i Xperia S MTP+ADB", 0x5169, + { "SonyEricsson", 0x0fce, "ST17i Xperia Active MTP+ADB", 0x5168, DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "LT26i Xperia S MTP+ADB", 0x5169, + DEVICE_FLAG_NO_ZERO_READS }, { "SonyEricsson", 0x0fce, "SK17i Xperia Mini Pro MTP+ADB", 0x516d, DEVICE_FLAG_NONE }, { "SONY", 0x0fce, "ST21i Xperia Tipo MTP+ADB", 0x5170, @@ -1613,12 +1685,26 @@ DEVICE_FLAG_NONE }, { "SONY", 0x0fce, "ST27i/ST27a Xperia go MTP+ADB", 0x517e, DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "ST23i Xperia Miro MTP+ADB", 0x5180, + DEVICE_FLAG_NONE }, { "SONY", 0x0fce, "SO-05D Xperia SX MTP+ADB", 0x5181, DEVICE_FLAG_NONE }, { "SONY", 0x0fce, "LT30p Xperia T MTP+ADB", 0x5182, DEVICE_FLAG_NONE }, { "SONY", 0x0fce, "LT25i Xperia V MTP+ADB", 0x5186, DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia J MTP+ADB", 0x5188, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia ZL MTP", 0x5189, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia E MTP+ADB", 0x518c, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia Tablet Z MTP", 0x518d, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia Z MTP", 0x5193, + DEVICE_FLAG_NONE }, + { "SONY", 0x0fce, "Xperia Tablet Z MTP", 0x5194, + DEVICE_FLAG_NONE }, /* * MTP+UMS modes @@ -1661,17 +1747,23 @@ { "Motorola", 0x22b8, "V3m/V750 verizon", 0x2a65, DEVICE_FLAG_BROKEN_SET_OBJECT_PROPLIST | DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST_ALL }, + { "Motorola", 0x22b8, "Atrix/Razr HD (MTP)", 0x2e32, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Motorola", 0x22b8, "Atrix/Razr HD (MTP+ADB)", 0x2e33, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Motorola", 0x22b8, "RAZR M (XT907)", 0x2e51, + DEVICE_FLAGS_ANDROID_BUGS }, // Reported by Jader Rodrigues Simoes { "Motorola", 0x22b8, "Xoom 2 Media Edition (ID2)", 0x41cf, DEVICE_FLAGS_ANDROID_BUGS }, // Reported by Steven Roemen { "Motorola", 0x22b8, "Droid X/MB525 (Defy)", 0x41d6, DEVICE_FLAG_NONE }, - // Reported by anonymous user + { "Motorola", 0x22b8, "DROID2 (ID1)", 0x41da, + DEVICE_FLAG_NONE }, { "Motorola", 0x22b8, "Milestone / Verizon Droid", 0x41dc, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by anonymous user - { "Motorola", 0x22b8, "DROID2", 0x42a7, + { "Motorola", 0x22b8, "DROID2 (ID2)", 0x42a7, DEVICE_FLAGS_ANDROID_BUGS }, { "Motorola", 0x22b8, "Xoom 2 Media Edition", 0x4311, DEVICE_FLAGS_ANDROID_BUGS }, @@ -1693,6 +1785,9 @@ // Reported by anonymous user { "Motorola", 0x22b8, "RAZR2 V8/U9/Z6", 0x6415, DEVICE_FLAG_BROKEN_SET_OBJECT_PROPLIST }, + // Reported by Brian Dolbec + { "Motorola", 0x22b8, "Atrix MB860 (MTP)", 0x7088, + DEVICE_FLAGS_ANDROID_BUGS }, /* * Motorola Xoom (Wingray) variants * @@ -1719,9 +1814,15 @@ DEVICE_FLAGS_ANDROID_BUGS }, { "Motorola", 0x22b8, "Xoom (MTP+ADB)", 0x70a9, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by anonymous Sourceforge user // "carried by C Spire and other CDMA US carriers" - { "Motorola", 0x22b8, "Milestone X2", 0x70ca, DEVICE_FLAGS_ANDROID_BUGS }, + { "Motorola", 0x22b8, "Milestone X2", 0x70ca, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Motorola", 0x22b8, "XT890/907 (MTP)", 0x710d, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Motorola", 0x22b8, "XT890/907 (MTP+ADB)", 0x710e, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Motorola", 0x22b8, "XT890/907 (MTP+?)", 0x710f, + DEVICE_FLAGS_ANDROID_BUGS }, /* * Google @@ -1729,23 +1830,32 @@ * road to produce an Android tablet it seems... The Vendor ID * was originally used for Nexus phones */ + { "Google Inc (for Ainol Novo)", 0x18d1, "Fire/Flame", 0x0007, + DEVICE_FLAGS_ANDROID_BUGS }, { "Google Inc (for Sony)", 0x18d1, "S1", 0x05b3, DEVICE_FLAGS_ANDROID_BUGS }, // Reported by anonymous Sourceforge user { "Google Inc (for Barnes & Noble)", 0x18d1, "Nook Color", 0x2d02, DEVICE_FLAGS_ANDROID_BUGS }, // Reported by anonymous Sourceforge user + { "Google Inc (for Asus)", 0x18d1, "TF201 Transformer", 0x4d00, + DEVICE_FLAGS_ANDROID_BUGS }, + // Reported by anonymous Sourceforge user { "Google Inc (for Asus)", 0x18d1, "TF101 Transformer", 0x4e0f, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by Laurent Artaud - { "Google Inc (for Samsung)", 0x18d1, "Nexus S", 0x4e21, - DEVICE_FLAGS_ANDROID_BUGS }, + // 0x4e21 (Nexus S) is a USB Mass Storage device. // Reported by Chris Smith { "Google Inc (for Asus)", 0x18d1, "Nexus 7 (MTP)", 0x4e41, DEVICE_FLAGS_ANDROID_BUGS }, // Reported by Michael Hess { "Google Inc (for Asus)", 0x18d1, "Nexus 7 (MTP+ADB)", 0x4e42, DEVICE_FLAGS_ANDROID_BUGS }, + { "Google Inc (for LG Electronics/Samsung)", 0x18d1, + "Nexus 4/10 (MTP)", 0x4ee1, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Google Inc (for LG Electronics/Samsung)", 0x18d1, + "Nexus 4/10 (MTP+ADB)", 0x4ee2, + DEVICE_FLAGS_ANDROID_BUGS }, // WiFi-only version of Xoom // See: http://bugzilla.gnome.org/show_bug.cgi?id=647506 { "Google Inc (for Motorola)", 0x18d1, "Xoom (MZ604)", 0x70a8, @@ -1845,9 +1955,16 @@ */ { "Coby", 0x1e74, "COBY MP705", 0x6512, DEVICE_FLAG_NONE }, +#if 0 /* * Apple devices, which are not MTP natively but can be made to speak MTP * using PwnTunes (http://www.pwntunes.net/) + * CURRENTLY COMMENTED OUT: + * These will make the UDEV rules flag these as MTP devices even if + * PwnTunes is NOT installed. That is unacceptable, so a better solution + * that actually inspects if the device has PwnTunes/MTP support needs + * to be found, see: + * https://sourceforge.net/p/libmtp/bugs/759/ */ { "Apple", 0x05ac, "iPhone", 0x1290, DEVICE_FLAG_NONE }, { "Apple", 0x05ac, "iPod Touch 1st Gen", 0x1291, DEVICE_FLAG_NONE }, @@ -1859,6 +1976,7 @@ { "Apple", 0x05ac, "0x1298", 0x1298, DEVICE_FLAG_NONE }, { "Apple", 0x05ac, "iPod Touch 3rd Gen", 0x1299, DEVICE_FLAG_NONE }, { "Apple", 0x05ac, "iPad", 0x129a, DEVICE_FLAG_NONE }, +#endif // Reported by anonymous SourceForge user, also reported as // Pantech Crux, claming to be: @@ -1872,30 +1990,48 @@ /* * Asus + * Pattern of PIDs on Android devices seem to be: + * n+0 = MTP + * n+1 = MTP+ADB + * n+2 = ? + * n+3 = ? + * n+4 = PTP */ // Reported by Glen Overby - { "Asus", 0x0b05, "TF300 Transformer", 0x4c80, + { "Asus", 0x0b05, "TF300 Transformer (MTP)", 0x4c80, DEVICE_FLAGS_ANDROID_BUGS }, // Reported by jaile - { "Asus", 0x0b05, "TF300 Transformer (USB debug mode)", 0x4c81, + { "Asus", 0x0b05, "TF300 Transformer (MTP+ADB)", 0x4c81, DEVICE_FLAGS_ANDROID_BUGS }, // Repored by Florian Apolloner - { "Asus", 0x0b05, "TF700 Transformer", 0x4c90, + { "Asus", 0x0b05, "TF700 Transformer (MTP)", 0x4c90, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Asus", 0x0b05, "TF700 Transformer (MTP+ADB)", 0x4c91, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Asus", 0x0b05, "MeMo Pad Smart 10", 0x4cd0, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by anonymous Sourceforge user { "Asus", 0x0b05, "TF201 Transformer Prime (keyboard dock)", 0x4d00, DEVICE_FLAGS_ANDROID_BUGS }, { "Asus", 0x0b05, "TF201 Transformer Prime (tablet only)", 0x4d01, DEVICE_FLAGS_ANDROID_BUGS }, - { "Asus", 0x0b05, "TFXXX Transformer Prime (unknown version)", 0x4d04, + // 4d04 is the PTP mode, don't add it + { "Asus", 0x0b05, "SL101 (MTP)", 0x4e00, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by anonymous Sourceforge user - { "Asus", 0x0b05, "TF101 Eeepad Slider", 0x4e01, + { "Asus", 0x0b05, "SL101 (MTP+ADB)", 0x4e01, DEVICE_FLAGS_ANDROID_BUGS }, - { "Asus", 0x0b05, "TF101 Eeepad Transformer", 0x4e0f, + { "Asus", 0x0b05, "TF101 Eeepad Transformer (MTP)", 0x4e0f, DEVICE_FLAGS_ANDROID_BUGS }, - { "Asus", 0x0b05, "TF101 Eeepad Transformer (debug mode)", 0x4e1f, + { "Asus", 0x0b05, "TF101 Eeepad Transformer (MTP+ADB)", 0x4e1f, DEVICE_FLAGS_ANDROID_BUGS }, + { "Asus", 0x0b05, "PadFone (MTP)", 0x5200, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Asus", 0x0b05, "PadFone (MTP+ADB)", 0x5201, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Asus", 0x0b05, "PadFone 2 (MTP+?)", 0x5210, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Asus", 0x0b05, "PadFone 2 (MTP)", 0x5211, + DEVICE_FLAGS_ANDROID_BUGS }, + /* @@ -1914,17 +2050,26 @@ // Reported by: anonymous sourceforge user { "Lenovo", 0x17ef, "Lifetab S9512", 0x74cc, DEVICE_FLAGS_ANDROID_BUGS }, + // Reported by Brian J. Murrell + { "Lenovo", 0x17ef, "IdeaTab A2109A", 0x7542, + DEVICE_FLAGS_ANDROID_BUGS }, /* * Huawei */ // Reported by anonymous SourceForge user - { "Huawei", 0x12d1, "Honor U8860", 0x1051, DEVICE_FLAGS_ANDROID_BUGS }, + { "Huawei", 0x12d1, "Honor U8860", 0x1051, + DEVICE_FLAGS_ANDROID_BUGS }, // Reported by anonymous SourceForge user - { "Huawei", 0x12d1, "Mediapad (mode 0)", 0x360f, DEVICE_FLAGS_ANDROID_BUGS }, + { "Huawei", 0x12d1, "U8815/U9200", 0x1052, + DEVICE_FLAGS_ANDROID_BUGS }, + // Reported by anonymous SourceForge user + { "Huawei", 0x12d1, "Mediapad (mode 0)", 0x360f, + DEVICE_FLAGS_ANDROID_BUGS }, // Reported by Bearsh - { "Huawei", 0x12d1, "Mediapad (mode 1)", 0x361f, DEVICE_FLAGS_ANDROID_BUGS }, + { "Huawei", 0x12d1, "Mediapad (mode 1)", 0x361f, + DEVICE_FLAGS_ANDROID_BUGS }, /* * ZTE @@ -1932,27 +2077,61 @@ */ { "ZTE", 0x19d2, "V55 ID 1", 0x0244, DEVICE_FLAGS_ANDROID_BUGS }, { "ZTE", 0x19d2, "V55 ID 2", 0x0245, DEVICE_FLAGS_ANDROID_BUGS }, + { "ZTE", 0x19d2, "v790/Blade 3", 0x0306, DEVICE_FLAGS_ANDROID_BUGS }, /* * HTC (High Tech Computer Corp) + * Reporters: + * Steven Eastland + * Kevin Cheng */ - { "HTC", 0x0bb4, "Zopo ZP100 (ID1)", 0x0c02, +#if 0 + /* + * This had to be commented out - the same VID+PID is used also for + * other modes than MTP, so we need to let mtp-probe do its job on this + * device instead of adding it to the database. + */ + { "HTC", 0x0bb4, "Android Device ID1 (Zopo, HD2, Bird...)", 0x0c02, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by Steven Eastland - { "HTC", 0x0bb4, "EVO 4G LTE", 0x0c93, +#endif + { "HTC", 0x0bb4, "EVO 4G LTE/One V (ID1)", 0x0c93, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by Steven Eastland - { "HTC", 0x0bb4, "EVO 4G LTE (second ID)", 0x0ca8, + { "HTC", 0x0bb4, "EVO 4G LTE/One V (ID2)", 0x0ca8, + DEVICE_FLAGS_ANDROID_BUGS }, + { "HTC", 0x0bb4, "HTC One S (ID1)", 0x0cec, + DEVICE_FLAGS_ANDROID_BUGS }, + { "HTC", 0x0bb4, "HTC Evo 4G LTE (ID1)", 0x0df5, + DEVICE_FLAGS_ANDROID_BUGS }, + { "HTC", 0x0bb4, "HTC One S (ID2)", 0x0df9, + DEVICE_FLAGS_ANDROID_BUGS }, + { "HTC", 0x0bb4, "HTC One X (ID1)", 0x0dfb, + DEVICE_FLAGS_ANDROID_BUGS }, + { "HTC", 0x0bb4, "HTC One X (ID2)", 0x0dfc, + DEVICE_FLAGS_ANDROID_BUGS }, + { "HTC", 0x0bb4, "HTC One X (ID3)", 0x0dfd, + DEVICE_FLAGS_ANDROID_BUGS }, + { "HTC", 0x0bb4, "HTC Butterfly (ID1)", 0x0dfe, + DEVICE_FLAGS_ANDROID_BUGS }, + { "HTC", 0x0bb4, "Droid DNA (MTP+UMS+ADB)", 0x0dff, + DEVICE_FLAGS_ANDROID_BUGS }, + { "HTC", 0x0bb4, "HTC Droid Incredible 4G LTE (MTP)", 0x0e31, + DEVICE_FLAGS_ANDROID_BUGS }, + { "HTC", 0x0bb4, "HTC Droid Incredible 4G LTE (MTP+ADB)", 0x0e32, + DEVICE_FLAGS_ANDROID_BUGS }, + { "HTC", 0x0bb4, "Droid DNA (MTP+UMS)", 0x0ebd, + DEVICE_FLAGS_ANDROID_BUGS }, + { "HTC", 0x0bb4, "HTC One X (ID2)", 0x0f91, DEVICE_FLAGS_ANDROID_BUGS }, // These identify themselves as "cm_tenderloin", fun... // Done by HTC for HP I guess. - { "Hewlett-Packard", 0x0bb4, "HP Touchpad", 0x685c, + { "Hewlett-Packard", 0x0bb4, "HP Touchpad (MTP)", 0x685c, DEVICE_FLAGS_ANDROID_BUGS }, - { "Hewlett-Packard", 0x0bb4, "HP Touchpad (debug mode)", - 0x6860, DEVICE_FLAGS_ANDROID_BUGS }, - // Reported by anonymous SourceForge user - { "HTC", 0x0bb4, "Zopo ZP100 (ID2)", 0x2008, + { "Hewlett-Packard", 0x0bb4, "HP Touchpad (MTP+ADB)", 0x6860, DEVICE_FLAGS_ANDROID_BUGS }, +#if 0 + { "HTC", 0x0bb4, "Android Device ID2 (Zopo, HD2...)", 0x2008, + DEVICE_FLAGS_ANDROID_BUGS }, +#endif /* * NEC @@ -1963,7 +2142,12 @@ * nVidia */ // Found on Internet forum - { "nVidia", 0x0955, "CM9-Adam", 0x70a9, DEVICE_FLAGS_ANDROID_BUGS }, + { "nVidia", 0x0955, "CM9-Adam", 0x70a9, + DEVICE_FLAGS_ANDROID_BUGS }, + { "nVidia", 0x0955, "Nabi2 Tablet (ID1)", 0x7100, + DEVICE_FLAGS_ANDROID_BUGS }, + { "nVidia", 0x0955, "Nabi2 Tablet (ID2)", 0x7102, + DEVICE_FLAGS_ANDROID_BUGS }, /* * Vizio @@ -1972,10 +2156,60 @@ { "Vizio", 0x0489, "VTAB1008", 0xe040, DEVICE_FLAGS_ANDROID_BUGS }, /* - * Viewpia, bq... - * Seems like some multi-branded OEM product. + * Amazon */ - { "Various", 0x2207, "Viewpia DR/bq Kepler", 0x0001, DEVICE_FLAGS_ANDROID_BUGS }, + { "Amazon", 0x1949, "Kindle Fire 2G (ID1)", 0x0005, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Amazon", 0x1949, "Kindle Fire (ID1)", 0x0007, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Amazon", 0x1949, "Kindle Fire (ID2)", 0x0008, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Amazon", 0x1949, "Kindle Fire (ID3)", 0x000a, + DEVICE_FLAGS_ANDROID_BUGS }, + + /* + * Barnes&Noble + */ + { "Barnes&Noble", 0x2080, "Nook HD+", 0x0005, + DEVICE_FLAGS_ANDROID_BUGS }, + + /* + * Viewpia, bq, YiFang + * Seems like some multi-branded OEM product line. + */ + { "Various", 0x2207, "Viewpia DR/bq Kepler", 0x0001, + DEVICE_FLAGS_ANDROID_BUGS }, + { "YiFang", 0x2207, "BQ Tesla", 0x0006, + DEVICE_FLAGS_ANDROID_BUGS }, + + /* + * Kobo + */ + // Reported by George Talusan + { "Kobo", 0x2237, "Arc (ID1)", 0xd108, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Kobo", 0x2237, "Arc (ID2)", 0xd109, + DEVICE_FLAGS_ANDROID_BUGS }, + + /* + * Hisense + */ + // Reported by Anonymous SourceForge user + { "Hisense", 0x109b, "E860", 0x9109, DEVICE_FLAGS_ANDROID_BUGS }, + + /* + * Intel + * Also sold rebranded as Orange products + */ + { "Intel", 0x8087, "Xolo 900/AZ210A", 0x09fb, DEVICE_FLAGS_ANDROID_BUGS }, + + /* + * Xiaomi + */ + { "Xiaomi", 0x2717, "Mi-2 (MTP+ADB)", 0x9039, + DEVICE_FLAGS_ANDROID_BUGS }, + { "Xiaomi", 0x2717, "Mi-2 (MTP)", 0xf003, + DEVICE_FLAGS_ANDROID_BUGS }, /* * Other strange stuff. diff --git a/src/calibre/devices/mtp/unix/upstream/update.py b/src/calibre/devices/mtp/unix/upstream/update.py index 20c03d072d..50ae2becd7 100644 --- a/src/calibre/devices/mtp/unix/upstream/update.py +++ b/src/calibre/devices/mtp/unix/upstream/update.py @@ -7,14 +7,16 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -MP = 'http://libmtp.git.sourceforge.net/git/gitweb.cgi?p=libmtp/libmtp;a=blob_plain;f=src/music-players.h;hb=HEAD' -DF = 'http://libmtp.git.sourceforge.net/git/gitweb.cgi?p=libmtp/libmtp;a=blob_plain;f=src/device-flags.h;hb=HEAD' - -import urllib, os, shutil +import os, shutil, subprocess base = os.path.dirname(os.path.abspath(__file__)) -for url, fname in [(MP, 'music-players.h'), (DF, 'device-flags.h')]: - with open(os.path.join(base, fname), 'wb') as f: - shutil.copyfileobj(urllib.urlopen(url), f) +os.chdir('/tmp') +if os.path.exists('libmtp'): + shutil.rmtree('libmtp') +subprocess.check_call(['git', 'clone', 'git://git.code.sf.net/p/libmtp/code', + 'libmtp']) +for x in ('src/music-players.h', 'src/device-flags.h'): + with open(os.path.join(base, os.path.basename(x)), 'wb') as f: + shutil.copyfileobj(open('libmtp/'+x), f) From ea1b4f80326e0b55c6d6b79b5dadf4bd78f188c5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 20 Mar 2013 14:30:44 +0530 Subject: [PATCH 09/18] Remove broken midday recipe --- recipes/midday.recipe | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 recipes/midday.recipe diff --git a/recipes/midday.recipe b/recipes/midday.recipe deleted file mode 100644 index 4dbee1d2f3..0000000000 --- a/recipes/midday.recipe +++ /dev/null @@ -1,13 +0,0 @@ -from calibre.web.feeds.news import CalibrePeriodical - -class MiDDay(CalibrePeriodical): - - title = 'MiDDay' - calibre_periodicals_slug = 'midday' - - description = '''Get your dose of the latest news, views and fun - from the - world of politics, sports and Bollywood to the cartoons, comics and games of - the entertainment section - India’s leading tabloid has it all. To subscribe - visit calibre - Periodicals.''' - language = 'en_IN' From 7e791304e94426aa2c1e48665580af2bd06985e4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 20 Mar 2013 14:45:54 +0530 Subject: [PATCH 10/18] Fix #1125102 (Device detected wrongly) --- src/calibre/devices/misc.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/calibre/devices/misc.py b/src/calibre/devices/misc.py index d7a4531fc7..d8a8ed1871 100644 --- a/src/calibre/devices/misc.py +++ b/src/calibre/devices/misc.py @@ -413,16 +413,16 @@ class WAYTEQ(USBMS): name = 'WayteQ device interface' gui_name = 'WayteQ xBook' - description = _('Communicate with the WayteQ Reader') + description = _('Communicate with the WayteQ and SPC Dickens Readers') author = 'Kovid Goyal' supported_platforms = ['windows', 'osx', 'linux'] # Ordered list of supported formats FORMATS = ['epub', 'mobi', 'prc', 'fb2', 'txt', 'pdf', 'html', 'rtf', 'chm', 'djvu', 'doc'] - VENDOR_ID = [0x05e3] - PRODUCT_ID = [0x0726] - BCD = [0x0222] + VENDOR_ID = [0x05e3, 0x0c45] + PRODUCT_ID = [0x0726, 0x0184] + BCD = [0x0222, 0x0100] EBOOK_DIR_MAIN = 'Documents' SCAN_FROM_ROOT = True @@ -431,6 +431,14 @@ class WAYTEQ(USBMS): WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'RK28_SDK_DEMO' SUPPORTS_SUB_DIRS = True + def get_gui_name(self): + try: + if self.detected_device.idVendor == 0x0c45: + return 'SPC Dickens' + except Exception: + pass + return self.gui_name + def get_carda_ebook_dir(self, for_upload=False): if for_upload: return 'Documents' From 95ef5a8eb4a6cc475326a8ebcd57a2ae8cc9584d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 20 Mar 2013 14:47:42 +0530 Subject: [PATCH 11/18] Update kath.net --- recipes/kath_net.recipe | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/recipes/kath_net.recipe b/recipes/kath_net.recipe index d6155270c0..9a21b18a7e 100644 --- a/recipes/kath_net.recipe +++ b/recipes/kath_net.recipe @@ -11,12 +11,10 @@ class AdvancedUserRecipe1295262156(BasicNewsRecipe): auto_cleanup = True encoding='iso-8859-1' - feeds = [(u'kath.net', u'http://www.kath.net/2005/xml/index.xml')] - def print_version(self, url): - return url+"&print=yes" + return url+"/print/yes" extra_css = 'td.textb {font-size: medium;}' From 97e632168b75538b9b0a30a47b972e554c912bd6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 20 Mar 2013 15:02:22 +0530 Subject: [PATCH 12/18] ToC Editor: Automatic creation of Table of Contents from headings in the book --- src/calibre/ebooks/oeb/polish/toc.py | 61 +++++++++++++++++++++++++++- src/calibre/gui2/toc/main.py | 53 ++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 4 deletions(-) diff --git a/src/calibre/ebooks/oeb/polish/toc.py b/src/calibre/ebooks/oeb/polish/toc.py index d5b013fb72..abac6bd7d1 100644 --- a/src/calibre/ebooks/oeb/polish/toc.py +++ b/src/calibre/ebooks/oeb/polish/toc.py @@ -15,7 +15,7 @@ from functools import partial from lxml import etree from calibre import __version__ -from calibre.ebooks.oeb.base import XPath, uuid_id, xml2text, NCX, NCX_NS, XML +from calibre.ebooks.oeb.base import XPath, uuid_id, xml2text, NCX, NCX_NS, XML, XHTML from calibre.ebooks.oeb.polish.container import guess_type from calibre.utils.localization import get_lang, canonicalize_lang, lang_as_iso639_1 @@ -43,6 +43,9 @@ class TOC(object): for c in self.children: yield c + def __len__(self): + return len(self.children) + def iterdescendants(self): for child in self: yield child @@ -169,6 +172,62 @@ def get_toc(container, verify_destinations=True): verify_toc_destinations(container, ans) return ans +def ensure_id(elem): + if elem.tag == XHTML('a'): + anchor = elem.get('name', None) + if anchor: + return False, anchor + elem_id = elem.get('id', None) + if elem_id: + return False, elem_id + elem.set('id', uuid_id()) + return True, elem.get('id') + +def elem_to_toc_text(elem): + text = xml2text(elem).strip() + if not text: + text = elem.get('title', '') + if not text: + text = elem.get('alt', '') + text = re.sub(r'\s+', ' ', text.strip()) + text = text[:1000].strip() + return text + +def from_xpaths(container, xpaths): + tocroot = TOC() + xpaths = [XPath(xp) for xp in xpaths] + level_prev = {i+1:None for i in xrange(len(xpaths))} + level_prev[0] = tocroot + + for spinepath in container.spine_items: + name = container.abspath_to_name(spinepath) + root = container.parsed(name) + level_item_map = {i+1:frozenset(xp(root)) for i, xp in enumerate(xpaths)} + item_level_map = {e:i for i, elems in level_item_map.iteritems() for e in elems} + item_dirtied = False + + for item in root.iterdescendants(etree.Element): + lvl = plvl = item_level_map.get(item, None) + if lvl is None: + continue + parent = None + while parent is None: + plvl -= 1 + parent = level_prev[plvl] + lvl = plvl + 1 + dirtied, elem_id = ensure_id(item) + text = elem_to_toc_text(item) + item_dirtied = dirtied or item_dirtied + toc = parent.add(text, name, elem_id) + toc.dest_exists = True + level_prev[lvl] = toc + for i in xrange(lvl+1, len(xpaths)+1): + level_prev[i] = None + + if item_dirtied: + container.commit_item(name, keep_parsed=True) + + return tocroot def add_id(container, name, loc): root = container.parsed(name) diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py index 63aad654ad..d0e2a8e0f6 100644 --- a/src/calibre/gui2/toc/main.py +++ b/src/calibre/gui2/toc/main.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import sys, os +import sys, os, textwrap from threading import Thread from functools import partial @@ -17,7 +17,8 @@ from PyQt4.Qt import (QPushButton, QFrame, QVariant, QToolButton, QItemSelectionModel) from calibre.ebooks.oeb.polish.container import get_container, AZW3Container -from calibre.ebooks.oeb.polish.toc import get_toc, add_id, TOC, commit_toc +from calibre.ebooks.oeb.polish.toc import ( + get_toc, add_id, TOC, commit_toc, from_xpaths) from calibre.gui2 import Application, error_dialog, gprefs from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.toc.location import ItemEdit @@ -31,6 +32,7 @@ class ItemView(QFrame): # {{{ delete_item = pyqtSignal() flatten_item = pyqtSignal() go_to_root = pyqtSignal() + create_from_xpath = pyqtSignal(object) def __init__(self, parent): QFrame.__init__(self, parent) @@ -60,6 +62,25 @@ class ItemView(QFrame): # {{{ self.add_new_to_root_button = b = QPushButton(_('Create a &new entry')) b.clicked.connect(self.add_new_to_root) l.addWidget(b) + l.addStretch() + + self.cfmhb = b = QPushButton(_('Generate ToC from &major headings')) + b.clicked.connect(self.create_from_major_headings) + b.setToolTip(textwrap.fill(_( + 'Generate a Table of Contents from the major headings in the book.' + ' This will work if the book identifies its headings using HTML' + ' heading tags. Uses the

,

and

tags.'))) + l.addWidget(b) + self.cfmab = b = QPushButton(_('Generate ToC from &all headings')) + b.clicked.connect(self.create_from_all_headings) + b.setToolTip(textwrap.fill(_( + 'Generate a Table of Contents from all the headings in the book.' + ' This will work if the book identifies its headings using HTML' + ' heading tags. Uses the tags.'))) + l.addWidget(b) + + + l.addStretch() self.w1 = la = QLabel(_('WARNING: calibre only supports the ' 'creation of linear ToCs in AZW3 files. In a ' @@ -133,7 +154,7 @@ class ItemView(QFrame): # {{{ b.setToolTip(_('All children of this entry are brought to the same ' 'level as this entry.')) l.addWidget(b, l.rowCount()+1, 0, 1, 2) - ip.b4 = b = QPushButton(QIcon(I('back.png')), _('&Return to root')) + ip.b4 = b = QPushButton(QIcon(I('back.png')), _('&Return to welcome screen')) b.clicked.connect(self.go_to_root) b.setToolTip(_('Go back to the top level view')) l.addWidget(b, l.rowCount()+1, 0, 1, 2) @@ -147,6 +168,12 @@ class ItemView(QFrame): # {{{ self.w2.setWordWrap(True) l.addWidget(la, l.rowCount(), 0, 1, 2) + def create_from_major_headings(self): + self.create_from_xpath.emit(['//h:h%d'%i for i in xrange(1, 4)]) + + def create_from_all_headings(self): + self.create_from_xpath.emit(['//h:h%d'%i for i in xrange(1, 7)]) + def hide_azw3_warning(self): self.w1.setVisible(False), self.w2.setVisible(False) @@ -242,6 +269,7 @@ class TOCView(QWidget): # {{{ self.item_view = i = ItemView(self) self.item_view.delete_item.connect(self.delete_current_item) i.add_new_item.connect(self.add_new_item) + i.create_from_xpath.connect(self.create_from_xpath) i.flatten_item.connect(self.flatten_item) i.go_to_root.connect(self.go_to_root) l.addWidget(i, 0, 4, col, 1) @@ -443,6 +471,25 @@ class TOCView(QWidget): # {{{ process_node(self.tocw.invisibleRootItem(), root) return root + def insert_toc_fragment(self, toc): + + def process_node(root, tocparent, added): + for child in tocparent: + item = self.create_item(root, child) + added.append(item) + process_node(item, child, added) + + nodes = [] + process_node(self.root, toc, nodes) + self.highlight_item(nodes[0]) + + def create_from_xpath(self, xpaths): + toc = from_xpaths(self.ebook, xpaths) + if len(toc) == 0: + return error_dialog(self, _('No items found'), + _('No items were found that could be added to the Table of Contents.'), show=True) + self.insert_toc_fragment(toc) + # }}} class TOCEditor(QDialog): # {{{ From 1f93448791e78c6465228a9338db518a2ced8cac Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 20 Mar 2013 17:00:41 +0530 Subject: [PATCH 13/18] ToC Editor: Allow creating ToC from links in the book --- src/calibre/ebooks/oeb/polish/toc.py | 33 ++++++++++++++++++++++++++++ src/calibre/gui2/toc/main.py | 21 ++++++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/oeb/polish/toc.py b/src/calibre/ebooks/oeb/polish/toc.py index abac6bd7d1..3a72b837c8 100644 --- a/src/calibre/ebooks/oeb/polish/toc.py +++ b/src/calibre/ebooks/oeb/polish/toc.py @@ -39,6 +39,10 @@ class TOC(object): c.parent = self return c + def remove(self, child): + self.children.remove(child) + child.parent = None + def __iter__(self): for c in self.children: yield c @@ -191,6 +195,8 @@ def elem_to_toc_text(elem): text = elem.get('alt', '') text = re.sub(r'\s+', ' ', text.strip()) text = text[:1000].strip() + if not text: + text = _('(Untitled)') return text def from_xpaths(container, xpaths): @@ -229,6 +235,33 @@ def from_xpaths(container, xpaths): return tocroot +def from_links(container): + toc = TOC() + link_path = XPath('//h:a[@href]') + seen_titles, seen_dests = set(), set() + for spinepath in container.spine_items: + name = container.abspath_to_name(spinepath) + root = container.parsed(name) + for a in link_path(root): + href = a.get('href') + if not href or not href.strip(): + continue + dest = container.href_to_name(href, base=name) + frag = href.rpartition('#')[-1] or None + if (dest, frag) in seen_dests: + continue + seen_dests.add((dest, frag)) + text = elem_to_toc_text(a) + if text in seen_titles: + continue + seen_titles.add(text) + toc.add(text, dest, frag=frag) + verify_toc_destinations(container, toc) + for child in toc: + if not child.dest_exists: + toc.remove(child) + return toc + def add_id(container, name, loc): root = container.parsed(name) body = root.xpath('//*[local-name()="body"]')[0] diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py index d0e2a8e0f6..a9c160a0d4 100644 --- a/src/calibre/gui2/toc/main.py +++ b/src/calibre/gui2/toc/main.py @@ -18,7 +18,7 @@ from PyQt4.Qt import (QPushButton, QFrame, QVariant, from calibre.ebooks.oeb.polish.container import get_container, AZW3Container from calibre.ebooks.oeb.polish.toc import ( - get_toc, add_id, TOC, commit_toc, from_xpaths) + get_toc, add_id, TOC, commit_toc, from_xpaths, from_links) from calibre.gui2 import Application, error_dialog, gprefs from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.toc.location import ItemEdit @@ -33,6 +33,7 @@ class ItemView(QFrame): # {{{ flatten_item = pyqtSignal() go_to_root = pyqtSignal() create_from_xpath = pyqtSignal(object) + create_from_links = pyqtSignal() def __init__(self, parent): QFrame.__init__(self, parent) @@ -79,7 +80,15 @@ class ItemView(QFrame): # {{{ ' heading tags. Uses the tags.'))) l.addWidget(b) - + self.lb = b = QPushButton(_('Generate ToC from &links')) + b.clicked.connect(self.create_from_links) + b.setToolTip(textwrap.fill(_( + 'Generate a Table of Contents from all the links in the book.' + ' Links that point to destinations that do not exist in the book are' + ' ignored. Also multiple links with the same destination or the same' + ' text are ignored.' + ))) + l.addWidget(b) l.addStretch() self.w1 = la = QLabel(_('WARNING: calibre only supports the ' @@ -270,6 +279,7 @@ class TOCView(QWidget): # {{{ self.item_view.delete_item.connect(self.delete_current_item) i.add_new_item.connect(self.add_new_item) i.create_from_xpath.connect(self.create_from_xpath) + i.create_from_links.connect(self.create_from_links) i.flatten_item.connect(self.flatten_item) i.go_to_root.connect(self.go_to_root) l.addWidget(i, 0, 4, col, 1) @@ -490,6 +500,13 @@ class TOCView(QWidget): # {{{ _('No items were found that could be added to the Table of Contents.'), show=True) self.insert_toc_fragment(toc) + def create_from_links(self): + toc = from_links(self.ebook) + if len(toc) == 0: + return error_dialog(self, _('No items found'), + _('No links were found that could be added to the Table of Contents.'), show=True) + self.insert_toc_fragment(toc) + # }}} class TOCEditor(QDialog): # {{{ From 6f7d6cf4eae85218c485e70c5db202298b99656c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 20 Mar 2013 17:51:27 +0530 Subject: [PATCH 14/18] ToC Editor: Allow creating toc from arbitrary xpath expressions --- src/calibre/gui2/toc/main.py | 69 ++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py index a9c160a0d4..06ddc37eaa 100644 --- a/src/calibre/gui2/toc/main.py +++ b/src/calibre/gui2/toc/main.py @@ -22,10 +22,52 @@ from calibre.ebooks.oeb.polish.toc import ( from calibre.gui2 import Application, error_dialog, gprefs from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.toc.location import ItemEdit +from calibre.gui2.convert.xpath_wizard import XPathEdit from calibre.utils.logging import GUILog ICON_SIZE = 24 +class XPathDialog(QDialog): # {{{ + + def __init__(self, parent): + QDialog.__init__(self, parent) + self.setWindowTitle(_('Create ToC from XPath')) + self.l = l = QVBoxLayout() + self.setLayout(l) + self.la = la = QLabel(_( + 'Specify a series of XPath expressions for the different levels of' + ' the Table of Contents. You can use the wizard buttons to help' + ' you create XPath expressions.')) + la.setWordWrap(True) + l.addWidget(la) + self.widgets = [] + for i in xrange(5): + la = _('Level %s ToC:')%('&%d'%(i+1)) + xp = XPathEdit(self) + xp.set_msg(la) + self.widgets.append(xp) + l.addWidget(xp) + + self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) + bb.accepted.connect(self.accept) + bb.rejected.connect(self.reject) + l.addStretch() + l.addWidget(bb) + self.resize(self.sizeHint() + QSize(50, 75)) + + def accept(self): + for w in self.widgets: + if not w.check(): + return error_dialog(self, _('Invalid XPath'), + _('The XPath expression %s is not valid.')%w.xpath, + show=True) + super(XPathDialog, self).accept() + + @property + def xpaths(self): + return [w.xpath for w in self.widgets if w.xpath.strip()] +# }}} + class ItemView(QFrame): # {{{ add_new_item = pyqtSignal(object, object) @@ -90,6 +132,14 @@ class ItemView(QFrame): # {{{ ))) l.addWidget(b) + self.xpb = b = QPushButton(_('Generate ToC from &XPath')) + b.clicked.connect(self.create_from_user_xpath) + b.setToolTip(textwrap.fill(_( + 'Generate a Table of Contents from arbitrary XPath expressions.' + ))) + l.addWidget(b) + + l.addStretch() self.w1 = la = QLabel(_('WARNING: calibre only supports the ' 'creation of linear ToCs in AZW3 files. In a ' @@ -151,18 +201,20 @@ class ItemView(QFrame): # {{{ ip.b5 = b = QPushButton(QIcon(I('plus.png')), _('New entry &below this entry')) b.clicked.connect(partial(self.add_new, 'after')) l.addWidget(b, l.rowCount(), 0, 1, 2) - ip.hl4 = hl = QFrame() - hl.setFrameShape(hl.HLine) - l.addWidget(hl, l.rowCount(), 0, 1, 2) - l.setRowMinimumHeight(rs, 20) - # Flatten entry - rs = l.rowCount() ip.b3 = b = QPushButton(QIcon(I('heuristics.png')), _('&Flatten this entry')) b.clicked.connect(self.flatten_item) b.setToolTip(_('All children of this entry are brought to the same ' 'level as this entry.')) l.addWidget(b, l.rowCount()+1, 0, 1, 2) + + ip.hl4 = hl = QFrame() + hl.setFrameShape(hl.HLine) + l.addWidget(hl, l.rowCount(), 0, 1, 2) + l.setRowMinimumHeight(rs, 20) + + # Return to welcome + rs = l.rowCount() ip.b4 = b = QPushButton(QIcon(I('back.png')), _('&Return to welcome screen')) b.clicked.connect(self.go_to_root) b.setToolTip(_('Go back to the top level view')) @@ -183,6 +235,11 @@ class ItemView(QFrame): # {{{ def create_from_all_headings(self): self.create_from_xpath.emit(['//h:h%d'%i for i in xrange(1, 7)]) + def create_from_user_xpath(self): + d = XPathDialog(self) + if d.exec_() == d.Accepted and d.xpaths: + self.create_from_xpath.emit(d.xpaths) + def hide_azw3_warning(self): self.w1.setVisible(False), self.w2.setVisible(False) From fc43f4c0d7c4f775c056bc77c56609cc9aabbd61 Mon Sep 17 00:00:00 2001 From: GRiker Date: Wed, 20 Mar 2013 06:07:23 -0700 Subject: [PATCH 15/18] Added diagnostic timing information to catalog creation --- src/calibre/library/catalogs/epub_mobi.py | 20 ++++++++++++++----- .../library/catalogs/epub_mobi_builder.py | 10 ++++++++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/calibre/library/catalogs/epub_mobi.py b/src/calibre/library/catalogs/epub_mobi.py index 965394a44e..b201136044 100644 --- a/src/calibre/library/catalogs/epub_mobi.py +++ b/src/calibre/library/catalogs/epub_mobi.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os +import datetime, os, time from collections import namedtuple from calibre import strftime @@ -388,18 +388,24 @@ class EPUB_MOBI(CatalogPlugin): 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) - if opts.verbose: - log.info(" Begin catalog source generation") - try: catalog.build_sources() if opts.verbose: - log.info(" Completed catalog source generation\n") + 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) except: @@ -489,5 +495,9 @@ class EPUB_MOBI(CatalogPlugin): 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 diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py index 32175cdbd3..04116cb0e3 100644 --- a/src/calibre/library/catalogs/epub_mobi_builder.py +++ b/src/calibre/library/catalogs/epub_mobi_builder.py @@ -3,7 +3,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Greg Riker' -import datetime, htmlentitydefs, os, platform, re, shutil, unicodedata, zlib +import datetime, htmlentitydefs, os, platform, re, shutil, time, unicodedata, zlib from copy import deepcopy from xml.sax.saxutils import escape @@ -4855,7 +4855,13 @@ class CatalogBuilder(object): self.progress_int = 0.01 self.reporter(self.progress_int, self.progress_string) if self.opts.cli_environment: - self.opts.log(u"%3.0f%% %s" % (self.progress_int * 100, self.progress_string)) + log_msg = u"%3.0f%% %s" % (self.progress_int * 100, self.progress_string) + if self.opts.verbose: + log_msg += " (%s)" % str(datetime.timedelta(seconds=int(time.time() - self.opts.start_time))) + else: + log_msg = ("%s (%s)" % (self.progress_string, + str(datetime.timedelta(seconds=int(time.time() - self.opts.start_time))))) + self.opts.log(log_msg) def update_progress_micro_step(self, description, micro_step_pct): """ Update calibre's job status UI. From 1dfc94eaf2a758b9eb65a087c049bf40203926ce Mon Sep 17 00:00:00 2001 From: GRiker Date: Wed, 20 Mar 2013 10:10:42 -0700 Subject: [PATCH 16/18] Fixed handling of two combo box fields in catalog presets, enable/disable Description-related fields when loading a preset. --- src/calibre/gui2/catalog/catalog_epub_mobi.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py index fa90bd85aa..70531b888f 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.py +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py @@ -579,6 +579,7 @@ class PluginWidget(QWidget,Ui_Form): ''' Update catalog options from current preset ''' + print("preset_change: %s" % item_name) if not item_name: return @@ -645,6 +646,9 @@ class PluginWidget(QWidget,Ui_Form): title = options['catalog_title'] self.set_format_and_title(format, title) + # Reset Descriptions-related enable/disable switches + self.generate_descriptions_changed(self.generate_descriptions.isChecked()) + def preset_remove(self): if self.preset_field.currentIndex() == 0: return @@ -740,9 +744,8 @@ class PluginWidget(QWidget,Ui_Form): preset['merge_comments_rule'] = "%s:%s:%s" % \ (self.merge_source_field_name, checked, include_hr) - preset['header_note_source_field'] = self.header_note_source_field_name - - preset['genre_source_field'] = self.genre_source_field_name + preset['header_note_source_field'] = unicode(self.header_note_source_field.currentText()) + preset['genre_source_field'] = unicode(self.genre_source_field.currentText()) # Append the current output profile try: From 78c81b183ae018a1cb1c403323d2395310d0843b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 20 Mar 2013 23:41:27 +0530 Subject: [PATCH 17/18] Conversion: Add support for CSS tylesheets that wrap their rules inside a @media rule. Fixes #1157345 (CSS stripped in Mobi-to-Mobi conversion) --- src/calibre/ebooks/oeb/stylizer.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py index 114bc3587f..3fac8c43cc 100644 --- a/src/calibre/ebooks/oeb/stylizer.py +++ b/src/calibre/ebooks/oeb/stylizer.py @@ -305,8 +305,17 @@ class Stylizer(object): href = stylesheet.href self.stylesheets.add(href) for rule in stylesheet.cssRules: - rules.extend(self.flatten_rule(rule, href, index)) - index = index + 1 + if rule.type == rule.MEDIA_RULE: + media = {rule.media.item(i) for i in + xrange(rule.media.length)} + if not media.intersection({'all', 'screen', 'amzn-kf8'}): + continue + for subrule in rule.cssRules: + rules.extend(self.flatten_rule(subrule, href, index)) + index += 1 + else: + rules.extend(self.flatten_rule(rule, href, index)) + index = index + 1 rules.sort() self.rules = rules self._styles = {} From 7afbb69a48bb11b77dbdca5a2a3c826ec33319ad Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 20 Mar 2013 23:56:19 +0530 Subject: [PATCH 18/18] ... --- src/calibre/web/feeds/news.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index e9348f6ae7..0e0afe2bc4 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -355,6 +355,10 @@ class BasicNewsRecipe(Recipe): #: The minimum jpeg quality will be 5/100 so it is possible this constraint #: will not be met. This parameter can be overridden by the parameter #: compress_news_images_max_size which provides a fixed maximum size for images. + #: Note that if you enable scale_news_images_to_device then the image will + #: first be scaled and then its quality lowered until its size is less than + #: (w * h)/factor where w and h are now the *scaled* image dimensions. In + #: other words, this compression happens after scaling. compress_news_images_auto_size = 16 #: Set jpeg quality so images do not exceed the size given (in KBytes).