+
+ Reject if target size is not in bitrate range: \
+ to\
+ kbps
+
+
Preferred Bitrate: kbps
Reject if less than % or more than % of the target size (leave blank for no limit)
@@ -496,7 +503,7 @@
- Results with these words in the title will be preferred over results without them
+ Results with these words in the title will be preferred over results without them (search provider names can also be entered)
@@ -1587,6 +1594,15 @@
}
});
+ if ($("#lossless_only").is(":checked"))
+ {
+ $("#lossless_only_options").show();
+ }
+ else
+ {
+ $("#lossless_only_options").hide();
+ }
+
if ($("#preferred_bitrate").is(":checked"))
{
$("#preferred_bitrate_options").show();
@@ -1632,18 +1648,22 @@
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 ($("#preferred_quality3").is(":checked"))
+ if ($("#lossless_only").is(":checked"))
{
- $("#preferred_bitrate_options").slideUp("fast");
+ $("#lossless_only_options").slideDown("fast");
+ $("#preferred_bitrate_options").slideUp("fast");
}
if ($("#nzb_downloader_sabnzbd").is(":checked"))
{
diff --git a/headphones/__init__.py b/headphones/__init__.py
index 9aa42ba1..0fff644b 100644
--- a/headphones/__init__.py
+++ b/headphones/__init__.py
@@ -103,6 +103,8 @@ PREFERRED_BITRATE_HIGH_BUFFER = None
PREFERRED_BITRATE_LOW_BUFFER = None
PREFERRED_BITRATE_ALLOW_LOSSLESS = False
DETECT_BITRATE = False
+LOSSLESS_BITRATE_FROM = None
+LOSSLESS_BITRATE_TO = None
ADD_ARTISTS = False
CORRECT_METADATA = False
MOVE_FILES = False
@@ -358,7 +360,7 @@ def initialize():
PUSHBULLET_ENABLED, PUSHBULLET_APIKEY, PUSHBULLET_DEVICEID, PUSHBULLET_ONSNATCH, \
MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \
XBMC_NOTIFY, LMS_ENABLED, LMS_HOST, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
- PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, CACHE_SIZEMB, JOURNAL_MODE, UMASK, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \
+ PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, LOSSLESS_BITRATE_FROM, LOSSLESS_BITRATE_TO, CACHE_SIZEMB, JOURNAL_MODE, UMASK, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \
PLEX_ENABLED, PLEX_SERVER_HOST, PLEX_CLIENT_HOST, PLEX_USERNAME, PLEX_PASSWORD, PLEX_UPDATE, PLEX_NOTIFY, PUSHALOT_ENABLED, PUSHALOT_APIKEY, \
PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED
@@ -438,6 +440,8 @@ def initialize():
PREFERRED_BITRATE_LOW_BUFFER = check_setting_int(CFG, 'General', 'preferred_bitrate_low_buffer', '')
PREFERRED_BITRATE_ALLOW_LOSSLESS = bool(check_setting_int(CFG, 'General', 'preferred_bitrate_allow_lossless', 0))
DETECT_BITRATE = bool(check_setting_int(CFG, 'General', 'detect_bitrate', 0))
+ LOSSLESS_BITRATE_FROM = check_setting_int(CFG, 'General', 'lossless_bitrate_from', '')
+ LOSSLESS_BITRATE_TO = check_setting_int(CFG, 'General', 'lossless_bitrate_to', '')
ADD_ARTISTS = bool(check_setting_int(CFG, 'General', 'auto_add_artists', 1))
CORRECT_METADATA = bool(check_setting_int(CFG, 'General', 'correct_metadata', 0))
MOVE_FILES = bool(check_setting_int(CFG, 'General', 'move_files', 0))
@@ -862,6 +866,8 @@ def config_write():
new_config['General']['preferred_bitrate_low_buffer'] = PREFERRED_BITRATE_LOW_BUFFER
new_config['General']['preferred_bitrate_allow_lossless'] = int(PREFERRED_BITRATE_ALLOW_LOSSLESS)
new_config['General']['detect_bitrate'] = int(DETECT_BITRATE)
+ new_config['General']['lossless_bitrate_from'] = LOSSLESS_BITRATE_FROM
+ new_config['General']['lossless_bitrate_to'] = LOSSLESS_BITRATE_TO
new_config['General']['auto_add_artists'] = int(ADD_ARTISTS)
new_config['General']['correct_metadata'] = int(CORRECT_METADATA)
new_config['General']['move_files'] = int(MOVE_FILES)
diff --git a/headphones/searcher.py b/headphones/searcher.py
index 0a33df12..c26b5826 100644
--- a/headphones/searcher.py
+++ b/headphones/searcher.py
@@ -151,11 +151,20 @@ def sort_search_results(resultlist, album, new):
# Add a priority if it has any of the preferred words
temp_list = []
+ preferred_words = None
+ if headphones.PREFERRED_WORDS:
+ preferred_words = helpers.split_string(headphones.PREFERRED_WORDS)
for result in resultlist:
- if headphones.PREFERRED_WORDS and any(word.lower() in result[0].lower() for word in helpers.split_string(headphones.PREFERRED_WORDS)):
- temp_list.append((result[0],result[1],result[2],result[3],result[4],1))
- else:
- temp_list.append((result[0],result[1],result[2],result[3],result[4],0))
+ priority = 0
+ if preferred_words:
+ if any(word.lower() in result[0].lower() for word in preferred_words):
+ 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:
+ 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))
resultlist = temp_list
@@ -218,6 +227,34 @@ def sort_search_results(resultlist, album, new):
finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
+ # lossless - ignore results if target size outside bitrate range
+ elif headphones.PREFERRED_QUALITY == 3 and (headphones.LOSSLESS_BITRATE_FROM or headphones.LOSSLESS_BITRATE_TO):
+
+ finallist = []
+ tracks = myDB.select('SELECT TrackDuration from tracks WHERE AlbumID=?', [album['AlbumID']])
+
+ if len(tracks):
+
+ albumlength = sum([pair[0] for pair in tracks])
+ mintargetsize = 0
+ maxtargetsize = 0
+ if headphones.LOSSLESS_BITRATE_FROM:
+ mintargetsize = albumlength/1000 * int(headphones.LOSSLESS_BITRATE_FROM) * 128
+ if headphones.LOSSLESS_BITRATE_TO:
+ maxtargetsize = albumlength/1000 * int(headphones.LOSSLESS_BITRATE_TO) * 128
+
+ if mintargetsize > 0 or maxtargetsize > 0:
+ for i, result in reversed(list(enumerate(resultlist))):
+ if int(result[1]) < mintargetsize and mintargetsize > 0 or int(result[1]) > maxtargetsize and maxtargetsize > 0:
+ if int(result[1]) < mintargetsize:
+ logger.info("%s is too small for this album - not considering it. (Size: %s, Minsize: %s)", result[0], helpers.bytes_to_mb(result[1]), helpers.bytes_to_mb(mintargetsize))
+ else:
+ logger.info("%s is too large for this album - not considering it. (Size: %s, Maxsize: %s)", result[0], helpers.bytes_to_mb(result[1]), helpers.bytes_to_mb(maxtargetsize))
+ del resultlist[i]
+
+ if len (resultlist):
+ finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
+
else:
finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
diff --git a/headphones/searcher_rutracker.py b/headphones/searcher_rutracker.py
index 8837d205..3c799f24 100644
--- a/headphones/searcher_rutracker.py
+++ b/headphones/searcher_rutracker.py
@@ -18,6 +18,7 @@ from tempfile import mkdtemp
class Rutracker():
logged_in = False
+
# Stores a number of login attempts to prevent recursion.
#login_counter = 0
@@ -29,7 +30,7 @@ class Rutracker():
def login(self, login, password):
"""Implements tracker login procedure."""
-
+
self.logged_in = False
if login is None or password is None:
@@ -51,7 +52,6 @@ class Rutracker():
pass
# Check if we're logged in
-
for cookie in self.cookiejar:
if cookie.name == 'bb_data':
self.logged_in = True
@@ -64,7 +64,6 @@ class Rutracker():
"""
# Build search url
-
searchterm = ''
if artist != 'Various Artists':
searchterm = artist
@@ -82,8 +81,7 @@ class Rutracker():
else:
format = '+mp3||aac'
- # sort by size, descending.
-
+ # sort by size, descending.
sort = '&o=7&s=2'
searchurl = "%s?nm=%s%s%s" % (providerurl, urllib.quote(searchterm), format, sort)
@@ -111,25 +109,21 @@ class Rutracker():
#logger.debug (soup.prettify())
# Title
-
for link in soup.find_all('a', attrs={'class' : 'med tLink hl-tags bold'}):
title = link.get_text()
titles.append(title)
# Download URL
-
for link in soup.find_all('a', attrs={'class' : 'small tr-dl dl-stub'}):
url = link.get('href')
urls.append(url)
# Seeders
-
for link in soup.find_all('b', attrs={'class' : 'seedmed'}):
seeder = link.get_text()
seeders.append(seeder)
# Size
-
for link in soup.find_all('td', attrs={'class' : 'row4 small nowrap tor-size'}):
size = link.u.string
sizes.append(size)
@@ -138,30 +132,33 @@ class Rutracker():
pass
# Combine lists
-
torrentlist = zip(titles, urls, seeders, sizes)
# return if nothing found
-
if not torrentlist:
return False
-
- # get headphones track count for album, return if not found
-
- myDB = db.DBConnection()
- tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
- hptrackcount = len(tracks)
-
- if not hptrackcount:
- logger.info('headphones track info not found, cannot compare to torrent')
- return False
-
- # Return all valid entries, ignored, required words now checked in searcher.py
-
- #unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd']
- formatlist = ['ape', 'flac', 'ogg', 'm4a', 'aac', 'mp3', 'wav', 'aif']
- deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive']
+ # don't bother checking track counts anymore, let searcher filter instead
+ # leave code in just in case
+ check_track_count = False
+
+ if check_track_count:
+
+ # get headphones track count for album, return if not found
+ myDB = db.DBConnection()
+ tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
+ hptrackcount = len(tracks)
+
+ if not hptrackcount:
+ logger.info('headphones track info not found, cannot compare to torrent')
+ return False
+
+ # Return all valid entries, ignored, required words now checked in searcher.py
+
+ #unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd']
+
+ formatlist = ['ape', 'flac', 'ogg', 'm4a', 'aac', 'mp3', 'wav', 'aif']
+ deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive']
for torrent in torrentlist:
@@ -169,105 +166,102 @@ class Rutracker():
url = torrent[1]
seeders = torrent[2]
size = torrent[3]
-
- title = returntitle.lower()
-
- if int(size) <= maxsize and int(seeders) >= minseeders:
-
- # Check torrent info
-
- torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t']
- self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False))
-
- # Debug
- #for cookie in self.cookiejar:
- # logger.debug ('Cookie: %s' % cookie)
-
- try:
- page = self.opener.open(url)
- torrent = page.read()
- if torrent:
- decoded = bencode.bdecode(torrent)
- metainfo = decoded['info']
- page.close ()
- except Exception, e:
- logger.error('Error getting torrent: %s' % e)
- return False
-
- # get torrent track count and check for cue
-
- trackcount = 0
- cuecount = 0
-
- if 'files' in metainfo: # multi
- for pathfile in metainfo['files']:
- path = pathfile['path']
- for file in path:
- if any(file.lower().endswith('.' + x.lower()) for x in formatlist):
- trackcount += 1
- if '.cue' in file:
- cuecount += 1
-
- #Torrent topic page
-
- topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id
- logger.debug ('torrent title: %s' % title)
- logger.debug ('headphones trackcount: %s' % hptrackcount)
- logger.debug ('rutracker trackcount: %s' % trackcount)
- # If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s)
- # This is for the case where we have a single .flac/.wav which can be split by cue
- # Not great, but shouldn't be doing this too often
-
- totallogcount = 0
- if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount:
- page = self.opener.open(topicurl, timeout=60)
- soup = BeautifulSoup(page.read())
- findtoc = soup.find_all(text='TOC of the extracted CD')
- if not findtoc:
- findtoc = soup.find_all(text='TOC извлечённого CD')
- for toc in findtoc:
- logcount = 0
- for toccontent in toc.find_all_next(text=True):
- cut_string = toccontent.split('|')
- new_string = cut_string[0].lstrip().rstrip()
- if new_string == '1' or new_string == '01':
- logcount = 1
- elif logcount > 0:
- if new_string.isdigit():
- logcount += 1
- else:
- break
- totallogcount = totallogcount + logcount
-
- if totallogcount > 0:
- trackcount = totallogcount
- logger.debug ('rutracker logtrackcount: %s' % totallogcount)
-
- # If torrent track count = hp track count then return torrent,
- # if greater, check for deluxe/special/foreign editions
- # if less, then allow if it's a single track with a cue
-
- valid = False
-
- if trackcount == hptrackcount:
+ if int(size) <= maxsize and int(seeders) >= minseeders:
+
+ #Torrent topic page
+ torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t']
+ topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id
+
+ # add to list
+ if not check_track_count:
valid = True
- elif trackcount > hptrackcount:
- if any(deluxe in title for deluxe in deluxelist):
+ else:
+
+ # Check torrent info
+ self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False))
+
+ # Debug
+ #for cookie in self.cookiejar:
+ # logger.debug ('Cookie: %s' % cookie)
+
+ try:
+ page = self.opener.open(url)
+ torrent = page.read()
+ if torrent:
+ decoded = bencode.bdecode(torrent)
+ metainfo = decoded['info']
+ page.close ()
+ except Exception, e:
+ logger.error('Error getting torrent: %s' % e)
+ return False
+
+ # get torrent track count and check for cue
+ trackcount = 0
+ cuecount = 0
+
+ if 'files' in metainfo: # multi
+ for pathfile in metainfo['files']:
+ path = pathfile['path']
+ for file in path:
+ if any(file.lower().endswith('.' + x.lower()) for x in formatlist):
+ trackcount += 1
+ if '.cue' in file:
+ cuecount += 1
+
+ title = returntitle.lower()
+ logger.debug ('torrent title: %s' % title)
+ logger.debug ('headphones trackcount: %s' % hptrackcount)
+ logger.debug ('rutracker trackcount: %s' % trackcount)
+
+ # If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s)
+ # This is for the case where we have a single .flac/.wav which can be split by cue
+ # Not great, but shouldn't be doing this too often
+ totallogcount = 0
+ if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount:
+ page = self.opener.open(topicurl, timeout=60)
+ soup = BeautifulSoup(page.read())
+ findtoc = soup.find_all(text='TOC of the extracted CD')
+ if not findtoc:
+ findtoc = soup.find_all(text='TOC извлечённого CD')
+ for toc in findtoc:
+ logcount = 0
+ for toccontent in toc.find_all_next(text=True):
+ cut_string = toccontent.split('|')
+ new_string = cut_string[0].lstrip().rstrip()
+ if new_string == '1' or new_string == '01':
+ logcount = 1
+ elif logcount > 0:
+ if new_string.isdigit():
+ logcount += 1
+ else:
+ break
+ totallogcount = totallogcount + logcount
+
+ if totallogcount > 0:
+ trackcount = totallogcount
+ logger.debug ('rutracker logtrackcount: %s' % totallogcount)
+
+ # If torrent track count = hp track count then return torrent,
+ # if greater, check for deluxe/special/foreign editions
+ # if less, then allow if it's a single track with a cue
+ valid = False
+
+ if trackcount == hptrackcount:
valid = True
+ elif trackcount > hptrackcount:
+ if any(deluxe in title for deluxe in deluxelist):
+ valid = True
# Add to list
-
if valid:
rulist.append((returntitle, size, topicurl))
else:
if topicurl:
logger.info(u'Torrent found with %s tracks but the selected headphones release has %s tracks, skipping for rutracker.org' % (topicurl, trackcount, hptrackcount))
-
else:
logger.info('%s is larger than the maxsize or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i)' % (returntitle, int(size), int(seeders)))
-
return rulist
def get_torrent(self, url, savelocation=None):
diff --git a/headphones/transmission.py b/headphones/transmission.py
index e2e321b3..17ad434e 100644
--- a/headphones/transmission.py
+++ b/headphones/transmission.py
@@ -30,7 +30,14 @@ from headphones import logger, notifiers, request
def addTorrent(link):
method = 'torrent-add'
- arguments = {'filename': link, 'download-dir': headphones.DOWNLOAD_TORRENT_DIR}
+
+ if link.endswith('.torrent'):
+ f = open(link,'rb')
+ metainfo = str(base64.b64encode(f.read()))
+ f.close()
+ arguments = {'metainfo': metainfo, 'download-dir':headphones.DOWNLOAD_TORRENT_DIR}
+ else:
+ arguments = {'filename': link, 'download-dir': headphones.DOWNLOAD_TORRENT_DIR}
response = torrentAction(method,arguments)
diff --git a/headphones/webserve.py b/headphones/webserve.py
index 6db4c1f3..709de4f0 100644
--- a/headphones/webserve.py
+++ b/headphones/webserve.py
@@ -1000,6 +1000,8 @@ class WebInterface(object):
"pref_bitrate_low" : headphones.PREFERRED_BITRATE_LOW_BUFFER,
"pref_bitrate_allow_lossless" : checked(headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS),
"detect_bitrate" : checked(headphones.DETECT_BITRATE),
+ "lossless_bitrate_from" : headphones.LOSSLESS_BITRATE_FROM,
+ "lossless_bitrate_to" : headphones.LOSSLESS_BITRATE_TO,
"move_files" : checked(headphones.MOVE_FILES),
"rename_files" : checked(headphones.RENAME_FILES),
"correct_metadata" : checked(headphones.CORRECT_METADATA),
@@ -1136,8 +1138,8 @@ class WebInterface(object):
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,
pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, pushover_apitoken=None, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0,
osx_notify_enabled=0, osx_notify_onsnatch=0, osx_notify_app=None, boxcar_enabled=0, boxcar_onsnatch=0, boxcar_token=None, mirror=None, customhost=None, customport=None, customsleep=None, hpuser=None, hppass=None,
- preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None, file_permissions=None, folder_permissions=None,
- plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, plex_password=None, plex_update=0, plex_notify=0,
+ preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, lossless_bitrate_from=None, lossless_bitrate_to=None, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None,
+ file_permissions=None, folder_permissions=None, plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, plex_password=None, plex_update=0, plex_notify=0,
songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, encoder_multicore=False, encoder_multicore_count=0, mpc_enabled=False, **kwargs ):
headphones.HTTP_HOST = http_host
@@ -1216,6 +1218,8 @@ class WebInterface(object):
headphones.PREFERRED_BITRATE_LOW_BUFFER = preferred_bitrate_low_buffer
headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS = preferred_bitrate_allow_lossless
headphones.DETECT_BITRATE = detect_bitrate
+ headphones.LOSSLESS_BITRATE_FROM = lossless_bitrate_from
+ headphones.LOSSLESS_BITRATE_TO = lossless_bitrate_to
headphones.MOVE_FILES = move_files
headphones.CORRECT_METADATA = correct_metadata
headphones.RENAME_FILES = rename_files