diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 3fdb7c1f..5d2bded8 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -317,10 +317,27 @@ Folder your Download program watches for Torrents -
- - - Allow Headphones to open magnet links + +
+ + + + + + + +
+ + Note: opening magnet URL's is not suitable for headless/console/terminal servers.
@@ -382,7 +399,7 @@ NZBs Torrents No Preference -
+ diff --git a/data/interfaces/default/css/style.css b/data/interfaces/default/css/style.css index 16a9f735..0fd4a4ea 100644 --- a/data/interfaces/default/css/style.css +++ b/data/interfaces/default/css/style.css @@ -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; } diff --git a/data/interfaces/default/css/style.less b/data/interfaces/default/css/style.less index 5cdc8e21..b03057c2 100644 --- a/data/interfaces/default/css/style.less +++ b/data/interfaces/default/css/style.less @@ -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] { diff --git a/headphones/__init__.py b/headphones/__init__.py index 585aa25e..c4ef0970 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -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 diff --git a/headphones/searcher.py b/headphones/searcher.py index 3ea3e0d8..aa7e1ecd 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -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 \ No newline at end of file + return request.request_content(url=result[2], headers=headers), result \ No newline at end of file diff --git a/headphones/webserve.py b/headphones/webserve.py index a91305c3..be08c309 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -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