mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Catalogs: Allow using custom columns as the source for Genres when generating catalogs
This commit is contained in:
commit
61ad8e6c53
@ -227,7 +227,7 @@ class ITUNES(DriverBase):
|
|||||||
# 0x1297 iPhone 4
|
# 0x1297 iPhone 4
|
||||||
# 0x129a iPad
|
# 0x129a iPad
|
||||||
# 0x129f iPad2 (WiFi)
|
# 0x129f iPad2 (WiFi)
|
||||||
# 0x12a0 iPhone 4S
|
# 0x12a0 iPhone 4S (GSM)
|
||||||
# 0x12a2 iPad2 (GSM)
|
# 0x12a2 iPad2 (GSM)
|
||||||
# 0x12a3 iPad2 (CDMA)
|
# 0x12a3 iPad2 (CDMA)
|
||||||
# 0x12a6 iPad3 (GSM)
|
# 0x12a6 iPad3 (GSM)
|
||||||
@ -1196,10 +1196,25 @@ class ITUNES(DriverBase):
|
|||||||
logger().error(" Device|Books playlist not found")
|
logger().error(" Device|Books playlist not found")
|
||||||
|
|
||||||
# Add the passed book to the Device|Books playlist
|
# Add the passed book to the Device|Books playlist
|
||||||
added = pl.add(appscript.mactypes.File(fpath),to=pl)
|
attempts = 2
|
||||||
if False:
|
delay = 1.0
|
||||||
logger().info(" '%s' added to Device|Books" % metadata.title)
|
while attempts:
|
||||||
|
try:
|
||||||
|
added = pl.add(appscript.mactypes.File(fpath),to=pl)
|
||||||
|
if False:
|
||||||
|
logger().info(" '%s' added to Device|Books" % metadata.title)
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
attempts -= 1
|
||||||
|
if DEBUG:
|
||||||
|
logger().warning(" failed to add book, waiting %.1f seconds to try again (attempt #%d)" %
|
||||||
|
(delay, (3 - attempts)))
|
||||||
|
time.sleep(delay)
|
||||||
|
else:
|
||||||
|
if DEBUG:
|
||||||
|
logger().error(" failed to add '%s' to Device|Books" % metadata.title)
|
||||||
|
raise UserFeedback("Unable to add '%s' in direct connect mode" % metadata.title,
|
||||||
|
details=None, level=UserFeedback.ERROR)
|
||||||
self._wait_for_writable_metadata(added)
|
self._wait_for_writable_metadata(added)
|
||||||
return added
|
return added
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
[{'ordinal':0,
|
[{'ordinal':0,
|
||||||
'enabled':True,
|
'enabled':True,
|
||||||
'name':_('Catalogs'),
|
'name':_('Catalogs'),
|
||||||
'field':'Tags',
|
'field':_('Tags'),
|
||||||
'pattern':'Catalog'},],
|
'pattern':'Catalog'},],
|
||||||
['table_widget'])
|
['table_widget'])
|
||||||
|
|
||||||
@ -97,13 +97,13 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
[{'ordinal':0,
|
[{'ordinal':0,
|
||||||
'enabled':True,
|
'enabled':True,
|
||||||
'name':_('Read book'),
|
'name':_('Read book'),
|
||||||
'field':'Tags',
|
'field':_('Tags'),
|
||||||
'pattern':'+',
|
'pattern':'+',
|
||||||
'prefix':u'\u2713'},
|
'prefix':u'\u2713'},
|
||||||
{'ordinal':1,
|
{'ordinal':1,
|
||||||
'enabled':True,
|
'enabled':True,
|
||||||
'name':_('Wishlist item'),
|
'name':_('Wishlist item'),
|
||||||
'field':'Tags',
|
'field':_('Tags'),
|
||||||
'pattern':'Wishlist',
|
'pattern':'Wishlist',
|
||||||
'prefix':u'\u00d7'},],
|
'prefix':u'\u00d7'},],
|
||||||
['table_widget','table_widget'])
|
['table_widget','table_widget'])
|
||||||
@ -127,7 +127,7 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
elif 'prefix' in rule and rule['prefix'] is None:
|
elif 'prefix' in rule and rule['prefix'] is None:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
if rule['field'] != 'Tags':
|
if rule['field'] != _('Tags'):
|
||||||
# Look up custom column friendly name
|
# Look up custom column friendly name
|
||||||
rule['field'] = self.eligible_custom_fields[rule['field']]['field']
|
rule['field'] = self.eligible_custom_fields[rule['field']]['field']
|
||||||
if rule['pattern'] in [_('any value'),_('any date')]:
|
if rule['pattern'] in [_('any value'),_('any date')]:
|
||||||
@ -144,14 +144,14 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
# Strip off the trailing '_tw'
|
# Strip off the trailing '_tw'
|
||||||
opts_dict[c_name[:-3]] = opt_value
|
opts_dict[c_name[:-3]] = opt_value
|
||||||
|
|
||||||
def exclude_genre_changed(self, regex):
|
def exclude_genre_changed(self):
|
||||||
""" Dynamically compute excluded genres.
|
""" Dynamically compute excluded genres.
|
||||||
|
|
||||||
Run exclude_genre regex against db.all_tags() to show excluded tags.
|
Run exclude_genre regex against selected genre_source_field to show excluded tags.
|
||||||
PROVISIONAL CODE, NEEDS TESTING
|
|
||||||
|
|
||||||
Args:
|
Inputs:
|
||||||
regex (QLineEdit.text()): regex to compile, compute
|
current regex
|
||||||
|
genre_source_field
|
||||||
|
|
||||||
Output:
|
Output:
|
||||||
self.exclude_genre_results (QLabel): updated to show tags to be excluded as genres
|
self.exclude_genre_results (QLabel): updated to show tags to be excluded as genres
|
||||||
@ -183,23 +183,31 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
return "%s ... %s" % (', '.join(start), ', '.join(end))
|
return "%s ... %s" % (', '.join(start), ', '.join(end))
|
||||||
|
|
||||||
results = _('No genres will be excluded')
|
results = _('No genres will be excluded')
|
||||||
|
|
||||||
|
regex = unicode(getattr(self, 'exclude_genre').text()).strip()
|
||||||
if not regex:
|
if not regex:
|
||||||
self.exclude_genre_results.clear()
|
self.exclude_genre_results.clear()
|
||||||
self.exclude_genre_results.setText(results)
|
self.exclude_genre_results.setText(results)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Populate all_genre_tags from currently source
|
||||||
|
if self.genre_source_field_name == _('Tags'):
|
||||||
|
all_genre_tags = self.db.all_tags()
|
||||||
|
else:
|
||||||
|
all_genre_tags = list(self.db.all_custom(self.db.field_metadata.key_to_label(self.genre_source_field_name)))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pattern = re.compile((str(regex)))
|
pattern = re.compile((str(regex)))
|
||||||
except:
|
except:
|
||||||
results = _("regex error: %s") % sys.exc_info()[1]
|
results = _("regex error: %s") % sys.exc_info()[1]
|
||||||
else:
|
else:
|
||||||
excluded_tags = []
|
excluded_tags = []
|
||||||
for tag in self.all_tags:
|
for tag in all_genre_tags:
|
||||||
hit = pattern.search(tag)
|
hit = pattern.search(tag)
|
||||||
if hit:
|
if hit:
|
||||||
excluded_tags.append(hit.string)
|
excluded_tags.append(hit.string)
|
||||||
if excluded_tags:
|
if excluded_tags:
|
||||||
if set(excluded_tags) == set(self.all_tags):
|
if set(excluded_tags) == set(all_genre_tags):
|
||||||
results = _("All genres will be excluded")
|
results = _("All genres will be excluded")
|
||||||
else:
|
else:
|
||||||
results = _truncated_results(excluded_tags)
|
results = _truncated_results(excluded_tags)
|
||||||
@ -218,7 +226,7 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
def fetch_eligible_custom_fields(self):
|
def fetch_eligible_custom_fields(self):
|
||||||
self.all_custom_fields = self.db.custom_field_keys()
|
self.all_custom_fields = self.db.custom_field_keys()
|
||||||
custom_fields = {}
|
custom_fields = {}
|
||||||
custom_fields['Tags'] = {'field':'tag', 'datatype':u'text'}
|
custom_fields[_('Tags')] = {'field':'tag', 'datatype':u'text'}
|
||||||
for custom_field in self.all_custom_fields:
|
for custom_field in self.all_custom_fields:
|
||||||
field_md = self.db.metadata_for_field(custom_field)
|
field_md = self.db.metadata_for_field(custom_field)
|
||||||
if field_md['datatype'] in ['bool','composite','datetime','enumeration','text']:
|
if field_md['datatype'] in ['bool','composite','datetime','enumeration','text']:
|
||||||
@ -237,6 +245,34 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
self.merge_after.setEnabled(enabled)
|
self.merge_after.setEnabled(enabled)
|
||||||
self.include_hr.setEnabled(enabled)
|
self.include_hr.setEnabled(enabled)
|
||||||
|
|
||||||
|
def generate_genres_changed(self, enabled):
|
||||||
|
'''
|
||||||
|
Toggle Genres-related controls
|
||||||
|
'''
|
||||||
|
self.genre_source_field.setEnabled(enabled)
|
||||||
|
|
||||||
|
def genre_source_field_changed(self,new_index):
|
||||||
|
'''
|
||||||
|
Process changes in the genre_source_field combo box
|
||||||
|
Update Excluded genres preview
|
||||||
|
'''
|
||||||
|
new_source = str(self.genre_source_field.currentText())
|
||||||
|
self.genre_source_field_name = new_source
|
||||||
|
if new_source != _('Tags'):
|
||||||
|
genre_source_spec = self.genre_source_fields[unicode(new_source)]
|
||||||
|
self.genre_source_field_name = genre_source_spec['field']
|
||||||
|
self.exclude_genre_changed()
|
||||||
|
|
||||||
|
def header_note_source_field_changed(self,new_index):
|
||||||
|
'''
|
||||||
|
Process changes in the header_note_source_field combo box
|
||||||
|
'''
|
||||||
|
new_source = str(self.header_note_source_field.currentText())
|
||||||
|
self.header_note_source_field_name = new_source
|
||||||
|
if new_source > '':
|
||||||
|
header_note_source_spec = self.header_note_source_fields[unicode(new_source)]
|
||||||
|
self.header_note_source_field_name = header_note_source_spec['field']
|
||||||
|
|
||||||
def initialize(self, name, db):
|
def initialize(self, name, db):
|
||||||
'''
|
'''
|
||||||
CheckBoxControls (c_type: check_box):
|
CheckBoxControls (c_type: check_box):
|
||||||
@ -245,8 +281,8 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
'generate_recently_added','generate_descriptions',
|
'generate_recently_added','generate_descriptions',
|
||||||
'include_hr']
|
'include_hr']
|
||||||
ComboBoxControls (c_type: combo_box):
|
ComboBoxControls (c_type: combo_box):
|
||||||
['exclude_source_field','header_note_source_field',
|
['exclude_source_field','genre_source_field',
|
||||||
'merge_source_field']
|
'header_note_source_field','merge_source_field']
|
||||||
LineEditControls (c_type: line_edit):
|
LineEditControls (c_type: line_edit):
|
||||||
['exclude_genre']
|
['exclude_genre']
|
||||||
RadioButtonControls (c_type: radio_button):
|
RadioButtonControls (c_type: radio_button):
|
||||||
@ -261,11 +297,11 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
'''
|
'''
|
||||||
self.name = name
|
self.name = name
|
||||||
self.db = db
|
self.db = db
|
||||||
self.all_tags = db.all_tags()
|
self.all_genre_tags = []
|
||||||
self.fetch_eligible_custom_fields()
|
self.fetch_eligible_custom_fields()
|
||||||
self.populate_combo_boxes()
|
self.populate_combo_boxes()
|
||||||
|
|
||||||
# Update dialog fields from stored options
|
# Update dialog fields from stored options, validating options for combo boxes
|
||||||
exclusion_rules = []
|
exclusion_rules = []
|
||||||
prefix_rules = []
|
prefix_rules = []
|
||||||
for opt in self.OPTION_FIELDS:
|
for opt in self.OPTION_FIELDS:
|
||||||
@ -273,13 +309,18 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
opt_value = gprefs.get(self.name + '_' + c_name, c_def)
|
opt_value = gprefs.get(self.name + '_' + c_name, c_def)
|
||||||
if c_type in ['check_box']:
|
if c_type in ['check_box']:
|
||||||
getattr(self, c_name).setChecked(eval(str(opt_value)))
|
getattr(self, c_name).setChecked(eval(str(opt_value)))
|
||||||
elif c_type in ['combo_box'] and opt_value is not None:
|
elif c_type in ['combo_box']:
|
||||||
# *** Test this code with combo boxes ***
|
if opt_value is None:
|
||||||
#index = self.read_source_field.findText(opt_value)
|
index = 0
|
||||||
index = getattr(self,c_name).findText(opt_value)
|
if c_name == 'genre_source_field':
|
||||||
if index == -1 and c_name == 'read_source_field':
|
index = self.genre_source_field.findText(_('Tags'))
|
||||||
index = self.read_source_field.findText('Tag')
|
else:
|
||||||
#self.read_source_field.setCurrentIndex(index)
|
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)
|
getattr(self,c_name).setCurrentIndex(index)
|
||||||
elif c_type in ['line_edit']:
|
elif c_type in ['line_edit']:
|
||||||
getattr(self, c_name).setText(opt_value if opt_value else '')
|
getattr(self, c_name).setText(opt_value if opt_value else '')
|
||||||
@ -320,6 +361,17 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
header_note_source_spec = self.header_note_source_fields[cs]
|
header_note_source_spec = self.header_note_source_fields[cs]
|
||||||
self.header_note_source_field_name = header_note_source_spec['field']
|
self.header_note_source_field_name = header_note_source_spec['field']
|
||||||
|
|
||||||
|
# Init self.genre_source_field_name
|
||||||
|
self.genre_source_field_name = _('Tags')
|
||||||
|
cs = unicode(self.genre_source_field.currentText())
|
||||||
|
if cs != _('Tags'):
|
||||||
|
genre_source_spec = self.genre_source_fields[cs]
|
||||||
|
self.genre_source_field_name = genre_source_spec['field']
|
||||||
|
|
||||||
|
# Hook Genres checkbox
|
||||||
|
self.generate_genres.clicked.connect(self.generate_genres_changed)
|
||||||
|
self.generate_genres_changed(self.generate_genres.isChecked())
|
||||||
|
|
||||||
# Initialize exclusion rules
|
# Initialize exclusion rules
|
||||||
self.exclusion_rules_table = ExclusionRules(self.exclusion_rules_gb,
|
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)
|
||||||
@ -329,7 +381,27 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
"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
|
# Initialize excluded genres preview
|
||||||
self.exclude_genre_changed(unicode(getattr(self, 'exclude_genre').text()).strip())
|
self.exclude_genre_changed()
|
||||||
|
|
||||||
|
def merge_source_field_changed(self,new_index):
|
||||||
|
'''
|
||||||
|
Process changes in the merge_source_field combo box
|
||||||
|
'''
|
||||||
|
new_source = str(self.merge_source_field.currentText())
|
||||||
|
self.merge_source_field_name = new_source
|
||||||
|
if new_source > '':
|
||||||
|
merge_source_spec = self.merge_source_fields[unicode(new_source)]
|
||||||
|
self.merge_source_field_name = merge_source_spec['field']
|
||||||
|
if not self.merge_before.isChecked() and not self.merge_after.isChecked():
|
||||||
|
self.merge_after.setChecked(True)
|
||||||
|
self.merge_before.setEnabled(True)
|
||||||
|
self.merge_after.setEnabled(True)
|
||||||
|
self.include_hr.setEnabled(True)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.merge_before.setEnabled(False)
|
||||||
|
self.merge_after.setEnabled(False)
|
||||||
|
self.include_hr.setEnabled(False)
|
||||||
|
|
||||||
def options(self):
|
def options(self):
|
||||||
# Save/return the current options
|
# Save/return the current options
|
||||||
@ -373,7 +445,7 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
else:
|
else:
|
||||||
opts_dict[c_name] = opt_value
|
opts_dict[c_name] = opt_value
|
||||||
|
|
||||||
# Generate specs for merge_comments, header_note_source_field
|
# Generate specs for merge_comments, header_note_source_field, genre_source_field
|
||||||
checked = ''
|
checked = ''
|
||||||
if self.merge_before.isChecked():
|
if self.merge_before.isChecked():
|
||||||
checked = 'before'
|
checked = 'before'
|
||||||
@ -385,6 +457,8 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
|
|
||||||
opts_dict['header_note_source_field'] = self.header_note_source_field_name
|
opts_dict['header_note_source_field'] = self.header_note_source_field_name
|
||||||
|
|
||||||
|
opts_dict['genre_source_field'] = self.genre_source_field_name
|
||||||
|
|
||||||
# Fix up exclude_genre regex if blank. Assume blank = no exclusions
|
# Fix up exclude_genre regex if blank. Assume blank = no exclusions
|
||||||
if opts_dict['exclude_genre'] == '':
|
if opts_dict['exclude_genre'] == '':
|
||||||
opts_dict['exclude_genre'] = 'a^'
|
opts_dict['exclude_genre'] = 'a^'
|
||||||
@ -457,35 +531,18 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
self.merge_after.setEnabled(False)
|
self.merge_after.setEnabled(False)
|
||||||
self.include_hr.setEnabled(False)
|
self.include_hr.setEnabled(False)
|
||||||
|
|
||||||
def header_note_source_field_changed(self,new_index):
|
# Populate the 'Genres' combo box
|
||||||
'''
|
custom_fields = {_('Tags'):{'field':None,'datatype':None}}
|
||||||
Process changes in the header_note_source_field combo box
|
for custom_field in self.all_custom_fields:
|
||||||
'''
|
field_md = self.db.metadata_for_field(custom_field)
|
||||||
new_source = str(self.header_note_source_field.currentText())
|
if field_md['datatype'] in ['text','enumeration']:
|
||||||
self.header_note_source_field_name = new_source
|
custom_fields[field_md['name']] = {'field':custom_field,
|
||||||
if new_source > '':
|
'datatype':field_md['datatype']}
|
||||||
header_note_source_spec = self.header_note_source_fields[unicode(new_source)]
|
# Add the sorted eligible fields to the combo box
|
||||||
self.header_note_source_field_name = header_note_source_spec['field']
|
for cf in sorted(custom_fields, key=sort_key):
|
||||||
|
self.genre_source_field.addItem(cf)
|
||||||
def merge_source_field_changed(self,new_index):
|
self.genre_source_fields = custom_fields
|
||||||
'''
|
self.genre_source_field.currentIndexChanged.connect(self.genre_source_field_changed)
|
||||||
Process changes in the merge_source_field combo box
|
|
||||||
'''
|
|
||||||
new_source = str(self.merge_source_field.currentText())
|
|
||||||
self.merge_source_field_name = new_source
|
|
||||||
if new_source > '':
|
|
||||||
merge_source_spec = self.merge_source_fields[unicode(new_source)]
|
|
||||||
self.merge_source_field_name = merge_source_spec['field']
|
|
||||||
if not self.merge_before.isChecked() and not self.merge_after.isChecked():
|
|
||||||
self.merge_after.setChecked(True)
|
|
||||||
self.merge_before.setEnabled(True)
|
|
||||||
self.merge_after.setEnabled(True)
|
|
||||||
self.include_hr.setEnabled(True)
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.merge_before.setEnabled(False)
|
|
||||||
self.merge_after.setEnabled(False)
|
|
||||||
self.include_hr.setEnabled(False)
|
|
||||||
|
|
||||||
def show_help(self):
|
def show_help(self):
|
||||||
'''
|
'''
|
||||||
@ -779,9 +836,10 @@ class GenericRulesTable(QTableWidget):
|
|||||||
# Populate the Pattern field based upon the Source field
|
# Populate the Pattern field based upon the Source field
|
||||||
|
|
||||||
source_field = str(combo.currentText())
|
source_field = str(combo.currentText())
|
||||||
|
|
||||||
if source_field == '':
|
if source_field == '':
|
||||||
values = []
|
values = []
|
||||||
elif source_field == 'Tags':
|
elif source_field == _('Tags'):
|
||||||
values = sorted(self.db.all_tags(), key=sort_key)
|
values = sorted(self.db.all_tags(), key=sort_key)
|
||||||
else:
|
else:
|
||||||
if self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['enumeration', 'text']:
|
if self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['enumeration', 'text']:
|
||||||
|
@ -54,40 +54,71 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="2">
|
<item row="1" column="0">
|
||||||
<widget class="QCheckBox" name="generate_titles">
|
<widget class="QCheckBox" name="generate_titles">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Titles</string>
|
<string>&Titles</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="3">
|
<item row="3" column="0">
|
||||||
<widget class="QCheckBox" name="generate_series">
|
<widget class="QCheckBox" name="generate_series">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Series</string>
|
<string>&Series</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="0" column="2">
|
||||||
<widget class="QCheckBox" name="generate_genres">
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
<property name="text">
|
<item>
|
||||||
<string>&Genres</string>
|
<widget class="QCheckBox" name="generate_genres">
|
||||||
</property>
|
<property name="text">
|
||||||
</widget>
|
<string>&Genres</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="genre_source_field">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Field containing Genre information</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="2">
|
<item row="1" column="2">
|
||||||
<widget class="QCheckBox" name="generate_recently_added">
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
<property name="text">
|
<item>
|
||||||
<string>&Recently Added</string>
|
<widget class="QCheckBox" name="generate_recently_added">
|
||||||
</property>
|
<property name="minimumSize">
|
||||||
</widget>
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>26</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Recently Added</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="3">
|
<item row="3" column="2">
|
||||||
<widget class="QCheckBox" name="generate_descriptions">
|
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||||
<property name="text">
|
<item>
|
||||||
<string>&Descriptions</string>
|
<widget class="QCheckBox" name="generate_descriptions">
|
||||||
</property>
|
<property name="minimumSize">
|
||||||
</widget>
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>26</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Descriptions</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
@ -177,7 +208,7 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
|
|||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Tags to &exclude (regex):</string>
|
<string>Genres to &exclude (regex):</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="textFormat">
|
<property name="textFormat">
|
||||||
<enum>Qt::AutoText</enum>
|
<enum>Qt::AutoText</enum>
|
||||||
|
@ -19,4 +19,5 @@ TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate', '
|
|||||||
|
|
||||||
class AuthorSortMismatchException(Exception): pass
|
class AuthorSortMismatchException(Exception): pass
|
||||||
class EmptyCatalogException(Exception): pass
|
class EmptyCatalogException(Exception): pass
|
||||||
|
class InvalidGenresSourceFieldException(Exception): pass
|
||||||
|
|
||||||
|
@ -121,6 +121,13 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
help=_("Include 'Recently Added' section in catalog.\n"
|
help=_("Include 'Recently Added' section in catalog.\n"
|
||||||
"Default: '%default'\n"
|
"Default: '%default'\n"
|
||||||
"Applies to: AZW3, ePub, MOBI output formats")),
|
"Applies to: AZW3, ePub, MOBI output formats")),
|
||||||
|
Option('--genre-source-field',
|
||||||
|
default='Tags',
|
||||||
|
dest='genre_source_field',
|
||||||
|
action = None,
|
||||||
|
help=_("Source field for Genres section.\n"
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: AZW3, ePub, MOBI output formats")),
|
||||||
Option('--header-note-source-field',
|
Option('--header-note-source-field',
|
||||||
default='',
|
default='',
|
||||||
dest='header_note_source_field',
|
dest='header_note_source_field',
|
||||||
@ -327,7 +334,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
if key in ['catalog_title','author_clip','connected_kindle','creator',
|
if key in ['catalog_title','author_clip','connected_kindle','creator',
|
||||||
'cross_reference_authors','description_clip','exclude_book_marker',
|
'cross_reference_authors','description_clip','exclude_book_marker',
|
||||||
'exclude_genre','exclude_tags','exclusion_rules', 'fmt',
|
'exclude_genre','exclude_tags','exclusion_rules', 'fmt',
|
||||||
'header_note_source_field','merge_comments_rule',
|
'genre_source_field', 'header_note_source_field','merge_comments_rule',
|
||||||
'output_profile','prefix_rules','read_book_marker',
|
'output_profile','prefix_rules','read_book_marker',
|
||||||
'search_text','sort_by','sort_descriptions_by_author','sync',
|
'search_text','sort_by','sort_descriptions_by_author','sync',
|
||||||
'thumb_width','use_existing_cover','wishlist_tag']:
|
'thumb_width','use_existing_cover','wishlist_tag']:
|
||||||
|
@ -15,7 +15,8 @@ from calibre.customize.ui import output_profiles
|
|||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString
|
||||||
from calibre.ebooks.chardet import substitute_entites
|
from calibre.ebooks.chardet import substitute_entites
|
||||||
from calibre.ebooks.metadata import author_to_author_sort
|
from calibre.ebooks.metadata import author_to_author_sort
|
||||||
from calibre.library.catalogs import AuthorSortMismatchException, EmptyCatalogException
|
from calibre.library.catalogs import AuthorSortMismatchException, EmptyCatalogException, \
|
||||||
|
InvalidGenresSourceFieldException
|
||||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||||
from calibre.utils.config import config_dir
|
from calibre.utils.config import config_dir
|
||||||
from calibre.utils.date import format_date, is_date_undefined, now as nowf
|
from calibre.utils.date import format_date, is_date_undefined, now as nowf
|
||||||
@ -134,7 +135,7 @@ class CatalogBuilder(object):
|
|||||||
self.generate_recently_read = False
|
self.generate_recently_read = False
|
||||||
self.genres = []
|
self.genres = []
|
||||||
self.genre_tags_dict = \
|
self.genre_tags_dict = \
|
||||||
self.filter_db_tags(max_len = 245 - len("%s/Genre_.html" % self.content_dir)) \
|
self.filter_genre_tags(max_len = 245 - len("%s/Genre_.html" % self.content_dir)) \
|
||||||
if self.opts.generate_genres else None
|
if self.opts.generate_genres else None
|
||||||
self.html_filelist_1 = []
|
self.html_filelist_1 = []
|
||||||
self.html_filelist_2 = []
|
self.html_filelist_2 = []
|
||||||
@ -938,6 +939,21 @@ class CatalogBuilder(object):
|
|||||||
this_title['tags'] = self.filter_excluded_genres(record['tags'],
|
this_title['tags'] = self.filter_excluded_genres(record['tags'],
|
||||||
self.opts.exclude_genre)
|
self.opts.exclude_genre)
|
||||||
|
|
||||||
|
this_title['genres'] = []
|
||||||
|
if self.opts.genre_source_field == _('Tags'):
|
||||||
|
this_title['genres'] = this_title['tags']
|
||||||
|
else:
|
||||||
|
record_genres = self.db.get_field(record['id'],
|
||||||
|
self.opts.genre_source_field,
|
||||||
|
index_is_id=True)
|
||||||
|
|
||||||
|
if record_genres:
|
||||||
|
if type(record_genres) is not list:
|
||||||
|
record_genres = [record_genres]
|
||||||
|
|
||||||
|
this_title['genres'] = self.filter_excluded_genres(record_genres,
|
||||||
|
self.opts.exclude_genre)
|
||||||
|
|
||||||
if record['formats']:
|
if record['formats']:
|
||||||
formats = []
|
formats = []
|
||||||
for format in record['formats']:
|
for format in record['formats']:
|
||||||
@ -1104,7 +1120,7 @@ class CatalogBuilder(object):
|
|||||||
|
|
||||||
self.bookmarked_books = bookmarks
|
self.bookmarked_books = bookmarks
|
||||||
|
|
||||||
def filter_db_tags(self, max_len):
|
def filter_genre_tags(self, max_len):
|
||||||
""" Remove excluded tags from data set, return normalized genre list.
|
""" Remove excluded tags from data set, return normalized genre list.
|
||||||
|
|
||||||
Filter all db tags, removing excluded tags supplied in opts.
|
Filter all db tags, removing excluded tags supplied in opts.
|
||||||
@ -1166,7 +1182,32 @@ class CatalogBuilder(object):
|
|||||||
normalized_tags = []
|
normalized_tags = []
|
||||||
friendly_tags = []
|
friendly_tags = []
|
||||||
excluded_tags = []
|
excluded_tags = []
|
||||||
for tag in self.db.all_tags():
|
|
||||||
|
# Fetch all possible genres from source field
|
||||||
|
all_genre_tags = []
|
||||||
|
if self.opts.genre_source_field == _('Tags'):
|
||||||
|
all_genre_tags = self.db.all_tags()
|
||||||
|
else:
|
||||||
|
# Validate custom field is usable as a genre source
|
||||||
|
field_md = self.db.metadata_for_field(self.opts.genre_source_field)
|
||||||
|
if not field_md['datatype'] in ['enumeration','text']:
|
||||||
|
all_custom_fields = self.db.custom_field_keys()
|
||||||
|
eligible_custom_fields = []
|
||||||
|
for cf in all_custom_fields:
|
||||||
|
if self.db.metadata_for_field(cf)['datatype'] in ['enumeration','text']:
|
||||||
|
eligible_custom_fields.append(cf)
|
||||||
|
self.opts.log.error("Custom genre_source_field must be either:\n"
|
||||||
|
" 'Comma separated text, like tags, shown in the browser',\n"
|
||||||
|
" 'Text, column shown in the tag browser', or\n"
|
||||||
|
" 'Text, but with a fixed set of permitted values'.")
|
||||||
|
self.opts.log.error("Eligible custom fields: %s" % ', '.join(eligible_custom_fields))
|
||||||
|
raise InvalidGenresSourceFieldException, "invalid custom field specified for genre_source_field"
|
||||||
|
|
||||||
|
all_genre_tags = list(self.db.all_custom(self.db.field_metadata.key_to_label(self.opts.genre_source_field)))
|
||||||
|
|
||||||
|
all_genre_tags.sort()
|
||||||
|
|
||||||
|
for tag in all_genre_tags:
|
||||||
if tag in self.excluded_tags:
|
if tag in self.excluded_tags:
|
||||||
excluded_tags.append(tag)
|
excluded_tags.append(tag)
|
||||||
continue
|
continue
|
||||||
@ -1194,9 +1235,10 @@ class CatalogBuilder(object):
|
|||||||
if genre_tags_dict[key] == normalized:
|
if genre_tags_dict[key] == normalized:
|
||||||
self.opts.log.warn(" %s" % key)
|
self.opts.log.warn(" %s" % key)
|
||||||
if self.opts.verbose:
|
if self.opts.verbose:
|
||||||
self.opts.log.info('%s' % _format_tag_list(genre_tags_dict, header="enabled genre tags in database"))
|
self.opts.log.info('%s' % _format_tag_list(genre_tags_dict, header="enabled genres"))
|
||||||
self.opts.log.info('%s' % _format_tag_list(excluded_tags, header="excluded genre tags"))
|
self.opts.log.info('%s' % _format_tag_list(excluded_tags, header="excluded genres"))
|
||||||
|
|
||||||
|
print("genre_tags_dict: %s" % genre_tags_dict)
|
||||||
return genre_tags_dict
|
return genre_tags_dict
|
||||||
|
|
||||||
def filter_excluded_genres(self, tags, regex):
|
def filter_excluded_genres(self, tags, regex):
|
||||||
@ -1969,7 +2011,7 @@ class CatalogBuilder(object):
|
|||||||
create a separate HTML file. Normalize tags to flatten synonymous tags.
|
create a separate HTML file. Normalize tags to flatten synonymous tags.
|
||||||
|
|
||||||
Inputs:
|
Inputs:
|
||||||
db.all_tags() (list): all database tags
|
self.genre_tags_dict (list): all genre tags
|
||||||
|
|
||||||
Output:
|
Output:
|
||||||
(files): HTML file per genre
|
(files): HTML file per genre
|
||||||
@ -1987,7 +2029,7 @@ class CatalogBuilder(object):
|
|||||||
tag_list = {}
|
tag_list = {}
|
||||||
for book in self.books_by_author:
|
for book in self.books_by_author:
|
||||||
# Scan each book for tag matching friendly_tag
|
# Scan each book for tag matching friendly_tag
|
||||||
if 'tags' in book and friendly_tag in book['tags']:
|
if 'genres' in book and friendly_tag in book['genres']:
|
||||||
this_book = {}
|
this_book = {}
|
||||||
this_book['author'] = book['author']
|
this_book['author'] = book['author']
|
||||||
this_book['title'] = book['title']
|
this_book['title'] = book['title']
|
||||||
@ -2577,18 +2619,18 @@ class CatalogBuilder(object):
|
|||||||
|
|
||||||
# Genres
|
# Genres
|
||||||
genres = ''
|
genres = ''
|
||||||
if 'tags' in book:
|
if 'genres' in book:
|
||||||
_soup = BeautifulSoup('')
|
_soup = BeautifulSoup('')
|
||||||
genresTag = Tag(_soup,'p')
|
genresTag = Tag(_soup,'p')
|
||||||
gtc = 0
|
gtc = 0
|
||||||
for (i, tag) in enumerate(sorted(book.get('tags', []))):
|
for (i, tag) in enumerate(sorted(book.get('genres', []))):
|
||||||
aTag = Tag(_soup,'a')
|
aTag = Tag(_soup,'a')
|
||||||
if self.opts.generate_genres:
|
if self.opts.generate_genres:
|
||||||
aTag['href'] = "Genre_%s.html" % self.genre_tags_dict[tag]
|
aTag['href'] = "Genre_%s.html" % self.genre_tags_dict[tag]
|
||||||
aTag.insert(0,escape(NavigableString(tag)))
|
aTag.insert(0,escape(NavigableString(tag)))
|
||||||
genresTag.insert(gtc, aTag)
|
genresTag.insert(gtc, aTag)
|
||||||
gtc += 1
|
gtc += 1
|
||||||
if i < len(book['tags'])-1:
|
if i < len(book['genres'])-1:
|
||||||
genresTag.insert(gtc, NavigableString(' · '))
|
genresTag.insert(gtc, NavigableString(' · '))
|
||||||
gtc += 1
|
gtc += 1
|
||||||
genres = genresTag.renderContents()
|
genres = genresTag.renderContents()
|
||||||
@ -4382,7 +4424,7 @@ class CatalogBuilder(object):
|
|||||||
""" Return the first friendly_tag matching genre.
|
""" Return the first friendly_tag matching genre.
|
||||||
|
|
||||||
Scan self.genre_tags_dict[] for first friendly_tag matching genre.
|
Scan self.genre_tags_dict[] for first friendly_tag matching genre.
|
||||||
genre_tags_dict[] populated in filter_db_tags().
|
genre_tags_dict[] populated in filter_genre_tags().
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
genre (str): genre to match
|
genre (str): genre to match
|
||||||
|
Loading…
x
Reference in New Issue
Block a user