From 3af0a5f5f2ec05550d2fb87bbce943d79698798b Mon Sep 17 00:00:00 2001 From: maxkoryukov Date: Sat, 6 Feb 2016 23:29:45 +0500 Subject: [PATCH 01/86] Templates for config --- data/interfaces/default/config-templates.html | 14 ++++++++++ data/interfaces/default/config.html | 28 ++++++++++--------- 2 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 data/interfaces/default/config-templates.html diff --git a/data/interfaces/default/config-templates.html b/data/interfaces/default/config-templates.html new file mode 100644 index 000000000..05f504b96 --- /dev/null +++ b/data/interfaces/default/config-templates.html @@ -0,0 +1,14 @@ +<%def name="OptionString(key, label='', tip='', caption='', size=None)"> +<% + txt_size_attr = ' size="%d"' % size if size is not None else ''; +%> +
+ + + %if caption: + ${caption} + %endif +
+ diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 9ba9f2aa8..a5b397625 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -1,4 +1,7 @@ <%inherit file="base.html"/> + +<%namespace file="config-templates.html" import="OptionString"/> + <%! import headphones import string @@ -33,19 +36,18 @@

Settings

Basic -
- - - Use 0.0.0.0 to allow outside connections -
-
- - -
+ + ${OptionString('http_host', + tip='Host to bind web server to', + label='HTTP Host', + caption='Use 0.0.0.0 to allow outside connections', + size=30)} + + ${OptionString('http_port', + tip='Port to bind web server to. Note that ports below 1024 may require root.', + label='HTTP Port', + size=10)} +
% endfor # for block in tab - +
+ +
@@ -120,8 +122,18 @@ embed_id = uiname + '_embed' val = me.uiValue() + ## TODO: this is scrutch. The boundaries are tuned for actual + ## value of option, to preserve its original value... + valr = me.model.get() + mi = me.minvalue + ma = me.maxvalue + if (ma is not None) and (valr > ma): + ma = valr + if (mi is not None) and (valr < mi): + mi = valr + min_attr = '' if me.minvalue is None else ' min="%d"' % me.minvalue - max_attr = '' if me.maxvalue is None else ' size="%d"' % me.maxvalue + max_attr = '' if me.maxvalue is None else ' max="%d"' % me.maxvalue tooltip_attr = '' if me.tooltip is None else ' title="%s"' % me.tooltip %>
@@ -149,6 +161,51 @@ +<%def name="OptionPercent(me, parent=None)"> +<% + uiname = me.uiName() + embed_id = uiname + '_embed' + val = me.uiValue() + + ## TODO: this is scrutch. The boundaries are tuned for actual + ## value of option, to preserve its original value... + valr = me.model.get() + mi = me.minvalue + ma = me.maxvalue + if (ma is not None) and (valr > ma): + ma = valr + if (mi is not None) and (valr < mi): + mi = valr + + min_attr = '' if me.minvalue is None else ' min="%d"' % me.minvalue + max_attr = '' if me.maxvalue is None else ' max="%d"' % me.maxvalue + tooltip_attr = '' if me.tooltip is None else ' title="%s"' % me.tooltip +%> +
+ %if me.label is not None: + + %endif + +
+ % + + %if me.caption is not None: +
${me.caption}
+ %endif +
+
+ % if 0 < len(me): +
+ % for option in me: + ${option.render(me)} + % endfor # for option in block +
+ % endif + + + <%def name="OptionBool(me, parent=None)"> <% uiname = me.uiName() diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 0b6646c0c..8ab638d58 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -242,26 +242,6 @@

Settings

{ $('input[type=radio]').change(function(){ - if ($("#preferred_bitrate").is(":checked")) - { - $("#preferred_bitrate_options").slideDown("fast"); - $("#lossless_only_options").slideUp("fast"); - } - if ($("#preferred_quality0").is(":checked")) - { - $("#preferred_bitrate_options").slideUp("fast"); - $("#lossless_only_options").slideUp("fast"); - } - if ($("#preferred_quality1").is(":checked")) - { - $("#preferred_bitrate_options").slideUp("fast"); - $("#lossless_only_options").slideUp("fast"); - } - if ($("#lossless_only").is(":checked")) - { - $("#lossless_only_options").slideDown("fast"); - $("#preferred_bitrate_options").slideUp("fast"); - } if ($("#nzb_downloader_sabnzbd").is(":checked")) { $("#nzbget_options,#blackhole_options").fadeOut("fast", function() { $("#sabnzbd_options").fadeIn() }); diff --git a/data/interfaces/default/css/style.css b/data/interfaces/default/css/style.css index 978e38da6..f071cdcb0 100644 --- a/data/interfaces/default/css/style.css +++ b/data/interfaces/default/css/style.css @@ -799,6 +799,11 @@ footer { background-color: transparent; margin-bottom: 1em; } +.config .tab .button-panel { + -webkit-column-span: all; + column-span: all; + margin-bottom: 2em; +} .config .block { -webkit-column-break-inside: avoid; page-break-inside: avoid; diff --git a/data/interfaces/default/css/style.less b/data/interfaces/default/css/style.less index 11572e6fb..d4b263fd9 100644 --- a/data/interfaces/default/css/style.less +++ b/data/interfaces/default/css/style.less @@ -592,6 +592,12 @@ footer { background-color:transparent; margin-bottom:1em; } + + .button-panel + { + .columnSpan(all); + margin-bottom:2em; + } } .block diff --git a/headphones/config/__init__.py b/headphones/config/__init__.py index 7c48940f9..583010e35 100644 --- a/headphones/config/__init__.py +++ b/headphones/config/__init__.py @@ -11,6 +11,7 @@ import headphones.config.definitions.webui import headphones.config.definitions.download import headphones.config.definitions.search +import headphones.config.definitions.quality import headphones.config.definitions.internal import headphones.config.definitions.advanced @@ -51,9 +52,11 @@ def __init__(self, config_file): # register options from definition's files: definitions.internal.reg(None, self._registerBlock, self._registerOptions) definitions.webui.reg('webui', self._registerBlock, self._registerOptions) - definitions.download.reg('download', self._registerBlock, self._registerOptions) + definitions.search.reg('search', self._registerBlock, self._registerOptions) + + definitions.quality.reg('quality_processing', self._registerBlock, self._registerOptions) definitions.advanced.reg('advanced', self._registerBlock, self._registerOptions) logger.debug('All options registered. Total options: {0}'.format(len(self._vault))) diff --git a/headphones/config/_datamodel.py b/headphones/config/_datamodel.py index 84b009d83..43a99de14 100644 --- a/headphones/config/_datamodel.py +++ b/headphones/config/_datamodel.py @@ -75,6 +75,9 @@ def get(self): except TypeError: logger.error('The value of option [{0}[{1}] is not well-typed. Will try to use default value.'.format(s, k)) v = t(d) + except ValueError: + logger.error('The value of option [{0}[{1}] is not well-typed. Will try to use default value.'.format(s, k)) + v = t(d) return v diff --git a/headphones/config/_viewmodel.py b/headphones/config/_viewmodel.py index e17880872..36316cccd 100644 --- a/headphones/config/_viewmodel.py +++ b/headphones/config/_viewmodel.py @@ -335,15 +335,21 @@ def uiValue2DataValue(self, value): # override return int(value) +class OptionPercent(OptionNumber): + pass + class OptionBool(OptionBase): - def __init__(self, appkey, section, default=None, label="", caption=None, tooltip=None, options=None): + def __init__(self, appkey, section, default=None, label="", caption=None, tooltip=None, options=None, alignleft=False): super(OptionBool, self).__init__(appkey, section, default, typeconv=boolext, options=options) self.label = label self.caption = caption self.tooltip = tooltip + # TODO : implement in template + self.alignleft = alignleft + def uiValue2DataValue(self, value): # override if value == '1': @@ -352,6 +358,16 @@ def uiValue2DataValue(self, value): return False raise ValueError('Unexpected bool value accepted: {0}'.format(value)) +# TODO : think about inheritance from Block +class OptionSwitch(OptionBool): + """ Option, which includes switcher and suboptions + + This is **enabler**, or **switch** option. Its own value is `bool`, and when it is checked - + the set of suboptions is visible for editing. When its own value is `False` - nothing happens, + no additional settings are visible + """ + pass + class OptionDropdown(OptionBase): class _HtmlOption: def __init__(self, value, label=""): @@ -435,20 +451,6 @@ def uiValue2DataValue(self, value): # override return [value] -# =============================================== -# API-usable options with SUBSTRUCTURE -# =============================================== - -# TODO : think about inheritance from Block -class OptionSwitch(OptionBool): - """ Option, which includes switcher and suboptions - - This is **enabler**, or **switch** option. Its own value is `bool`, and when it is checked - - the set of suboptions is visible for editing. When its own value is `False` - nothing happens, - no additional settings are visible - """ - pass - # =============================================== # PLACEHOLDERS and STATIC templates diff --git a/headphones/config/definitions/download.py b/headphones/config/definitions/download.py index aee5185dc..0096713a4 100644 --- a/headphones/config/definitions/download.py +++ b/headphones/config/definitions/download.py @@ -143,8 +143,8 @@ def reg(tabname, register_block_cb, register_options_cb): tooltip=_('Magnet links'), caption=_('Note: Opening magnet URLs is not suitable for headless/console/terminal servers.
Embed only works for rTorrent.'), items=( - (0, _('Ignore')), # title = Invoke shell command to open magnet URL - (1, _('Open')), # title = Use external service to convert magnet links into torrents. + (0, _('Ignore')), # TODO: title = Invoke shell command to open magnet URL + (1, _('Open')), # TODO: title = Use external service to convert magnet links into torrents. (2, _('Convert')), (3, _('Embed')), ) diff --git a/headphones/config/definitions/quality.py b/headphones/config/definitions/quality.py new file mode 100644 index 000000000..ceebdfefe --- /dev/null +++ b/headphones/config/definitions/quality.py @@ -0,0 +1,206 @@ +from .._viewmodel import Block +from .._viewmodel import OptionString, OptionNumber, OptionSwitch, OptionPassword, OptionBool, OptionPath, OptionUrl, LabelExtension, OptionDropdownSelector, OptionPercent +from .._viewmodel import TemplaterExtension + +""" +Options from "Quality and Post Processing" Tab +""" + +def _(x): + """ required just for marking translatable strings""" + return x + +def reg(tabname, register_block_cb, register_options_cb): + # ======================================================================================= + register_block_cb(tabname, + Block('quality', caption=_("Quality"), options=register_options_cb( + + OptionDropdownSelector('PREFERRED_QUALITY', 'General', 0, typeconv=int, + label=_('Preferred quality'), + tooltip=_('Preferred quality'), + items=( + (0, _('Highest Quality excluding Lossless')), # TODO: title = Snatch the highest quality available, excluding lossless. + (1, _('Highest Quality including Lossless')), # TODO: title = Snatch the highest quality available, including lossless. + (3, _('Lossless Only'), # TODO: title = Snatch only lossless quality. + register_options_cb( + LabelExtension( + cssclasses=None, + fullwidth=True, + label=_(('Reject if target size is not in bitrate range:')) + ), + OptionNumber('LOSSLESS_BITRATE_FROM', 'General', 0, + label=_('from:'), + caption=_('in kbps'), + tooltip=_(('e.g. if album length = 40 mins, from = 350 kbps, then min' + ' target size = 102.5 mb, anything less will be rejected' + )), + minvalue=0, + maxvalue=999999 + ), + OptionNumber('LOSSLESS_BITRATE_TO', 'General', 0, + label=_('to'), + caption=_('in kbps'), + tooltip=_(('e.g. if album length = 40 mins, to = 2000 kbps, then max' + ' target size = 586 mb, anything greater will be rejected' + )), + minvalue=0, + maxvalue=999999 + ), + + ) + ), + (2, _('Preferred Bitrate'), # TODO: title = Prefer certain bitrate range, including lossless fallback. Use this option if you have a lot of wrong snatches. + register_options_cb( + OptionNumber('PREFERRED_BITRATE', 'General', 0, + label=_('Target bitrate'), + caption=_('in kbps'), + minvalue=0, + maxvalue=9999999 + ), + OptionPercent('PREFERRED_BITRATE_LOW_BUFFER', 'General', 0, + label=_('Reject if less than'), + caption=_('(leave blank for no limit)'), + minvalue=0, + maxvalue=100 + ), + OptionPercent('PREFERRED_BITRATE_HIGH_BUFFER', 'General', 0, + label=_('or more than'), + caption=_('(leave blank for no limit)'), + minvalue=0, + maxvalue=100 + ), + LabelExtension( + label=_(('of the target size ')), + fullwidth=False, + ), + OptionBool('PREFERRED_BITRATE_ALLOW_LOSSLESS', 'General', False, + alignleft=True, + label=_('Allow lossless if no good lossy match found'), + ), + OptionBool('DETECT_BITRATE', 'General', False, + alignleft=True, + label=_('Auto-Detect Preferred Bitrate'), + ), + ) + ), + ) + ), + )) + ) + + # ======================================================================================= + register_block_cb(tabname, + Block('search_words', caption=_("Search Words"), options=register_options_cb( + LabelExtension( + cssclasses=None, # ['small'], + fullwidth=True, + label=_(('Separate words with a comma, e.g. "word1,word2,word3".')) + ), + OptionString('IGNORED_WORDS', 'General', '', + label=_('Ignored Words'), + caption=_('Results with any of these words in the title will be filtered out'), + ), + OptionString('PREFERRED_WORDS', 'General', '', + label=_('Preferred Words'), + caption=_(('Results with these words in the title will be preferred over results' + ' without them (search provider names can also be entered)' + )), + ), + OptionString('REQUIRED_WORDS', 'General', '', + label=_('Required Words'), + caption=_(('Results without these words in the title will be filtered out. You can' + ' use OR: "flac OR lossless OR alac, vinyl"' + )), + ), + OptionBool('IGNORE_CLEAN_RELEASES', 'General', False, + alignleft=True, + label=_('Ignore clean/censored releases'), + caption=_(''), + tooltip=_(('Filter out releases that contain the words "clean","edited" or' + ' "censored", as long as those words aren\'t in the search term' + )), + ), + )) + ) + + # ======================================================================================= + register_block_cb(tabname, + Block('post_processing', caption=_("Post-Processing"), options=register_options_cb( + OptionSwitch('MOVE_FILES', 'General', False, + alignleft=True, + label=_('Move downloads to Destination Folder'), + options=register_options_cb( + OptionBool('REPLACE_EXISTING_FOLDERS', 'General', False, + alignleft=True, + label=_('Replace existing folders?'), + ), + OptionBool('KEEP_ORIGINAL_FOLDER', 'General', False, + alignleft=True, + label=_('Keep original folder (i.e. copy)'), + ), + ) + ), + OptionBool('RENAME_FILES', 'General', False, + alignleft=True, + label=_('Rename files'), + ), + OptionBool('CORRECT_METADATA', 'General', False, + alignleft=True, + label=_('Correct metadata'), + ), + OptionBool('CUE_SPLIT', 'General', True, + alignleft=True, + label=_('Split single file albums into multiple tracks'), + tooltip=_(('Use associated .cue sheet to split single file albums into multiple tracks.' + ' Requires shntool with flac or xld cli (OS X) to be installed.' + )), + ), + OptionBool('CLEANUP_FILES', 'General', False, + alignleft=True, + label=_('Delete leftover files'), + caption=_('(.m3u, .nfo, .sfv, .nzb, etc.)'), + ), + OptionBool('KEEP_NFO', 'General', False, + alignleft=True, + label=_('Keep original nfo'), + caption=_('(extension changed to .orig.nfo)'), + ), + OptionSwitch('ADD_ALBUM_ART', 'General', False, + alignleft=True, + label=_('Add album art jpeg to album folder'), + options=register_options_cb( + OptionString('ALBUM_ART_FORMAT', 'General', 'folder', + label=_('Name of album art file'), + caption=_('will be suffixed with ".jpg"'), + tooltip=_(''), + ), + LabelExtension( + cssclasses=['small'], + fullwidth=True, + label=_(('Use $Artist/$artist, $Album/$album, $Year/$year, put optional variables' + ' in square brackets, use single-quote marks to escape square brackets' + ' literally (\'[\', \']\').')) + ), + ) + ), + OptionBool('EMBED_ALBUM_ART', 'General', False, + alignleft=True, + label=_('Embed album art in each file'), + ), + OptionBool('EMBED_LYRICS', 'General', False, + alignleft=True, + label=_('Embed lyrics'), + ), + + OptionPath('DESTINATION_DIR', 'General', '', + label=_('Destination Directory'), + caption=_(('The directory where Headphones will move file to after post processing, e.g.' + ' /Volumes/share/music.' + )), + ), + OptionPath('LOSSLESS_DESTINATION_DIR', 'General', '', + label=_('Lossless Destination Directory'), + caption=_('Optional. Set this if you have a separate directory for lossless music.'), + ), + )) + ) diff --git a/headphones/config/definitions/search.py b/headphones/config/definitions/search.py index e080b27eb..e5f2e7b8e 100644 --- a/headphones/config/definitions/search.py +++ b/headphones/config/definitions/search.py @@ -19,25 +19,14 @@ def reg(tabname, register_block_cb, register_options_cb): # ======================================================================================= # ======================================================================================= # ======================================================================================= - OptionNumber('ADD_ALBUM_ART', 'General', 0, - label=_('zzADD_ALBUM_ARTzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), + OptionString('ADVANCEDENCODER', 'General', '', label=_('zzADVANCEDENCODERzz'), caption=_(''), tooltip=_(''), maxlength=None ), - OptionString('ALBUM_ART_FORMAT', 'General', 'folder', - label=_('zzALBUM_ART_FORMATzz'), - caption=_(''), - tooltip=_(''), - maxlength=None - ), + OptionNumber('ALBUM_COMPLETION_PCT', 'Advanced', 80, label=_('zzALBUM_COMPLETION_PCTzz'), caption=_(''), @@ -100,27 +89,9 @@ def reg(tabname, register_block_cb, register_options_cb): tooltip=_(''), maxlength=None ), - OptionNumber('CLEANUP_FILES', 'General', 0, - label=_('zzCLEANUP_FILESzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionNumber('CORRECT_METADATA', 'General', 0, - label=_('zzCORRECT_METADATAzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionNumber('CUE_SPLIT', 'General', 1, - label=_('zzCUE_SPLITzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), + + + OptionPath('CUE_SPLIT_FLAC_PATH', 'General', '', label=_('zzCUE_SPLIT_FLAC_PATHzz'), caption=_(''), @@ -177,18 +148,7 @@ def reg(tabname, register_block_cb, register_options_cb): minvalue=None, maxvalue=None ), - OptionPath('DESTINATION_DIR', 'General', '', - label=_('zzDESTINATION_DIRzz'), - caption=_(''), - tooltip=_(''), - ), - OptionNumber('DETECT_BITRATE', 'General', 0, - label=_('zzDETECT_BITRATEzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), + OptionNumber('DO_NOT_PROCESS_UNMATCHED', 'General', 0, label=_('zzDO_NOT_PROCESS_UNMATCHEDzz'), caption=_(''), @@ -261,20 +221,8 @@ def reg(tabname, register_block_cb, register_options_cb): tooltip=_(''), maxlength=None ), - OptionNumber('EMBED_ALBUM_ART', 'General', 0, - label=_('zzEMBED_ALBUM_ARTzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionNumber('EMBED_LYRICS', 'General', 0, - label=_('zzEMBED_LYRICSzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), + + OptionString('ENCODER', 'General', 'ffmpeg', label=_('zzENCODERzz'), caption=_(''), @@ -435,19 +383,6 @@ def reg(tabname, register_block_cb, register_options_cb): caption=_(''), tooltip=_(''), ), - OptionString('IGNORED_WORDS', 'General', '', - label=_('zzIGNORED_WORDSzz'), - caption=_(''), - tooltip=_(''), - maxlength=None - ), - OptionNumber('IGNORE_CLEAN_RELEASES', 'General', 0, - label=_('zzIGNORE_CLEAN_RELEASESzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), OptionNumber('INCLUDE_EXTRAS', 'General', 0, label=_('zzINCLUDE_EXTRASzz'), caption=_(''), @@ -480,20 +415,8 @@ def reg(tabname, register_block_cb, register_options_cb): tooltip=_(''), maxlength=None ), - OptionNumber('KEEP_NFO', 'General', 0, - label=_('zzKEEP_NFOzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionNumber('KEEP_ORIGINAL_FOLDER', 'General', 0, - label=_('zzKEEP_ORIGINAL_FOLDERzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), + + OptionString('LASTFM_USERNAME', 'General', '', label=_('zzLASTFM_USERNAMEzz'), caption=_(''), @@ -520,25 +443,7 @@ def reg(tabname, register_block_cb, register_options_cb): tooltip=_(''), maxlength=None ), - OptionNumber('LOSSLESS_BITRATE_FROM', 'General', 0, - label=_('zzLOSSLESS_BITRATE_FROMzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionNumber('LOSSLESS_BITRATE_TO', 'General', 0, - label=_('zzLOSSLESS_BITRATE_TOzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionPath('LOSSLESS_DESTINATION_DIR', 'General', '', - label=_('zzLOSSLESS_DESTINATION_DIRzz'), - caption=_(''), - tooltip=_(''), - ), + OptionNumber('MININOVA', 'Mininova', 0, label=_('zzMININOVAzz'), caption=_(''), @@ -558,13 +463,7 @@ def reg(tabname, register_block_cb, register_options_cb): tooltip=_(''), maxlength=None ), - OptionNumber('MOVE_FILES', 'General', 0, - label=_('zzMOVE_FILESzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), + OptionBool('MPC_ENABLED', 'MPC', False, label=_('zzMPC_ENABLEDzz'), caption=_(''), @@ -789,46 +688,7 @@ def reg(tabname, register_block_cb, register_options_cb): tooltip=_(''), maxlength=None ), - OptionString('PREFERRED_BITRATE', 'General', '', - label=_('zzPREFERRED_BITRATEzz'), - caption=_(''), - tooltip=_(''), - maxlength=None - ), - OptionNumber('PREFERRED_BITRATE_ALLOW_LOSSLESS', 'General', 0, - label=_('zzPREFERRED_BITRATE_ALLOW_LOSSLESSzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionNumber('PREFERRED_BITRATE_HIGH_BUFFER', 'General', 0, - label=_('zzPREFERRED_BITRATE_HIGH_BUFFERzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionNumber('PREFERRED_BITRATE_LOW_BUFFER', 'General', 0, - label=_('zzPREFERRED_BITRATE_LOW_BUFFERzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionNumber('PREFERRED_QUALITY', 'General', 0, - label=_('zzPREFERRED_QUALITYzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionString('PREFERRED_WORDS', 'General', '', - label=_('zzPREFERRED_WORDSzz'), - caption=_(''), - tooltip=_(''), - maxlength=None - ), + OptionNumber('PROWL_ENABLED', 'Prowl', 0, label=_('zzPROWL_ENABLEDzz'), caption=_(''), @@ -935,13 +795,7 @@ def reg(tabname, register_block_cb, register_options_cb): minvalue=None, maxvalue=None ), - OptionNumber('RENAME_FILES', 'General', 0, - label=_('zzRENAME_FILESzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), + OptionBool('RENAME_FROZEN', 'General', True, label=_('zzRENAME_FROZENzz'), caption=_(''), @@ -952,19 +806,7 @@ def reg(tabname, register_block_cb, register_options_cb): caption=_(''), tooltip=_(''), ), - OptionNumber('REPLACE_EXISTING_FOLDERS', 'General', 0, - label=_('zzREPLACE_EXISTING_FOLDERSzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionString('REQUIRED_WORDS', 'General', '', - label=_('zzREQUIRED_WORDSzz'), - caption=_(''), - tooltip=_(''), - maxlength=None - ), + OptionNumber('RUTRACKER', 'Rutracker', 0, label=_('zzRUTRACKERzz'), caption=_(''), From 02cc796110f61a5904fa05532b06956b4ed5944b Mon Sep 17 00:00:00 2001 From: maxkoryukov Date: Thu, 17 Mar 2016 09:27:22 +0500 Subject: [PATCH 45/86] FIX: pep8 and pyflakes --- headphones/config/__init__.py | 12 ++++++------ headphones/config/_datamodel_test.py | 2 +- headphones/config/config_test.py | 7 +++---- headphones/config/definitions/advanced.py | 3 +-- headphones/config/definitions/quality.py | 3 +-- headphones/config/definitions/search.py | 8 +++----- headphones/searcher.py | 2 +- 7 files changed, 16 insertions(+), 21 deletions(-) diff --git a/headphones/config/__init__.py b/headphones/config/__init__.py index 583010e35..8979b3ddc 100644 --- a/headphones/config/__init__.py +++ b/headphones/config/__init__.py @@ -50,14 +50,14 @@ def __init__(self, config_file): self._uiresolver = PostDataParser() # register options from definition's files: - definitions.internal.reg(None, self._registerBlock, self._registerOptions) - definitions.webui.reg('webui', self._registerBlock, self._registerOptions) - definitions.download.reg('download', self._registerBlock, self._registerOptions) + headphones.config.definitions.internal.reg(None, self._registerBlock, self._registerOptions) + headphones.config.definitions.webui.reg('webui', self._registerBlock, self._registerOptions) + headphones.config.definitions.download.reg('download', self._registerBlock, self._registerOptions) - definitions.search.reg('search', self._registerBlock, self._registerOptions) + headphones.config.definitions.search.reg('search', self._registerBlock, self._registerOptions) - definitions.quality.reg('quality_processing', self._registerBlock, self._registerOptions) - definitions.advanced.reg('advanced', self._registerBlock, self._registerOptions) + headphones.config.definitions.quality.reg('quality_processing', self._registerBlock, self._registerOptions) + headphones.config.definitions.advanced.reg('advanced', self._registerBlock, self._registerOptions) logger.debug('All options registered. Total options: {0}'.format(len(self._vault))) diff --git a/headphones/config/_datamodel_test.py b/headphones/config/_datamodel_test.py index 5cb1938e1..c2f68d1c8 100644 --- a/headphones/config/_datamodel_test.py +++ b/headphones/config/_datamodel_test.py @@ -82,7 +82,7 @@ def test_set_section_raise(self, sec1, sec2): p = OptionModel('KEY', sec1, 0, int) - with self.assertRaisesRegexp(ValueError, r'already set') as exc: + with self.assertRaisesRegexp(ValueError, r'already set'): p.section = sec2 @TestArgs( diff --git a/headphones/config/config_test.py b/headphones/config/config_test.py index 66b88c861..378bd1913 100644 --- a/headphones/config/config_test.py +++ b/headphones/config/config_test.py @@ -8,9 +8,9 @@ import headphones.config -logging.basicConfig( stream=sys.stderr ) +logging.basicConfig(stream=sys.stderr) logger = logging.getLogger('headphones.config.TEST') -logger.setLevel( logging.INFO ) +logger.setLevel(logging.INFO) class ConfigApiTest(TestCase): """ Common tests for headphones.Config @@ -98,8 +98,7 @@ def test_write(self): self.assertTrue('email_enabled' in config_tester['Email']) self.assertTrue('email_from' in config_tester['Email']) - - x = self.config_module_mock_patcher.start() + self.config_module_mock_patcher.start() @skip("process_kwargs should be removed") def test_process_kwargs(self): diff --git a/headphones/config/definitions/advanced.py b/headphones/config/definitions/advanced.py index c5b46db0e..a9d899443 100644 --- a/headphones/config/definitions/advanced.py +++ b/headphones/config/definitions/advanced.py @@ -3,8 +3,7 @@ # ======================================================================================= from .._viewmodel import Block -from .._viewmodel import OptionBase, OptionString, OptionNumber, OptionSwitch, OptionPassword, OptionBool, OptionPath, OptionList -from .._viewmodel import TemplaterExtension +from .._viewmodel import OptionString, OptionNumber, OptionSwitch, OptionBool, OptionPath # , OptionPassword def _(x): """ required just for marking translatable strings""" diff --git a/headphones/config/definitions/quality.py b/headphones/config/definitions/quality.py index ceebdfefe..2670235af 100644 --- a/headphones/config/definitions/quality.py +++ b/headphones/config/definitions/quality.py @@ -1,6 +1,5 @@ from .._viewmodel import Block -from .._viewmodel import OptionString, OptionNumber, OptionSwitch, OptionPassword, OptionBool, OptionPath, OptionUrl, LabelExtension, OptionDropdownSelector, OptionPercent -from .._viewmodel import TemplaterExtension +from .._viewmodel import OptionString, OptionNumber, OptionSwitch, OptionBool, OptionPath, LabelExtension, OptionDropdownSelector, OptionPercent """ Options from "Quality and Post Processing" Tab diff --git a/headphones/config/definitions/search.py b/headphones/config/definitions/search.py index e5f2e7b8e..d1e1d58e2 100644 --- a/headphones/config/definitions/search.py +++ b/headphones/config/definitions/search.py @@ -2,15 +2,13 @@ # Options from "Search Providers" Tab # ======================================================================================= -from .._viewmodel import Tab, Tabs, Block -from .._viewmodel import OptionBase, OptionString, OptionNumber, OptionSwitch, OptionPassword, OptionBool, OptionPath, OptionList -from .._viewmodel import TemplaterExtension +from .._viewmodel import Block +from .._viewmodel import OptionString, OptionNumber, OptionPassword, OptionBool, OptionPath, OptionList def _(x): """ required just for marking translatable strings""" return x - def reg(tabname, register_block_cb, register_options_cb): register_block_cb(tabname, @@ -176,7 +174,7 @@ def reg(tabname, register_block_cb, register_options_cb): minvalue=None, maxvalue=None ), - OptionString('EMAIL_SMTP_PASSWORD', 'Email', '', + OptionPassword('EMAIL_SMTP_PASSWORD', 'Email', '', label=_('zzEMAIL_SMTP_PASSWORDzz'), caption=_(''), tooltip=_(''), diff --git a/headphones/searcher.py b/headphones/searcher.py index 8f2befc26..85a490813 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -1433,7 +1433,7 @@ def set_proxy(proxy_url): bitrate_int = headphones.CONFIG.PREFERRED_BITRATE bitrate = str(bitrate_int) - if bitrate_int>=175: + if bitrate_int >= 175: if bitrate_int < 200: bitrate = 'V2' From 5286b9a4ab9e34bcb6791bb6ea281633d07be5fa Mon Sep 17 00:00:00 2001 From: maxkoryukov Date: Thu, 17 Mar 2016 10:28:10 +0500 Subject: [PATCH 46/86] TEST: improved coverage --- headphones/config/_datamodel.py | 5 +- headphones/config/_datamodel_test.py | 72 +++++++++++++++++++++++----- headphones/config/_viewmodel_test.py | 47 ++++++++++++++++++ 3 files changed, 111 insertions(+), 13 deletions(-) create mode 100644 headphones/config/_viewmodel_test.py diff --git a/headphones/config/_datamodel.py b/headphones/config/_datamodel.py index 43a99de14..3207782c0 100644 --- a/headphones/config/_datamodel.py +++ b/headphones/config/_datamodel.py @@ -1,4 +1,5 @@ from headphones import logger +from headphones.exceptions import ConfigError """ List of types, which ConfigOpt could handle internally, without conversions """ _primitives = (int, str, bool, list, float) @@ -55,7 +56,7 @@ def get(self): if self._config_callback is None: msg = 'Option [{0}][{1}] was not binded/registered with config'.format(s, k) logger.error(msg) - raise Exception(msg) + raise ConfigError(msg) config = self._config_callback() @@ -90,7 +91,7 @@ def set(self, value): if self._config_callback is None: msg = 'Option [{0}][{1}] was not binded/registered with config'.format(s, k) logger.error(msg) - raise Exception(msg) + raise ConfigError(msg) config = self._config_callback() diff --git a/headphones/config/_datamodel_test.py b/headphones/config/_datamodel_test.py index c2f68d1c8..873e9710e 100644 --- a/headphones/config/_datamodel_test.py +++ b/headphones/config/_datamodel_test.py @@ -1,13 +1,36 @@ from unittestcompat import TestCase, TestArgs +from headphones.exceptions import ConfigError from headphones.config.typeconv import boolext, path from _datamodel import OptionModel class OptionModelTest(TestCase): + # --------------------------------------------------------------- + # __init__ + # --------------------------------------------------------------- + def test_init(self): + """ OptionModel: __init__ """ + p = OptionModel('KEY', 'GeneralSection', 0, int) + st = {} + p.bindToConfig(lambda: st) + + self.assertIsNotNone(p) + self.assertIsInstance(p, OptionModel) + # --------------------------------------------------------------- # get # --------------------------------------------------------------- + @TestArgs( + (1, int), + ) + def test_get_without_binding_raises(self, deflt, tp): + """ OptionModel:get without bindToConfig SHOULD raise """ + p = OptionModel('ANYKEY', 'AnySection', deflt, tp) + + with self.assertRaisesRegexp(ConfigError, r'ANYKEY.*not\sbinded'): + act = p.get() + @TestArgs( (1, int), (100, int), @@ -16,15 +39,12 @@ class OptionModelTest(TestCase): ('asdf', str), (True, bool), ) - def test_default_get(self, deflt, tp): + def test_get_default(self, deflt, tp): """ OptionModel: test get of default value """ p = OptionModel('KEY', 'GeneralSection', deflt, tp) st = {} p.bindToConfig(lambda: st) - self.assertIsNotNone(p) - self.assertIsInstance(p, OptionModel) - act = p.get() self.assertIsInstance(act, tp) self.assertEqual(act, deflt) @@ -41,19 +61,49 @@ def test_default_get(self, deflt, tp): ("hello", str, "hello", str), ) - def test_default_get_with_conv(self, deflt, conv, expval, exptp): + def test_get_default_with_conv(self, deflt, conv, expval, exptp): """ OptionModel: test get of default value vith type conversion """ p = OptionModel('KEY', 'GeneralSection', deflt, conv) st = {} p.bindToConfig(lambda: st) - self.assertIsNotNone(p) - self.assertIsInstance(p, OptionModel) - act = p.get() self.assertIsInstance(act, exptp) self.assertEqual(act, expval) + @TestArgs( + (1023, int, 'not an int'), + ([1023], list, 1), + ) + def test_get_wrong_type_falls_back_to_default(self, deflt, conv, confval): + """ OptionModel: get with wrong type in config SHOULD fallback to default """ + sec = 'AnySection' + key = 'ANYKEY' + inikey = key.lower() + p = OptionModel(key, sec, deflt, conv) + st = { + sec : { + inikey : confval + } + } + p.bindToConfig(lambda: st) + + act = p.get() + self.assertEqual(act, deflt) + + # --------------------------------------------------------------- + # set + # --------------------------------------------------------------- + @TestArgs( + (1, int), + ) + def test_set_without_binding_raises(self, deflt, tp): + """ OptionModel:set without bindToConfig SHOULD raise """ + p = OptionModel('ANYKEY', 'AnySection', deflt, tp) + + with self.assertRaisesRegexp(ConfigError, r'ANYKEY.*not\sbinded'): + act = p.set(deflt) + # --------------------------------------------------------------- # section # --------------------------------------------------------------- @@ -62,7 +112,7 @@ def test_default_get_with_conv(self, deflt, conv, expval, exptp): ("123"), ("GEnerAL"), ) - def test_set_section_none_to_any(self, new_sec_name): + def test_section_set_none_to_any(self, new_sec_name): """ OptionModel:section:set set value from None to any """ section_name = None @@ -77,7 +127,7 @@ def test_set_section_none_to_any(self, new_sec_name): ("123", None), ("GEnerAL", "Gen"), ) - def test_set_section_raise(self, sec1, sec2): + def test_section_set_raise(self, sec1, sec2): """ OptionModel:section:set change section should raise """ p = OptionModel('KEY', sec1, 0, int) @@ -91,7 +141,7 @@ def test_set_section_raise(self, sec1, sec2): ("GEnerAL", "General"), ("Web UI", "web ui"), ) - def test_set_section_set(self, sec1, sec2): + def test_section_set(self, sec1, sec2): """ OptionModel:section:set change case of value should work """ p = OptionModel('KEY', sec1, 0, int) diff --git a/headphones/config/_viewmodel_test.py b/headphones/config/_viewmodel_test.py new file mode 100644 index 000000000..5763a748c --- /dev/null +++ b/headphones/config/_viewmodel_test.py @@ -0,0 +1,47 @@ +from unittestcompat import TestCase, TestArgs +from _viewmodel import OptionBase, OptionInternal, OptionDeprecated + +class OptionInternalTest(TestCase): + + # --------------------------------------------------------------- + # __init__ + # --------------------------------------------------------------- + def test_init(self): + """ OptionInternal: __init__ """ + p = OptionInternal('KEY', 'GeneralSection', 0, typeconv=int) + + self.assertIsNotNone(p) + self.assertIsInstance(p, OptionBase) + self.assertIsInstance(p, OptionInternal) + + # --------------------------------------------------------------- + # render + # --------------------------------------------------------------- + def test_render(self): + """ OptionInternal: render """ + p = OptionInternal('KEY', 'GeneralSection', 0, typeconv=int) + + self.assertEqual(p.render(), "") + + +class OptionDeprecatedTest(TestCase): + + # --------------------------------------------------------------- + # __init__ + # --------------------------------------------------------------- + def test_init(self): + """ OptionDeprecated: __init__ """ + p = OptionDeprecated('KEY', 'GeneralSection', 0, typeconv=int) + + self.assertIsNotNone(p) + self.assertIsInstance(p, OptionBase) + self.assertIsInstance(p, OptionDeprecated) + + # --------------------------------------------------------------- + # render + # --------------------------------------------------------------- + def test_render(self): + """ OptionDeprecated: render """ + p = OptionDeprecated('KEY', 'GeneralSection', 0, typeconv=int) + + self.assertEqual(p.render(), "") \ No newline at end of file From 5b588bb6180bcd666f3e820cb271c7ce14a992db Mon Sep 17 00:00:00 2001 From: maxkoryukov Date: Thu, 17 Mar 2016 23:20:01 +0500 Subject: [PATCH 47/86] UPG: the half of Advanced tab * OptionCombobox for string-options with list of variants (implemented as ) see #12 * a little bit more informational logging --- data/interfaces/default/config-templates.html | 54 +++++- headphones/config/__init__.py | 6 +- headphones/config/_viewmodel.py | 21 +++ headphones/config/definitions/advanced.py | 176 +++++++++++++++++- headphones/config/definitions/internal.py | 1 + headphones/config/definitions/search.py | 121 ------------ 6 files changed, 246 insertions(+), 133 deletions(-) diff --git a/data/interfaces/default/config-templates.html b/data/interfaces/default/config-templates.html index c9e8dce1a..5119d2257 100644 --- a/data/interfaces/default/config-templates.html +++ b/data/interfaces/default/config-templates.html @@ -42,7 +42,6 @@ <%def name="OptionString(me, parent=None)"> <% uiname = me.uiName() - embed_id = uiname + '_embed' val = me.uiValue() size_attr = '' if me.maxlength is None else ' maxlength="%d"' % me.maxlength @@ -69,7 +68,52 @@
% if 0 < len(me): -
+
+ % for option in me: + ${option.render(parent=me)} + % endfor # for option in block +
+ % endif + + + +<%def name="OptionCombobox(me, parent=None)"> +<% + uiname = me.uiName() + datalist_id = uiname + '_hp_datalist' + val = me.uiValue() + + size_attr = '' if me.maxlength is None else ' maxlength="%d"' % me.maxlength + tooltip_attr = '' if me.tooltip is None else ' title="%s"' % me.tooltip +%> +
+ %if me.label is not None: + + %endif + +
+ %if me.readonly: + ${val} + %else: + + + + % for di in me.items: + + % endfor + + %endif + + %if me.caption is not None: +
${me.caption}
+ %endif +
+
+ + % if 0 < len(me): +
% for option in me: ${option.render(parent=me)} % endfor # for option in block @@ -81,7 +125,6 @@ <%def name="OptionPassword(me, parent=None)"> <% uiname = me.uiName() - embed_id = uiname + '_embed' val = me.uiValue() size_attr = '' if me.maxlength is None else ' maxlength="%d"' % me.maxlength @@ -107,7 +150,7 @@
% if 0 < len(me): -
+
% for option in me: ${option.render(me)} % endfor # for option in block @@ -119,7 +162,6 @@ <%def name="OptionNumber(me, parent=None)"> <% uiname = me.uiName() - embed_id = uiname + '_embed' val = me.uiValue() ## TODO: this is scrutch. The boundaries are tuned for actual @@ -152,7 +194,7 @@
% if 0 < len(me): -
+
% for option in me: ${option.render(me)} % endfor # for option in block diff --git a/headphones/config/__init__.py b/headphones/config/__init__.py index 8979b3ddc..0b490a045 100644 --- a/headphones/config/__init__.py +++ b/headphones/config/__init__.py @@ -59,7 +59,7 @@ def __init__(self, config_file): headphones.config.definitions.quality.reg('quality_processing', self._registerBlock, self._registerOptions) headphones.config.definitions.advanced.reg('advanced', self._registerBlock, self._registerOptions) - logger.debug('All options registered. Total options: {0}'.format(len(self._vault))) + logger.info('All options registered. Total options: {0}'.format(len(self._vault))) # forced reinit of the config file, by default HP will not save # values of non-touched options @@ -167,7 +167,7 @@ def accept(self, uidata): def write(self): """ Make a copy of the stored config and write it to the configured file """ - headphones.logger.info("Writing configuration to file") + headphones.logger.debug("Writing configuration to file") try: self._config.write() headphones.logger.info("Writing configuration to file: DONE") @@ -181,7 +181,7 @@ def _reInitConfigFile(self): of all options to config file """ # force copying all options to config: - headphones.logger.info("Reinit config file") + headphones.logger.debug("Reinit config file") for k in self._vault.keys(): optmod = self._vault[k] optmod.set(optmod.get()) diff --git a/headphones/config/_viewmodel.py b/headphones/config/_viewmodel.py index 36316cccd..7fe75f177 100644 --- a/headphones/config/_viewmodel.py +++ b/headphones/config/_viewmodel.py @@ -294,6 +294,27 @@ def __init__(self, appkey, section, default=None, label="", caption=None, toolti self.maxlength = maxlength +class OptionCombobox(OptionString): + """ Textbox with list of available variants """ + def __init__(self, appkey, section, default=None, label="", caption=None, tooltip=None, options=None, maxlength=None, items=None): + super(OptionCombobox, self).__init__( + appkey, + section, + default, + options=options, + label=label, + caption=caption, + tooltip=tooltip, + maxlength=maxlength) + + self.items = [] + if items: + for i in items: + if isinstance(i, basestring): + self.items.append(i) + else: + self.items.append(str(i)) + class OptionPath(OptionBase): def __init__(self, appkey, section, default=None, label="", caption=None, tooltip=None, options=None, maxlength=None): diff --git a/headphones/config/definitions/advanced.py b/headphones/config/definitions/advanced.py index a9d899443..12be67f20 100644 --- a/headphones/config/definitions/advanced.py +++ b/headphones/config/definitions/advanced.py @@ -3,7 +3,7 @@ # ======================================================================================= from .._viewmodel import Block -from .._viewmodel import OptionString, OptionNumber, OptionSwitch, OptionBool, OptionPath # , OptionPassword +from .._viewmodel import OptionString, OptionNumber, OptionSwitch, OptionBool, OptionPath, LabelExtension, OptionDropdownSelector, OptionDropdown, OptionCombobox # , OptionPassword def _(x): """ required just for marking translatable strings""" @@ -11,6 +11,176 @@ def _(x): def reg(tabname, register_block_cb, register_options_cb): + # ======================================================================================= + register_block_cb(tabname, + Block('renaming_options', caption=_("Renaming options"), options=register_options_cb( + + OptionString('FOLDER_FORMAT', 'General', 'Artist/Album [Year]', + label=_('Folder Format'), + caption=_(('Use: $Artist/$artist, $SortArtist/$sortartist, $Album/$album, $Year/$year,' + ' $Type/$type (release type) and $First/$first (first letter in artist name),' + ' $OriginalFolder/$originalfolder (downloaded directory name). Put optional' + ' variables in square brackets, use single-quote marks to escape square brackets' + ' literally (\'[\', \']\').
E.g.: $Type/$First/$artist/$album \'[\'$year\']\'' + ' = Album/G/girl talk/all day [2010]' + )), + ), + OptionString('FILE_FORMAT', 'General', 'Track Artist - Album [Year] - Title', + label=_('File Format'), + caption=_(('Use: $Disc/$disc (disc #), $Track/$track (track #), $Title/$title,' + ' $Artist/$artist, $Album/$album and $Year/$year. Put optional variables in' + ' square brackets, use single-quote marks to escape square brackets literally' + ' (\'[\', \']\').' + )), + ), + OptionBool('FILE_UNDERSCORES', 'General', False, + label=_('Use underscores instead of spaces'), + ), + )) + ) + + # ======================================================================================= + register_block_cb(tabname, + Block('reencoding_options', caption=_("Re-Encoding Options"), options=register_options_cb( + LabelExtension( + label=_((' Note: this option requires the lame,' + ' ffmpeg or xld encoder' + )), + cssclasses=['heading'], + fullwidth=True, + ), + OptionSwitch('MUSIC_ENCODER', 'General', False, + label=_('Re-encode downloads during postprocessing'), + options=register_options_cb( + OptionBool('ENCODERLOSSLESS', 'General', True, + label=_('Only re-encode lossless files (.flac)'), + ), + OptionBool('DELETE_LOSSLESS_FILES', 'General', True, + label=_('Delete original lossless files after encoding'), + ), + ), + ), + + OptionSwitch('ENCODER_MULTICORE', 'General', False, + label=_('Enable multi-core'), + caption=_(''), + tooltip=_(''), + options=register_options_cb( + OptionNumber('ENCODER_MULTICORE_COUNT', 'General', 0, + label=_('Multi-core count'), + caption=_('Set equal to the number of cores, or 0 for auto'), + minvalue=0, + maxvalue=None + ), + ), + ), + + OptionDropdownSelector('ENCODER', 'General', 'ffmpeg', typeconv=str, + label=_('Encoder'), + tooltip=_(('Name of encoder to use. Lame, FFmpeg and libav are available for most Linux' + ' distributions. On Ubuntu, libav replaces FFmpeg. xld is OS X-only.' + )), + items=( + ('lame', _('lame'), register_options_cb( + )), + ('ffmpeg', _('ffmpeg'), register_options_cb( + )), + ('libav', _('libav'), register_options_cb( + )), + ('xld', _('xld'), register_options_cb( + )), + ) + ) + )) + ) + + # TODO : move this block + register_block_cb(tabname, + Block('audio_sub_block', caption=_("Audio Properties"), options=register_options_cb( + # FIX : I want add CUSTOM values here!!!! + OptionCombobox('ENCODEROUTPUTFORMAT', 'General', 'mp3', #typeconv=str, + label=_('Format'), + caption=_('Use one of "mp3", "ogg", "m4a", or any custom format'), + items=( + 'mp3', + 'ogg', + 'm4a', + ) + ), + OptionDropdownSelector('ENCODERVBRCBR', 'General', 'cbr', typeconv=str, + label=_('VBR/CBR'), + items=( + ('cbr', _('cbr'), register_options_cb( + OptionDropdown('BITRATE', 'General', 192, typeconv=int, + label=_('Bitrate'), + items=( + (64, _('64')), + (128, _('128')), + (192, _('192')), + (256, _('256')), + (320, _('320')), + ) + ), + )), + ('vbr', _('vbr'), register_options_cb( + OptionDropdown('ENCODERQUALITY', 'General', 2, typeconv=int, + label=_('Quality'), + items=( + (0, _('0')), + (1, _('1')), + (2, _('2')), + (3, _('3')), + (4, _('4')), + (5, _('5')), + (6, _('6')), + (7, _('7')), + (8, _('8')), + (9, _('9')), + ) + ), + )), + ) + ), + OptionDropdown('SAMPLINGFREQUENCY', 'General', 44100, typeconv=int, + label=_('Sampling'), + items=( + (44100, _('44.1 kHz')), + (48000, _('48.0 kHz')), + ) + ), + + # TODO : use special separator!!!!!! + LabelExtension( + label='
', + fullwidth=True, + ), + + OptionString('ADVANCEDENCODER', 'General', '', + label=_('Arguments'), + caption=_('Ignores all of the above options'), + tooltip=_('Advanced Encoding Options'), + ), + )) + ) + # TODO : move this block too [ XLD ] + register_block_cb(tabname, + Block('audio_sub_block_2', caption=_("Audio Properties XLD"), options=register_options_cb( + OptionString('XLDPROFILE', 'General', '', + label=_('XLD Profile'), + ) + )) + ) + + # ======================================================================================= + # REAL Advanced block + register_block_cb(tabname, + Block('advanced_encoding_options', caption=_("Advanced Encoding Options"), options=register_options_cb( + OptionPath('ENCODER_PATH', 'General', '', + label=_('Path to Encoder'), + ), + )) + ) + # ======================================================================================= register_block_cb(tabname, Block('git', caption=_("GitHub"), options=register_options_cb( @@ -63,7 +233,7 @@ def reg(tabname, register_block_cb, register_options_cb): tooltip=_('Change file permissions during post-processing'), options=register_options_cb( - # Convert to OptionFilePermission + # TODO: Convert to OptionFilePermission OptionString('FILE_PERMISSIONS', 'General', '0644', label=_('File Permissions'), tooltip=_('Desired file permissions'), @@ -76,7 +246,7 @@ def reg(tabname, register_block_cb, register_options_cb): tooltip=_('Change folder permissions during post-processing'), options=register_options_cb( - # Convert to OptionFilePermission + # TODO: Convert to OptionFilePermission OptionString('FOLDER_PERMISSIONS', 'General', '0755', label=_('Folder Permissions'), tooltip=_('Desired folder permissions'), diff --git a/headphones/config/definitions/internal.py b/headphones/config/definitions/internal.py index 6ec8c1a99..dea760bcf 100644 --- a/headphones/config/definitions/internal.py +++ b/headphones/config/definitions/internal.py @@ -35,4 +35,5 @@ def reg(tabname, register_block_cb, register_options_cb): register_options_cb( OptionDeprecated('BLACKHOLE', 'General', False, typeconv=boolext), OptionDeprecated('OPEN_MAGNET_LINKS', 'General', False, typeconv=boolext), + OptionDeprecated('ENCODERFOLDER', 'General', '', typeconv=path), ) diff --git a/headphones/config/definitions/search.py b/headphones/config/definitions/search.py index d1e1d58e2..30a28d13c 100644 --- a/headphones/config/definitions/search.py +++ b/headphones/config/definitions/search.py @@ -18,13 +18,6 @@ def reg(tabname, register_block_cb, register_options_cb): # ======================================================================================= # ======================================================================================= - OptionString('ADVANCEDENCODER', 'General', '', - label=_('zzADVANCEDENCODERzz'), - caption=_(''), - tooltip=_(''), - maxlength=None - ), - OptionNumber('ALBUM_COMPLETION_PCT', 'Advanced', 80, label=_('zzALBUM_COMPLETION_PCTzz'), caption=_(''), @@ -60,13 +53,6 @@ def reg(tabname, register_block_cb, register_options_cb): minvalue=None, maxvalue=None ), - OptionNumber('BITRATE', 'General', 192, - label=_('zzBITRATEzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), OptionNumber('BOXCAR_ENABLED', 'Boxcar', 0, label=_('zzBOXCAR_ENABLEDzz'), caption=_(''), @@ -139,14 +125,6 @@ def reg(tabname, register_block_cb, register_options_cb): tooltip=_(''), maxlength=None ), - OptionNumber('DELETE_LOSSLESS_FILES', 'General', 1, - label=_('zzDELETE_LOSSLESS_FILESzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionNumber('DO_NOT_PROCESS_UNMATCHED', 'General', 0, label=_('zzDO_NOT_PROCESS_UNMATCHEDzz'), caption=_(''), @@ -219,66 +197,6 @@ def reg(tabname, register_block_cb, register_options_cb): tooltip=_(''), maxlength=None ), - - - OptionString('ENCODER', 'General', 'ffmpeg', - label=_('zzENCODERzz'), - caption=_(''), - tooltip=_(''), - maxlength=None - ), - OptionPath('ENCODERFOLDER', 'General', '', - label=_('zzENCODERFOLDERzz'), - caption=_(''), - tooltip=_(''), - ), - OptionNumber('ENCODERLOSSLESS', 'General', 1, - label=_('zzENCODERLOSSLESSzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionString('ENCODEROUTPUTFORMAT', 'General', 'mp3', - label=_('zzENCODEROUTPUTFORMATzz'), - caption=_(''), - tooltip=_(''), - maxlength=None - ), - OptionNumber('ENCODERQUALITY', 'General', 2, - label=_('zzENCODERQUALITYzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionString('ENCODERVBRCBR', 'General', 'cbr', - label=_('zzENCODERVBRCBRzz'), - caption=_(''), - tooltip=_(''), - maxlength=None - ), - OptionNumber('ENCODER_MULTICORE', 'General', 0, - label=_('zzENCODER_MULTICOREzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - - # STRANGE 0 CHECK :( - OptionNumber('ENCODER_MULTICORE_COUNT', 'General', 0, - label=_('zzENCODER_MULTICORE_COUNTzz'), - caption=_(''), - tooltip=_(''), - minvalue=0, - maxvalue=None - ), - OptionPath('ENCODER_PATH', 'General', '', - label=_('zzENCODER_PATHzz'), - caption=_(''), - tooltip=_(''), - ), OptionString('EXTRAS', 'General', '', label=_('zzEXTRASzz'), caption=_(''), @@ -295,25 +213,6 @@ def reg(tabname, register_block_cb, register_options_cb): caption=_(''), tooltip=_(''), ), - OptionString('FILE_FORMAT', 'General', 'Track Artist - Album [Year] - Title', - label=_('zzFILE_FORMATzz'), - caption=_(''), - tooltip=_(''), - maxlength=None - ), - OptionNumber('FILE_UNDERSCORES', 'General', 0, - label=_('zzFILE_UNDERSCORESzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), - OptionString('FOLDER_FORMAT', 'General', 'Artist/Album [Year]', - label=_('zzFOLDER_FORMATzz'), - caption=_(''), - tooltip=_(''), - maxlength=None - ), OptionNumber('FREEZE_DB', 'General', 0, label=_('zzFREEZE_DBzz'), caption=_(''), @@ -472,13 +371,6 @@ def reg(tabname, register_block_cb, register_options_cb): caption=_(''), tooltip=_(''), ), - OptionNumber('MUSIC_ENCODER', 'General', 0, - label=_('zzMUSIC_ENCODERzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), OptionNumber('NEWZNAB', 'Newznab', 0, label=_('zzNEWZNABzz'), caption=_(''), @@ -830,13 +722,6 @@ def reg(tabname, register_block_cb, register_options_cb): tooltip=_(''), maxlength=None ), - OptionNumber('SAMPLINGFREQUENCY', 'General', 44100, - label=_('zzSAMPLINGFREQUENCYzz'), - caption=_(''), - tooltip=_(''), - minvalue=None, - maxvalue=None - ), OptionString('SONGKICK_APIKEY', 'Songkick', 'nd1We7dFW2RqxPw8', label=_('zzSONGKICK_APIKEYzz'), caption=_(''), @@ -1074,12 +959,6 @@ def reg(tabname, register_block_cb, register_options_cb): tooltip=_(''), maxlength=None ), - OptionString('XLDPROFILE', 'General', '', - label=_('zzXLDPROFILEzz'), - caption=_(''), - tooltip=_(''), - maxlength=None - ), # ======================================================================================= # ======================================================================================= From bb8e62ea9a3fd597cd2a9fb93cdc818073c042ad Mon Sep 17 00:00:00 2001 From: maxkoryukov Date: Fri, 18 Mar 2016 04:30:21 +0500 Subject: [PATCH 48/86] FIX: EXTRA-checkbox beaten. Now it is common option, without superdupermagic on every config page. (see #12) * new options types: `OptionCheckboxList` and `OptionCheckboxListExtrasCrutch` * More poserfull way to create items of OptionDropdownSelector and OptionDropdown (copied from OptionCheckboxList) * small fixes in argument-names * another one step in LESS improvements --- data/interfaces/default/config-templates.html | 89 ++++++-- data/interfaces/default/css/style.css | 59 +++--- data/interfaces/default/css/style.less | 133 ++++++------ headphones/config/_datamodel.py | 8 +- headphones/config/_datamodel_test.py | 9 +- headphones/config/_viewmodel.py | 191 +++++++++++++++--- headphones/config/_viewmodel_test.py | 12 +- headphones/config/definitions/advanced.py | 98 ++++++--- headphones/config/definitions/download.py | 10 +- headphones/config/definitions/internal.py | 22 +- headphones/config/definitions/quality.py | 2 +- headphones/config/definitions/search.py | 13 -- 12 files changed, 423 insertions(+), 223 deletions(-) diff --git a/data/interfaces/default/config-templates.html b/data/interfaces/default/config-templates.html index 5119d2257..94fa3ca1b 100644 --- a/data/interfaces/default/config-templates.html +++ b/data/interfaces/default/config-templates.html @@ -47,9 +47,9 @@ size_attr = '' if me.maxlength is None else ' maxlength="%d"' % me.maxlength tooltip_attr = '' if me.tooltip is None else ' title="%s"' % me.tooltip %> -
+
%if me.label is not None: -