Cleanup search code. Add support for magnet links conversion. Fixes #1926.

This commit is contained in:
Bas Stottelaar
2014-10-21 02:07:56 +02:00
parent 8a8821530a
commit 6b4afd8877
6 changed files with 222 additions and 103 deletions

View File

@@ -317,10 +317,27 @@
<input type="text" name="torrentblackhole_dir" value="${config['torrentblackhole_dir']}" size="50">
<small>Folder your Download program watches for Torrents</small>
</div>
<div class="row checkbox">
<label>Open Magnet Links</label>
<input type="checkbox" name="open_magnet_links" value="1" ${config['open_magnet_links']}>
<small>Allow Headphones to open magnet links</small>
<div class="row">
<label>Magnet links</label>
<label class="inline" title="Invoke shell command to open magnet URL.">
<input type="radio" name="magnet_links" id="magnet_links_0" value="0" ${config['magnet_links_0']}>
Ignore
</label>
<label class="inline" title="Use external service to convert magnet links into torrents.">
<input type="radio" name="magnet_links" id="magnet_links_1" value="1" ${config['magnet_links_1']}>
Open
</label>
<label class="inline">
<input type="radio" name="magnet_links" id="magnet_links_2" value="2" ${config['magnet_links_2']}>
Convert
</label>
<div style="clear: both"></div>
<small>Note: opening magnet URL's is not suitable for headless/console/terminal servers.</small>
</div>
</fieldset>
<fieldset id="transmission_options">
@@ -382,7 +399,7 @@
<input type="radio" name="prefer_torrents" id="prefer_torrents_0" value="0" ${config['prefer_torrents_0']}>NZBs
<input type="radio" name="prefer_torrents" id="prefer_torrents_1" value="1" ${config['prefer_torrents_1']}>Torrents
<input type="radio" name="prefer_torrents" id="prefer_torrents_2" value="2" ${config['prefer_torrents_2']}>No Preference
</div>
</div>
</fieldset>
</td>
</tr>

View File

@@ -336,6 +336,10 @@ form .row label {
padding-top: 7px;
width: 175px;
}
form .row label.inline {
margin-right: 5px;
width: auto;
}
form .row input {
margin-right: 5px;
}

View File

@@ -191,6 +191,11 @@ form {
line-height: normal;
padding-top: 7px;
width: 175px;
&.inline {
margin-right: 5px;
width: auto;
}
}
input { margin-right: 5px; }
input[type=text], input[type=password] {

View File

@@ -134,7 +134,7 @@ AUTOWANT_ALL = False
AUTOWANT_MANUALLY_ADDED = True
KEEP_TORRENT_FILES = False
PREFER_TORRENTS = None # 0: nzbs, 1: torrents, 2: no preference
OPEN_MAGNET_LINKS = False
MAGNET_LINKS = None # 0: Ignore, 1: Open, 2: Convert
SEARCH_INTERVAL = 360
LIBRARYSCAN = False
@@ -361,7 +361,7 @@ def initialize():
HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, HTTP_PROXY, LAUNCH_BROWSER, API_ENABLED, API_KEY, GIT_PATH, GIT_USER, GIT_BRANCH, DO_NOT_OVERRIDE_GIT_BRANCH, \
CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, MUSIC_DIR, DESTINATION_DIR, \
LOSSLESS_DESTINATION_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, ADD_ARTISTS, CORRECT_METADATA, FREEZE_DB, MOVE_FILES, \
RENAME_FILES, FOLDER_FORMAT, FILE_FORMAT, FILE_UNDERSCORES, CLEANUP_FILES, KEEP_NFO, INCLUDE_EXTRAS, EXTRAS, AUTOWANT_UPCOMING, AUTOWANT_ALL, AUTOWANT_MANUALLY_ADDED, KEEP_TORRENT_FILES, PREFER_TORRENTS, OPEN_MAGNET_LINKS, \
RENAME_FILES, FOLDER_FORMAT, FILE_FORMAT, FILE_UNDERSCORES, CLEANUP_FILES, KEEP_NFO, INCLUDE_EXTRAS, EXTRAS, AUTOWANT_UPCOMING, AUTOWANT_ALL, AUTOWANT_MANUALLY_ADDED, KEEP_TORRENT_FILES, PREFER_TORRENTS, MAGNET_LINKS, \
ADD_ALBUM_ART, ALBUM_ART_FORMAT, EMBED_ALBUM_ART, EMBED_LYRICS, REPLACE_EXISTING_FOLDERS, DOWNLOAD_DIR, BLACKHOLE, BLACKHOLE_DIR, USENET_RETENTION, SEARCH_INTERVAL, \
TORRENTBLACKHOLE_DIR, NUMBEROFSEEDERS, KAT, KAT_PROXY_URL, KAT_RATIO, PIRATEBAY, PIRATEBAY_PROXY_URL, PIRATEBAY_RATIO, MININOVA, MININOVA_RATIO, WAFFLES, WAFFLES_UID, WAFFLES_PASSKEY, WAFFLES_RATIO, \
RUTRACKER, RUTRACKER_USER, RUTRACKER_PASSWORD, RUTRACKER_RATIO, WHATCD, WHATCD_USERNAME, WHATCD_PASSWORD, WHATCD_RATIO, DOWNLOAD_TORRENT_DIR, \
@@ -467,7 +467,7 @@ def initialize():
AUTOWANT_MANUALLY_ADDED = bool(check_setting_int(CFG, 'General', 'autowant_manually_added', 1))
KEEP_TORRENT_FILES = bool(check_setting_int(CFG, 'General', 'keep_torrent_files', 0))
PREFER_TORRENTS = check_setting_int(CFG, 'General', 'prefer_torrents', 0)
OPEN_MAGNET_LINKS = bool(check_setting_int(CFG, 'General', 'open_magnet_links', 0))
MAGNET_LINKS = int(check_setting_int(CFG, 'General', 'magnet_links', 0))
SEARCH_INTERVAL = check_setting_int(CFG, 'General', 'search_interval', 1440)
LIBRARYSCAN = bool(check_setting_int(CFG, 'General', 'libraryscan', 1))
@@ -913,7 +913,7 @@ def config_write():
new_config['General']['autowant_manually_added'] = int(AUTOWANT_MANUALLY_ADDED)
new_config['General']['keep_torrent_files'] = int(KEEP_TORRENT_FILES)
new_config['General']['prefer_torrents'] = PREFER_TORRENTS
new_config['General']['open_magnet_links'] = OPEN_MAGNET_LINKS
new_config['General']['magnet_links'] = int(MAGNET_LINKS)
new_config['General']['numberofseeders'] = NUMBEROFSEEDERS
new_config['General']['torrentblackhole_dir'] = TORRENTBLACKHOLE_DIR

View File

@@ -20,18 +20,19 @@ from pygazelle import api as gazelleapi
from pygazelle import encoding as gazelleencoding
from pygazelle import format as gazelleformat
from pygazelle import media as gazellemedia
from xml.dom import minidom
from base64 import b16encode, b32decode
from hashlib import sha1
import os, re, time
import os
import re
import time
import string
import shutil
import requests
import random
import headphones
import subprocess
import unicodedata
import headphones
from headphones.common import USER_AGENT
from headphones import logger, db, helpers, classes, sab, nzbget, request
from headphones import utorrent, transmission, notifiers
@@ -39,19 +40,136 @@ from headphones import utorrent, transmission, notifiers
from bencode import bencode, bdecode
import headphones.searcher_rutracker as rutrackersearch
rutracker = rutrackersearch.Rutracker()
# Magnet to torrent services, for Black hole. Stolen from CouchPotato.
TORRENT_TO_MAGNET_SERVICES = [
'https://zoink.it/torrent/%s.torrent',
'http://torrage.com/torrent/%s.torrent',
'https://torcache.net/torrent/%s.torrent',
]
# Persistent What.cd API object
gazelle = None
def url_fix(s, charset='utf-8'):
# RUtracker search object
rutracker = rutrackersearch.Rutracker()
def fix_url(s, charset="utf-8"):
"""
Fix the URL so it is proper formatted and encoded.
"""
if isinstance(s, unicode):
s = s.encode(charset, 'ignore')
scheme, netloc, path, qs, anchor = urlparse.urlsplit(s)
path = urllib.quote(path, '/%')
qs = urllib.quote_plus(qs, ':&=')
return urlparse.urlunsplit((scheme, netloc, path, qs, anchor))
def torrent_to_file(target_file, data):
"""
Write torrent data to file, and change permissions accordingly. Will return
None in case of a write error. If changing permissions fails, it will
continue anyway.
"""
# Write data to file
try:
with open(target_file, "wb") as fp:
fp.write(data)
except IOError as e:
logger.error("Could not write torrent file '%s': %s. Skipping.",
target_file, e.message)
return
# Try to change permissions
try:
os.chmod(target_file, int(headphones.FILE_PERMISSIONS, 8))
except OSError as e:
logger.warn("Could not change permissions for file '%s': %s. " \
"Continuing.", target_file, e.message)
# Done
return True
def read_torrent_name(torrent_file, default_name=None):
"""
Read the torrent file and return the torrent name. If the torrent name
cannot be determined, it will return the `default_name`.
"""
# Open file
try:
with open(torrent_file, "rb") as fp:
torrent_info = bdecode(fp.read())
except IOError as e:
logger.error("Unable to open torrent file: %s", torrent_file)
return
# Read dictionary
if torrent_info:
try:
return torrent_info["info"]["name"]
except KeyError:
if default_name:
logger.warning("Couldn't get name from torrent file: %s. " \
"Defaulting to '%s'", e, default_name)
else:
logger.warning("Couldn't get name from torrent file: %s. No " \
"default given", e)
# Return default
return default_name
def calculate_torrent_hash(link, data=None):
"""
Calculate the torrent hash from a magnet link or data.
"""
if link.startswith("magnet:"):
torrent_hash = re.findall("urn:btih:([\w]{32,40})", link)[0]
if len(torrent_hash) == 32:
torrent_hash = b16encode(b32decode(torrent_hash)).lower()
elif data:
info = bdecode(data)["info"]
torrent_hash = sha1(bencode(info)).hexdigest()
else:
raise ValueError("Cannot calculate torrent hash without magnet link " \
"or data")
return torrent_hash
def get_seed_ratio(provider):
"""
Return the seed ratio for the specified provider, if applicable. Defaults to
None in case of an error.
"""
if provider == 'rutracker.org':
seed_ratio = headphones.RUTRACKER_RATIO
elif provider == 'Kick Ass Torrents':
seed_ratio = headphones.KAT_RATIO
elif provider == 'What.cd':
seed_ratio = headphones.WHATCD_RATIO
elif provider == 'The Pirate Bay':
seed_ratio = headphones.PIRATEBAY_RATIO
elif provider == 'Waffles.fm':
seed_ratio = headphones.WAFFLES_RATIO
elif provider == 'Mininova':
seed_ratio = headphones.MININOVA_RATIO
else:
seed_ratio = None
if seed_ratio is not None:
try:
seed_ratio = float(seed_ratio)
except ValueError:
logger.warn("Could not get seed ratio for %s" % provider)
return seed_ratio
def searchforalbum(albumid=None, new=False, losslessOnly=False, choose_specific_download=False):
myDB = db.DBConnection()
@@ -80,7 +198,6 @@ def searchforalbum(albumid=None, new=False, losslessOnly=False, choose_specific_
return results
else:
album = myDB.action('SELECT * from albums WHERE AlbumID=?', [albumid]).fetchone()
logger.info('Searching for "%s - %s" since it was marked as wanted' % (album['ArtistName'], album['AlbumTitle']))
do_sorted_search(album, new, losslessOnly)
@@ -233,7 +350,7 @@ def sort_search_results(resultlist, album, new, albumlength):
priority = 1
# add a search provider priority (weighted based on position)
i = next((i for i, word in enumerate(preferred_words) if word in result[3].lower()), None)
if i != None:
if i is not None:
priority += round((len(preferred_words) - i) / float(len(preferred_words)),2)
temp_list.append((result[0],result[1],result[2],result[3],result[4],priority))
@@ -622,8 +739,8 @@ def send_to_downloader(data, bestqual, album):
torrent_name = helpers.replace_illegal_chars(folder_name) + '.torrent'
download_path = os.path.join(headphones.TORRENTBLACKHOLE_DIR, torrent_name)
if bestqual[2].startswith("magnet:"):
if headphones.OPEN_MAGNET_LINKS:
if bestqual[2].lower().startswith("magnet:"):
if headphones.MAGNET_LINKS == 1:
try:
if headphones.SYS_PLATFORM == 'win32':
os.startfile(bestqual[2])
@@ -637,37 +754,51 @@ def send_to_downloader(data, bestqual, album):
except Exception as e:
logger.error("Error opening magnet link: %s" % str(e))
return
else:
logger.error("Cannot save magnet files to blackhole. Please switch your torrent downloader to Transmission or uTorrent or allow Headphones to try to open magnet links")
return
elif headphones.MAGNET_LINKS == 2:
# Procedure adapted from CouchPotato
torrent_hash = calculate_torrent_hash(bestqual[2])
else:
try:
# Randomize list of services
services = TORRENT_TO_MAGNET_SERVICES[:]
random.shuffle(services)
if bestqual[3] == 'rutracker.org':
download_path = rutracker.get_torrent(bestqual[2], headphones.TORRENTBLACKHOLE_DIR)
if not download_path:
return
for service in services:
data = request.request_content(service % torrent_hash)
if data and "torcache" in data:
if not torrent_to_file(download_path, data):
return
# Extract folder name from torrent
folder_name = read_torrent_name(download_path,
bestqual[0])
# Break for loop
break
else:
#Write the torrent file to a path derived from the TORRENTBLACKHOLE_DIR and file name.
with open(download_path, 'wb') as fp:
fp.write(data)
# No service succeeded
logger.warning("Unable to convert magnet with hash " \
"'%s' into a torrent file.", torrent_hash)
return
else:
logger.error("Cannot save magnet link in blackhole. " \
"Please switch your torrent downloader to " \
"Transmission or uTorrent, or allow Headphones " \
"to open or convert magnet links")
return
else:
if bestqual[3] == "rutracker.org":
download_path = rutracker.get_torrent(bestqual[2],
headphones.TORRENTBLACKHOLE_DIR)
try:
os.chmod(download_path, int(headphones.FILE_PERMISSIONS, 8))
except:
logger.error("Could not change permissions for file: %s", download_path)
if not download_path:
return
else:
if not torrent_to_file(download_path, data):
return
# Open the fresh torrent file again so we can extract the
# proper torrent name Used later in post-processing.
with open(download_path, 'rb') as fp:
torrent_info = bdecode(fp.read())
folder_name = torrent_info['info'].get('name', '')
logger.info('Torrent folder name: %s' % folder_name)
except Exception as e:
logger.error('Couldn\'t get name from Torrent file: %s. Defaulting to torrent title' % e)
folder_name = bestqual[0]
# Extract folder name from torrent
folder_name = read_torrent_name(download_path, bestqual[0])
elif headphones.TORRENT_DOWNLOADER == 1:
logger.info("Sending torrent to Transmission")
@@ -699,8 +830,8 @@ def send_to_downloader(data, bestqual, album):
logger.exception("Unhandled exception")
# Set Seed Ratio
seed_ratio = getSeedRatio(bestqual[3])
if seed_ratio != None:
seed_ratio = get_seed_ratio(bestqual[3])
if seed_ratio is not None:
transmission.setSeedRatio(torrentid, seed_ratio)
else:# if headphones.TORRENT_DOWNLOADER == 2:
@@ -714,7 +845,7 @@ def send_to_downloader(data, bestqual, album):
utorrent.labelTorrent(torrentid)
else:
file_or_url = bestqual[2]
torrentid = CalculateTorrentHash(file_or_url, data)
torrentid = calculate_torrent_hash(file_or_url, data)
folder_name = utorrent.addTorrent(file_or_url, torrentid)
if folder_name:
@@ -731,8 +862,8 @@ def send_to_downloader(data, bestqual, album):
logger.exception("Unhandled exception")
# Set Seed Ratio
seed_ratio = getSeedRatio(bestqual[3])
if seed_ratio != None:
seed_ratio = get_seed_ratio(bestqual[3])
if seed_ratio is not None:
utorrent.setSeedRatio(torrentid, seed_ratio)
myDB = db.DBConnection()
@@ -740,7 +871,7 @@ def send_to_downloader(data, bestqual, album):
myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?, ?)', [album['AlbumID'], bestqual[0], bestqual[1], bestqual[2], "Snatched", folder_name, kind])
# Store the torrent id so we can check later if it's finished seeding and can be removed
if seed_ratio != None and seed_ratio != 0 and torrentid:
if seed_ratio is not None and seed_ratio != 0 and torrentid:
myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?, ?)', [album['AlbumID'], bestqual[0], bestqual[1], bestqual[2], "Seed_Snatched", torrentid, kind])
# notify
@@ -935,9 +1066,9 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
# Use proxy if specified
if headphones.KAT_PROXY_URL:
providerurl = url_fix(set_proxy(headphones.KAT_PROXY_URL))
providerurl = fix_url(set_proxy(headphones.KAT_PROXY_URL))
else:
providerurl = url_fix("https://kickass.to")
providerurl = fix_url("https://kickass.to")
# Build URL
providerurl = providerurl + "/usearch/" + ka_term
@@ -995,7 +1126,7 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
if headphones.WAFFLES:
provider = "Waffles.fm"
providerurl = url_fix("https://www.waffles.fm/browse.php")
providerurl = fix_url("https://www.waffles.fm/browse.php")
bitrate = None
if headphones.PREFERRED_QUALITY == 3 or losslessOnly:
@@ -1186,9 +1317,9 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
# Use proxy if specified
if headphones.PIRATEBAY_PROXY_URL:
providerurl = url_fix(set_proxy(headphones.PIRATEBAY_PROXY_URL))
providerurl = fix_url(set_proxy(headphones.PIRATEBAY_PROXY_URL))
else:
providerurl = url_fix("https://thepiratebay.se")
providerurl = fix_url("https://thepiratebay.se")
# Build URL
providerurl = providerurl + "/search/" + tpb_term + "/0/7/" # 7 is sort by seeders
@@ -1228,7 +1359,7 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
try:
url = item.find("a", {"title":"Download this torrent"})['href']
except TypeError:
if headphones.OPEN_MAGNET_LINKS:
if headphones.MAGNET_LINKS != 0:
url = item.findAll("a")[3]['href']
else:
logger.info('"%s" only has a magnet link, skipping' % title)
@@ -1252,7 +1383,7 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
if headphones.MININOVA:
provider = "Mininova"
providerurl = url_fix("http://www.mininova.org/rss/" + term + "/5")
providerurl = fix_url("http://www.mininova.org/rss/" + term + "/5")
if headphones.PREFERRED_QUALITY == 3 or losslessOnly:
categories = "7" #music
@@ -1320,7 +1451,6 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
def preprocess(resultlist):
for result in resultlist:
if result[4] == 'torrent':
#Get out of here if we're using Transmission
if headphones.TORRENT_DOWNLOADER == 1: ## if not a magnet link still need the .torrent to generate hash... uTorrent support labeling
@@ -1329,7 +1459,7 @@ def preprocess(resultlist):
if result[3] == 'rutracker.org':
return True, result
# Get out of here if it's a magnet link
if result[2].startswith("magnet"):
if result[2].lower().startswith("magnet:"):
return True, result
# Download the torrent file
@@ -1344,48 +1474,9 @@ def preprocess(resultlist):
return request.request_content(url=result[2], headers=headers), result
else:
headers = {'User-Agent': USER_AGENT}
if result[3] == 'headphones':
return request.request_content(url=result[2], headers=headers, auth=(headphones.HPUSER, headphones.HPPASS)), result
else:
return request.request_content(url=result[2], headers=headers), result
def CalculateTorrentHash(link, data):
if link.startswith('magnet'):
tor_hash = re.findall('urn:btih:([\w]{32,40})', link)[0]
if len(tor_hash) == 32:
tor_hash = b16encode(b32decode(tor_hash)).lower()
else:
info = bdecode(data)["info"]
tor_hash = sha1(bencode(info)).hexdigest()
logger.debug('Torrent Hash: ' + str(tor_hash))
return tor_hash
def getSeedRatio(provider):
seed_ratio = ''
if provider == 'rutracker.org':
seed_ratio = headphones.RUTRACKER_RATIO
elif provider == 'Kick Ass Torrents':
seed_ratio = headphones.KAT_RATIO
elif provider == 'What.cd':
seed_ratio = headphones.WHATCD_RATIO
elif provider == 'The Pirate Bay':
seed_ratio = headphones.PIRATEBAY_RATIO
elif provider == 'Waffles.fm':
seed_ratio = headphones.WAFFLES_RATIO
elif provider == 'Mininova':
seed_ratio = headphones.MININOVA_RATIO
if seed_ratio != '':
try:
seed_ratio_float = float(seed_ratio)
except:
seed_ratio_float = None
logger.warn('Could not get Seed Ratio for %s' % provider)
return seed_ratio_float
else:
return None
return request.request_content(url=result[2], headers=headers), result

View File

@@ -1070,7 +1070,9 @@ class WebInterface(object):
"prefer_torrents_0" : radio(headphones.PREFER_TORRENTS, 0),
"prefer_torrents_1" : radio(headphones.PREFER_TORRENTS, 1),
"prefer_torrents_2" : radio(headphones.PREFER_TORRENTS, 2),
"open_magnet_links" : checked(headphones.OPEN_MAGNET_LINKS),
"magnet_links_0" : radio(headphones.MAGNET_LINKS, 0),
"magnet_links_1" : radio(headphones.MAGNET_LINKS, 1),
"magnet_links_2" : radio(headphones.MAGNET_LINKS, 2),
"log_dir" : headphones.LOG_DIR,
"cache_dir" : headphones.CACHE_DIR,
"interface_list" : interface_list,
@@ -1188,7 +1190,7 @@ class WebInterface(object):
numberofseeders=None, use_piratebay=0, piratebay_proxy_url=None, piratebay_ratio=None, use_kat=0, kat_proxy_url=None, kat_ratio=None, use_mininova=0, mininova_ratio=None, waffles=0, waffles_uid=None, waffles_passkey=None, waffles_ratio=None, whatcd=0, whatcd_username=None, whatcd_password=None, whatcd_ratio=None,
rutracker=0, rutracker_user=None, rutracker_password=None, rutracker_ratio=None, rename_files=0, correct_metadata=0, cleanup_files=0, keep_nfo=0, add_album_art=0, album_art_format=None, embed_album_art=0, embed_lyrics=0, replace_existing_folders=False,
destination_dir=None, lossless_destination_dir=None, folder_format=None, file_format=None, file_underscores=0, include_extras=0, single=0, ep=0, compilation=0, soundtrack=0, live=0, remix=0, spokenword=0, audiobook=0, other=0, djmix=0, mixtape_street=0, broadcast=0, interview=0, demo=0,
autowant_upcoming=False, autowant_all=False, autowant_manually_added=False, keep_torrent_files=False, prefer_torrents=0, open_magnet_links=0, interface=None, log_dir=None, cache_dir=None, music_encoder=0, encoder=None, xldprofile=None,
autowant_upcoming=False, autowant_all=False, autowant_manually_added=False, keep_torrent_files=False, prefer_torrents=0, magnet_links=0, interface=None, log_dir=None, cache_dir=None, music_encoder=0, encoder=None, xldprofile=None,
bitrate=None, samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0, subsonic_enabled=False, subsonic_host=None, subsonic_username=None, subsonic_password=None,
delete_lossless_files=0, growl_enabled=0, growl_onsnatch=0, growl_host=None, growl_password=None, prowl_enabled=0, prowl_onsnatch=0, prowl_keys=None, prowl_priority=0, xbmc_enabled=0, xbmc_host=None, xbmc_username=None, xbmc_password=None,
xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, pushalot_enabled=False, pushalot_apikey=None, pushalot_onsnatch=0, synoindex_enabled=False, lms_enabled=0, lms_host=None,
@@ -1302,7 +1304,7 @@ class WebInterface(object):
headphones.AUTOWANT_MANUALLY_ADDED = autowant_manually_added
headphones.KEEP_TORRENT_FILES = keep_torrent_files
headphones.PREFER_TORRENTS = int(prefer_torrents)
headphones.OPEN_MAGNET_LINKS = open_magnet_links
headphones.MAGNET_LINKS = int(magnet_links)
headphones.INTERFACE = interface
headphones.LOG_DIR = log_dir
headphones.CACHE_DIR = cache_dir