diff --git a/.gitignore b/.gitignore index 29da270b8..334dedd4d 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,8 @@ version.lock logs/* cache/* +# mako generated templates: +/auto/ # HTTPS Cert/Key *.crt @@ -70,4 +72,4 @@ _ReSharper*/ .vscode # Python virtual env -venv \ No newline at end of file +venv diff --git a/.travis.yml b/.travis.yml index 41ad6f255..7889835df 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,13 +14,38 @@ python: - "2.6" - "2.7" +env: + global: + # Soucelabs + - secure: "QJqlXGFBbMBCIJYJky3LXXvuk132O9cUqxAw4WI5kmNgbE8oRdyN9jYPbRKJ2XmWHX2mvDuo7M45IJyvsukYvYJO8jgFGygY5GVhcYt6Id3HMapmZfU+VYvNXKmNJJGSTzU6omYO+9qy7QOL2Kyvzt6+/hmloymPHr3poPR0qpxLobNR7izIrZmkuJ5QRINE+ryScbVgLMsMC+ktiXI79Q9/k7BW0gYOTo60XiAqOk1edHYK7SkJ+OHYR8JQA90u5JwOkwWz3cjcqeJJjp8okW0cjV2s5KEumfxEpj/wXAho6lh6dQfeE7ii2DtrUwWs8qYAVVb9dXfV4bvMTTOqGqPi7HlouU10Ai2OU5tWMmqhWJ99nyHL9jeCxxAs2RnwlLL5X4h2bzabpvouMr5v64Q1Lfz0GpX4fLQD+MUsusNI1GTos1t9VkF/yvVn/Ka58a3+pOd5Do/yKPcD7Cfho4pAkvdtqKpNfbocJtxVky4ECbX26fFVhTPKU6/YF94MmpIXzskMMfzRvOx/PApZtyA7siYCgT3LjDrRWgqfKOd22D+e2rI3G1ufAe1QZx4MQD+baLYPrILgmBWJ62ouBTrw368h7PBOCCXWpjMlIED42fw1kz8qOMzAp6bpB4Zk81NduKTohWo+vX6RRWbv4ESBWTpvmpuoXeBoLPs+o0A=" + - secure: "e0zp9XZbgY80LKZINTBLnYjdUON7zkC96vK7PyJXRnmrdyrBkF/kmp+wHAu8I/9frrG0oVVL3Okev5XdTzbCG3HQ52Ys63oa/bjqyQ15GBKD0bogvUQ5+f2TFTbOe+xvMKTA1NLnMxBnX9Ta7zPGh1K36zZtGURQY+l/XqLbOf0MT5ExA1JMwXPSxcgHQwoR5T2KqX65gOExdA/CF11EEKcWc3iIqRitFQkuGSeXKYrwH7IaZQqOc+PkX2rwykWse2dFHlm19oJDxS2uaddtfXu6NOVDDNiw3bN7xsd0bSzKb5N0D1tUZcAIDtv3mOqsSv9Ffs1JgcoHKkDnEsC/0I5qLRmld8cpCwONnp6Kx+RdCZs5h6NxdaXCRT51oB/OriRiDZGRrygJOXMG8Gepkk6ydhrE5UCwKaOqehHRL4m3koWbwTXVKpVGjvimzCXE31/+R5G+DKEc93q54IFxAPHEAcdMFbYfYO12R/cT189hY16SXG6KyNl8ZTMImuEbEACkjHmzY2i8BZoITPi6U7SXHoFqcL7Z7QVO2Tr1a2ZgSRyEHrVqhupu+XG7vSKhG9GCa43R7Om1i6FebzvX2IxIEJiYAdBV7ssR6F+pgBAKe1dXZetCC8bToGSqc4WXCkq5DAJmfEmAy204D7/Y2IuxzyBfGbeFOBvF7k50nuI=" + - SOUCELABS=false # set "true" to enable Soucelabs integration + install: - pip install -r requirements-dev.txt +before_script: + # Creatind dumb config, which allows to run HP, listening on all interfaces + - printf '[General]\nhttp_host=0.0.0.0\n' > config.ini + - python Headphones.py -v -d + - sleep 7 # give Web server some time to bind to sockets, etc + script: + # required for running browser + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" + - sleep 3 # give xvfb some time to start + - pep8 headphones - pyflakes headphones - nosetests after_success: - if [[ $TRAVIS_PYTHON_VERSION == "2.7" ]]; then coveralls; fi + +after_failure: + - cat ./logs/headphones.log + - cat /home/travis/sauce-connect.log + +addons: + sauce_connect: true diff --git a/Headphones.py b/Headphones.py index dc956a43e..229ab28bc 100755 --- a/Headphones.py +++ b/Headphones.py @@ -186,6 +186,7 @@ def main(): 'http_host': headphones.CONFIG.HTTP_HOST, 'http_root': headphones.CONFIG.HTTP_ROOT, 'http_proxy': headphones.CONFIG.HTTP_PROXY, + 'http_proxy_header_host': headphones.CONFIG.HTTP_PROXY_HEADER_HOST, 'enable_https': headphones.CONFIG.ENABLE_HTTPS, 'https_cert': headphones.CONFIG.HTTPS_CERT, 'https_key': headphones.CONFIG.HTTPS_KEY, diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index f95be9784..096558a86 100644 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -71,7 +71,7 @@ -
+
${next.headerIncludes()}
diff --git a/data/interfaces/default/config-meta-ui.html b/data/interfaces/default/config-meta-ui.html new file mode 100644 index 000000000..080d9a470 --- /dev/null +++ b/data/interfaces/default/config-meta-ui.html @@ -0,0 +1,32 @@ +<%inherit file="base.html"/> + +<%! + import headphones + import string +%> + +<%def name="headerIncludes()"> + + + +<%def name="body()"> +
+

Meta UI Settings

+
+ +
+ + +
+ + +<%def name="javascriptIncludes()"> + + diff --git a/data/interfaces/default/config-templates.html b/data/interfaces/default/config-templates.html new file mode 100644 index 000000000..bcd888e5b --- /dev/null +++ b/data/interfaces/default/config-templates.html @@ -0,0 +1,705 @@ +## +## Here defined all templates, required for CONFIG page of the app +## + +## =============================================== +## TEMPLATES for TABS +## =============================================== + +<%def name="Tab(me, parent=None)"> + ## ID for TAB here is required, it is used to choose appropriate tab + ## usign url in address bar (#bookmarks) + ## Do not delete ID! +
+ % if me.message: +
+ ${me.message} +
+ % endif + + % for item in me: + ${item.render(parent=me)} + % endfor + +
+ +
+
+ + +## =============================================== +## TEMPLATES for common set of OPTIONS +## =============================================== + +<%def name="OptionString(me, parent=None)"> +<% + uiname = me.uiName() + 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.caption is not None: +
${me.caption}
+ %endif +
+
+ + % 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 not me.readonly: + + % 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 +
+ % endif + + + +<%def name="OptionPassword(me, parent=None)"> +<% + uiname = me.uiName() + 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.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="OptionNumber(me, parent=None)"> +<% + uiname = me.uiName() + val = me.uiValue() + + 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="OptionPercent(me, parent=None)"> +<% + uiname = me.uiName() + embed_id = uiname + '_embed' + val = me.uiValue() + + 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)"> +<% + ## TODO: try to simplify this template + ## SORRY, there is enouth complicated layout in this template. The `alignleft` make it dirty.... + ## When checkbox is left aligned - input is inside of the label. In other case - outside... + + uiname = me.uiName() + checkbox_id = uiname + embed_id = uiname + '_embed' + val = me.uiValue() + + need_label = me.label is not None + tooltip_attr = '' if me.tooltip is None else ' title="%s"' % me.tooltip + checked_attr = 'checked' if val else '' + hidden_value = '1' if val else '0' +%> +
+ + ## ENTIRE LABEL or OPEN LABEL (when left aligned) + %if need_label: + + % if me.alignleft: + + % endif + %endif + + % if me.alignleft: + %if me.caption is not None: +
${me.caption}
+ %endif + % endif + +
+ % if 0 < len(me): +
+ % for option in me: + ${option.render(me)} + % endfor # for option in block +
+ % endif + + + +<%def name="OptionSwitch(me, parent=None)"> +<% + ## TODO: try to simplify this template + ## SORRY, there is enouth complicated layout in this template. The `alignleft` make it dirty.... + ## When checkbox is left aligned - input is inside of the label. In other case - outside... + + uiname = me.uiName() + checkbox_id = uiname + embed_id = uiname + '_embed' + val = me.uiValue() + + need_label = me.label is not None + checked_attr = 'checked' if val else '' + embed_hidden_style_attr = '' if val else 'style="display:none"' + tooltip_attr = '' if me.tooltip is None else ' title="%s"' % me.tooltip + hidden_value = '1' if val else '0' +%> +
+ + ## ENTIRE LABEL or OPEN LABEL (when left aligned) + %if need_label: + + % if me.alignleft: + + % endif + %endif + + % if me.alignleft: + %if me.caption is not None: +
${me.caption}
+ %endif + % endif +
+ + % if 0 < len(me): +
+ % for option in me: + ${option.render()} + % endfor # for option in block +
+ % endif + + + +<%def name="OptionCheckboxList(me, parent=None)"> +<% + uiname = me.uiName() + + cssclass_cb_prefix = '-item-' + tooltip_attr = '' if me.tooltip is None else ' title="%s"' % me.tooltip +%> +
+ + ## COULD NO APPLY THIS:
, but this block actually is `input-wrapper` + ## because it is tight linked with two columns layout... + +
+ ## Fictive value, which will guarantee posting of at least one value + + % for i in me.items: + ## TODO : add an appropriate class for next div: +
+ +
+ % endfor +
+ + %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="OptionDropdown(me, parent=None)"> +<% + uiname = me.uiName() + val = me.uiValue() + + 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(parent=me)} + % endfor # for option in block +
+ % endif + + + +<%def name="OptionDropdownSelector(me, parent=None)"> +<% + uiname = me.uiName() + val = me.uiValue() + + cssclass_items_common = 'hp-option-selector-items-' + uiname + cssclass_item_prefix = '-item-' + + raw_hidden_style_attr = 'style="display:none"' + 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(parent=me)} + % endfor # for option in block +
+ % endif + + + % for item in me.items: + % if len(item)>0: +
+ % for option in item: + ${option.render(parent=me)} + % endfor +
+ % endif + % endfor + + + +<%def name="OptionExtra(me, parent=None)"> +<% + uiname = me.uiName() + val = me.uiValue() + uniqclass = '-hp-' + uiname + '-class' + + tooltip_attr = '' if me.tooltip is None else ' title="%s"' % me.tooltip +%> + <%def name="OptionExtraInternalTemplate(istemplate=True, val_host='', val_apikey='', val_enabled=True)"> +
+
+ %if me.labelHost is not None: + + %endif + +
+ + + %if me.captionHost is not None: +
${me.captionHost}
+ %endif +
+
+
+ %if me.labelApiKey is not None: + + %endif + +
+ + + %if me.captionApiKey is not None: +
${me.captionApiKey}
+ %endif +
+
+
+ %if me.labelEnabled is not None: + + %endif + +
+ ## IMPORTANT for web ui: + ## HIDDEN MUST be next sibling of CHECKBOX, jquery uses .next() to find it + + + + %if me.captionEnabled is not None: +
${me.captionEnabled}
+ %endif +
+
+ + % if not me.readonly: +
+ +
+ %endif +
+ + + + +
+ ## val = [{"host": "http://snab.ru", "apikey": "yyyy", "enabled": true}, {"host": "http://ya.ru", "apikey": "xxx", "enabled": true}] + + % for i in val: + ${OptionExtraInternalTemplate(me.readonly, i['host'], i['apikey'], i['enabled'])} + % endfor +
+ + % if not me.readonly: +
+ +
+ %endif + + % if 0 < len(me): +
+ % for option in me: + ${option.render(parent=me)} + % endfor # for option in block +
+ % endif + + +## =============================================== +## PLACEHOLDERS and STATIC templates +## =============================================== + +<%def name="BlockExtension(me, parent=None)"> + % if 0 < len(me): +
+ % if me.caption is not None: + ${me.caption} + % endif + + % for option in me: + ${option.render(parent=me)} + % endfor # for option in block +
+ % endif + + +## =============================================== + +<%def name="MessageExtension(me, parent=None)"> +<% + cls = list(me.cssclasses) # important - it should be new list!! + if not me.fullwidth: + cls.append('input-wrapper') +%> +
+
${me.icon + " " if me.icon else ''}${me.message}
+
+ + +## =============================================== + +<%def name="DividerExtension(me, parent=None)"> +
+
+
+ + + +## =============================================== +## TEMPLATES for +## +## TemplaterExtension +## +## Minimum of python code, just HTML and templates +## +## USE THESE AS RARE AS POSSIBLE +## +## =============================================== + +<%def name="ApiKeyExtension(me, parent=None)"> +<% + val = parent.uiValue() + uiname = parent.uiName() +%> +
+ +
+
${me.strings['caption']}${val}
+
+
+ + +## =============================================== + +<%def name="SongkickAreaIdExtension(me, parent=None)"> + + + +## =============================================== + +<%def name="CodeshyRegExtension(me, parent=None)"> + + + +## =============================================== + +<%def name="OsxAppRegisterExtension(me, parent=None)"> +
+
+ +
+
+ + +## =============================================== + +<%def name="TwitterNotifyExtension(me, parent=None)"> + +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ + + +## =============================================== diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index ffd6e68d1..61022381e 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -1,10 +1,15 @@ <%inherit file="base.html"/> + <%! import headphones import string %> +<%page args="model"/> + <%def name="headerIncludes()"> + +
Shut Down @@ -14,2417 +19,231 @@ <%def name="body()"> +

Settings

+
-
+
-
- - - - - - -
- Web Interface changes require a restart to take effect. Saving settings will restart intervals if changed. -
- -
-
- Basic -
- - - Use 0.0.0.0 to allow outside connections -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- - -
-
- - -
-
-
-
-
- API -
- - -
-
- - - - Current API key: ${config['api_key']} -
-
- -
- Interval - An interval of 0 will disable a task. -
- - mins - minimum is 360 minutes -
-
- - mins -
-
- - hours -
-
- - hours -
-
- - days -
-
-
- -
- -
- - - - - -
-
- Usenet - Sabnzbd - NZBget - Black Hole -
-
-
- - - usually http://localhost:8080 -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
- - - usually http://localhost:6789 -
-
- - -
-
- - -
-
- - -
- <% - if config['nzbget_priority'] == -100: - prio_verylow = 'selected="selected"' - prio_low = '' - prio_normal = '' - prio_high = '' - prio_veryhigh = '' - prio_force = '' - elif config['nzbget_priority'] == -50: - prio_verylow = '' - prio_low = 'selected="selected"' - prio_normal = '' - prio_high = '' - prio_veryhigh = '' - prio_force = '' - elif config['nzbget_priority'] == 0: - prio_verylow = '' - prio_low = '' - prio_normal = 'selected="selected"' - prio_high = '' - prio_veryhigh = '' - prio_force = '' - elif config['nzbget_priority'] == 50: - prio_verylow = '' - prio_low = '' - prio_normal = '' - prio_high = 'selected="selected"' - prio_veryhigh = '' - prio_force = '' - elif config['nzbget_priority'] == 100: - prio_verylow = '' - prio_low = '' - prio_normal = '' - prio_high = '' - prio_veryhigh = 'selected="selected"' - prio_force = '' - elif config['nzbget_priority'] == 900: - prio_verylow = '' - prio_low = '' - prio_normal = '' - prio_high = '' - prio_veryhigh = '' - prio_force = 'selected="selected"' - else: - prio_verylow = '' - prio_low = '' - prio_normal = 'selected="selected"' - prio_high = '' - prio_veryhigh = '' - prio_force = '' - %> -
- - -
-
- -
-
- - - Folder your Download program watches for NZBs -
-
- -
-
- - - Full path where SAB or NZBget downloads your music, e.g. /Users/name/Downloads/music -
-
- - -
-
-
-
- Torrents - Black Hole - Transmission - uTorrent (Beta) - Deluge (Beta) -
-
-
- - - Folder your Download program watches for Torrents -
- -
- - - - - - - - - -
- - Note: Opening magnet URLs is not suitable for headless/console/terminal servers.
Embed only works for rTorrent.
-
-
-
-
- - - usually http://localhost:9091 -
-
- - -
-
- - -
-
- Note: With Transmission, you can specify a different download directory for downloads sent from Headphones. - Set it in the Music Download Directory below -
-
-
- Note: uTorrent may keep files read only when completed. Check 'Preferences -> Advanced -> bt.read_only_on_complete' in case of problems. -
- - - usually http://localhost:9091 -
-
- - -
-
- - -
-
- - -
-
-
-
- - - Usually http://localhost:8112 (requires WebUI plugin) -
-
- - - Path to the certificate file. Make sure to use a valid certificate ("Issued To" field must match - hostname) which is not the case with the default certificate. Path is usually %appdata%\deluge\ssl on Windows, ~/.config/deluge/ssl/ on Linux. Leave this blank if you are using a self-signed certificate. -
-
- - -
-
- Note: With Deluge, you can specify a different download directory for downloads sent from Headphones. - Set it in the Music Download Directory below -
-
- - - Labels shouldn't contain spaces (requires Label plugin) -
-
- - - Directory where Deluge should move completed downloads -
-
- - -
-
-
-
- - - Number of minimum seeders a torrent must have to be accepted -
-
- - - Full path where your torrent client downloads your music e.g. /Users/name/Downloads/music. -
-
- - -
-
- - NZBs - Torrents - No Preference -
-
-
- -
-
- - - - - -
- NZBs -
-
- - -
-
-
- - - Headphones VIP Server username & password -
-
- - -
- -
-
-
-
- -
-
-
-
- - - e.g. http://nzb.su -
-
- - -
-
- -
-
- <% - newznab_number = 2 - %> - %for newznab in config['extra_newznabs']: - <% - if newznab[2] == '1' or newznab[2] == 1: - newznab_enabled = "checked" - else: - newznab_enabled = "" - %> -
-
- - -
-
- - -
-
- -
-
- -
-
- <% - newznab_number += 1 - %> - %endfor - -
-
- -
-
- -
-
-
- - -
-
-
-
-
- -
-
-
- - -
-
- - -
-
-
-
-
- Torrents - -
-
- -
-
-
- - - Optional. Leave empty for default. -
-
- - -
-
-
- -
-
- -
-
-
- - - Optional. Leave empty for default. -
-
- - -
-
-
- -
-
- -
-
-
- - -
-
- - -
-
- - -
-
-
- -
-
- -
-
-
- - -
-
- - -
-
- - -
-
-
- -
-
- -
-
-
- - -
-
- - -
-
- - -
-
-
- -
-
- -
-
-
- - -
-
-
- -
-
- -
-
-
-
- - - e.g. http://localhost:9117/torznab/iptorrents -
-
- - -
-
- -
-
- <% - torznab_number = 2 - %> - %for torznab in config['extra_torznabs']: - <% - if torznab[2] == '1' or torznab[2] == 1: - torznab_enabled = "checked" - else: - torznab_enabled = "" - %> -
-
- - -
-
- - -
-
- -
-
- -
-
- <% - torznab_number += 1 - %> - %endfor - -
-
- -
-
- -
-
-
- - -
-
- - -
-
-
- -
-
- -
-
-
- - -
-
-
- -
-
- -
-
- - - - - -
- Quality -
-
- -
-
- -
-
- -
-
- - Reject if target size is not in bitrate range: - to\ - kbps - -
-
-
-
-
- Target bitrate: - kbps
-
-
- Reject if less than % or more than % of the target size (leave blank for no limit) -
-
- - -
-
- - -
-
-
-
- Search Words - Separate words with a comma, e.g. "word1,word2,word3". -
- - - Results with any of these words in the title will be filtered out -
-
- - - Results with these words in the title will be preferred over results without them (search provider names can also be entered) -
-
- - - Results without these words in the title will be filtered out. You can use OR: 'flac OR lossless OR alac, vinyl' -
-
- -
-
-
-
- Post-Processing -
- -
-
-
- -
-
- -
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
- as .jpg -
- Use $Artist/$artist, $Album/$album, $Year/$year, put optional variables in curly braces, use single-quote marks to escape curly braces literally ('{', '}'). -
-
- -
-
- -
-
- - - The directory where Headphones will move file to after post processing, e.g. /Volumes/share/music. -
-
- - - Optional. Set this if you have a separate directory for lossless music. -
-
-
- -
-
- - - - - -
-
-
- -
-
-
- -
-
- -
-
-
- -
-
- -
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
- -
-
- -
-
-
- -
-
- -
-
- -
-
-
- -
-
- -
-
-
- - - e.g. http://localhost:8080. Separate hosts with commas -
-
- -
-
- -
-
- -
-
- -
-
-
- -
-
- -
-
-
- - - e.g. http://localhost:9000. Seperate hosts with commas -
-
-
- -
-
- -
-
- -
-
- -
-
-
- -
-
- - - Separate multiple api keys with commas -
-
- - -
-
-
- -
-
- -
-
-
- -
-
- Enter the path/application name to be registered with the Notification Center, default is /Applications/Headphones -
-
- -
-
- -
-
-
- -
- -
-
- -
-
-
- - - Host running Plex Media Server (eg. http://192.168.1.100:32400) -
-
- - - Host running Plex Client (eg. http://192.168.1.100:3005) -
-
- - Username of your Plex client API (blank for none) -
-
- - Password of your Plex client API (blank for none) -
-
- - Plex Token (for use with Plex Home) -
-
- -
-
- -
-
-
- -
-
- -
-
-
- -
-
- -
-
- - -
-
-
- -
-
- -
-
-
- -
-
- - - Separate multiple api keys with commas -
-
-
- -
-
- -
-
-
- -
-
- Leave blank to send to all devices -
-
- -
-
-
- -
-
- -
-
-
- -
-
- -
-
- - -
-
- -
-
-
- -
-
- -
-
-
- -
-
- -
-
- -
-
-
- -
-
- -
-
- -
-
- -
-
-
- -
-
- -
-
- - -
-
- -
-
-
- -
-
- -
-
-
- Contact @BotFather to create a bot and get its token -
-
- Contact @myidbot to get your user ID -
-
- -
-
-
- -
- -
-
- - - - - -
-
- Renaming options -
- - - 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 curly braces, use single-quote marks to escape curly braces literally ('{', '}').
E.g.: $Type/$First/$artist/$album{ '['$year']'} = Album/G/girl talk/all day [2010]
- -
-
- - - Use: $Disc/$disc (disc #), $Track/$track (track #), $Title/$title, $Artist/$artist, $Album/$album and $Year/$year. Put optional variables in curly braces, use single-quote marks to escape curly braces literally ('{', '}'). -
-
- -
-
-
- Re-Encoding Options - Note: this option requires the lame, ffmpeg or xld encoder -
- -
-
-
- -
-
-
- -
-
- <% - if config['encoder'] == 'lame': - lameselect = 'selected="selected"' - ffmpegselect = '' - xldselect = '' - libavselect = '' - elif config['encoder'] == 'ffmpeg': - lameselect = '' - ffmpegselect = 'selected="selected"' - xldselect = '' - libavselect = '' - elif config['encoder'] == 'libav': - lameselect = '' - ffmpegselect = '' - xldselect = '' - libavselect = 'selected="selected"' - else: - lameselect = '' - ffmpegselect = '' - xldselect = 'selected="selected"' - libavselect = '' - %> -
- - -
-
-
- -
-
-
-
- - - Set equal to the number of cores, or 0 for auto -
-
-
-
- Audio Properties -
-
- - -
-
- - -
-
-
- - -
-
-
-
- - -
-
- <% - if config["samplingfrequency"] == 44100: - freq44100 = 'selected="selected"' - freq48000 = '' - else: - freq44100 = '' - freq48000 = 'selected="selected"' - %> -
- - -
-
- -
- Advanced Encoding Options -
- - - Ignores all of the above options -
-
- - -
-
- - -
-
-
- - - If different from format selected above -
-
- - -
-
-
- Miscellaneous -
- - -
- %for extra in config['extras']: - ${string.capwords(extra)}
- %endfor -
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- - -
-
- - -
-
- - -
-
- -
- Directories -
- - -
-
- - -
-
- -
- Songkick -
- -
-
-
- - -
-
- -
-
-
- - - Find Area ID -
-
-
-
- -
- Musicbrainz -
- - -
-
-
- -
-
- -
-
- -
-
-
- -
-
-
-
-
-
- -
-
- -
-
- -
-
-
- Get an Account! -
-
-
- -
- -
- -
+ % for tab in model: + ${tab.render(me=tab, parent=None)} + % endfor +
+ +
<%def name="javascriptIncludes()"> -