diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 3e0641a6..34e35d17 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -20,7 +20,7 @@

settingsSettings

- +
- - + + -
@@ -41,7 +41,7 @@ e.g. localhost or 0.0.0.0 - +
@@ -50,20 +50,20 @@
- +
-
- +
+
+
API -
+
@@ -73,14 +73,14 @@ Current API key: ${config['api_key']}
- +
Interval
mins
- +
mins @@ -100,7 +100,7 @@
- +
@@ -122,7 +122,7 @@
-
+
@@ -131,8 +131,8 @@
- - + +
@@ -146,14 +146,14 @@
-
+
- - + +
@@ -161,7 +161,7 @@ Folder your Download program watches for NZBs
- +
@@ -169,15 +169,15 @@ Full path where SAB or NZBget downloads your music. e.g. /Users/name/Downloads/music
- -
+ +
- - + +
Torrents -
+
Folder your Download program watches for Torrents @@ -191,15 +191,15 @@ Full path where your torrent client downloads your music e.g. /Users/name/Downloads/music -
+
- +
@@ -210,7 +210,7 @@ NZBs
Newznab -
+
@@ -264,7 +264,7 @@
NZBs.org -
+
@@ -276,8 +276,8 @@
NZBsRus -
- +
+
@@ -290,21 +290,15 @@
-
- nzbX -
- -
-
- +
Torrents -
- +
+
-
+
@@ -335,7 +329,7 @@
-
+
@@ -349,7 +343,7 @@
- + @@ -369,17 +363,17 @@ Preferred Bitrate: kbps
Reject if less than % or more than % of the target size (leave blank for no limit)

-
+
-
+
-
+
- +
Search Words @@ -408,13 +402,13 @@ - +
-
+
as .jpg -
- Use $Artist/$artist, $Album/$album, $Year/$year +
+ Use $Artist/$artist, $Album/$album, $Year/$year
@@ -439,7 +433,7 @@
- +
Renaming options
@@ -447,7 +441,7 @@ Use: $Artist/$artist, $Album/$album, $Year/$year, $Type/$type (release type) and $First/$first (first letter in artist name) E.g.: $Type/$First/$artist/$album [$year] = Album/G/girl talk/all day [2010] - +
@@ -455,7 +449,7 @@ Use: $Disc/$disc (disc #), $Track/$track (track #), $Title/$title, $Artist/$artist, $Album/$album and $Year/$year
- +
Re-Encoding Options Note: this option requires the lame, ffmpeg or xld encoder @@ -467,7 +461,7 @@
-
+
@@ -511,7 +505,7 @@ %endfor -
+
- - + + <% if config["samplingfrequency"] == 44100: freq44100 = 'selected="selected"' @@ -571,14 +565,14 @@
- +
Advanced Encoding Options
- +
@@ -593,7 +587,7 @@
- +
Miscellaneous
@@ -622,20 +616,20 @@
- +
-
+
-
+
Interface
- + - +
@@ -658,12 +652,12 @@
- - - -

Notifications

-
-

Prowl

+ + + +

Notifications

+
+

Prowl

@@ -678,12 +672,12 @@ - +
- - -
-

XBMC

+ + +
+

XBMC

@@ -693,7 +687,7 @@ e.g. http://localhost:8080. Separate hosts with commas -
+
@@ -707,7 +701,7 @@
- +

NotifyMyAndroid

@@ -731,7 +725,7 @@ nma_priority_selected = 'selected' else: nma_priority_selected = '' - + if x == -2: nma_priority_value = 'Very Low' elif x == -1: @@ -757,8 +751,8 @@
-
-

Pushover

+
+

Pushover

@@ -773,10 +767,10 @@ - +
- - + +
Musicbrainz
@@ -804,7 +798,7 @@
- +
@@ -815,12 +809,12 @@
- +
-
+
@@ -828,21 +822,21 @@ <%def name="javascriptIncludes()"> diff --git a/headphones/__init__.py b/headphones/__init__.py index 965c5e1b..4e863b54 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -158,8 +158,6 @@ NZBSRUS = False NZBSRUS_UID = None NZBSRUS_APIKEY = None -NZBX = False - PREFERRED_WORDS = None IGNORED_WORDS = None REQUIRED_WORDS = None @@ -294,7 +292,7 @@ def initialize(): RUTRACKER, RUTRACKER_USER, RUTRACKER_PASSWORD, WHATCD, WHATCD_USERNAME, WHATCD_PASSWORD, DOWNLOAD_TORRENT_DIR, \ LIBRARYSCAN, LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \ NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_HOST, NZBMATRIX, NZBMATRIX_USERNAME, NZBMATRIX_APIKEY, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, NEWZNAB_ENABLED, EXTRA_NEWZNABS, \ - NZBSORG, NZBSORG_UID, NZBSORG_HASH, NEWZBIN, NEWZBIN_UID, NEWZBIN_PASSWORD, NZBSRUS, NZBSRUS_UID, NZBSRUS_APIKEY, NZBX, \ + NZBSORG, NZBSORG_UID, NZBSORG_HASH, NEWZBIN, NEWZBIN_UID, NEWZBIN_PASSWORD, NZBSRUS, NZBSRUS_UID, NZBSRUS_APIKEY, \ NZB_DOWNLOADER, PREFERRED_WORDS, REQUIRED_WORDS, IGNORED_WORDS, \ LASTFM_USERNAME, INTERFACE, FOLDER_PERMISSIONS, ENCODERFOLDER, ENCODER_PATH, ENCODER, XLDPROFILE, BITRATE, SAMPLINGFREQUENCY, \ MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, ENCODERVBRCBR, ENCODERLOSSLESS, DELETE_LOSSLESS_FILES, \ @@ -315,7 +313,6 @@ def initialize(): CheckSection('Newznab') CheckSection('NZBsorg') CheckSection('NZBsRus') - CheckSection('nzbX') CheckSection('Newzbin') CheckSection('Waffles') CheckSection('Rutracker') @@ -421,7 +418,7 @@ def initialize(): NZBGET_PASSWORD = check_setting_str(CFG, 'NZBget', 'nzbget_password', '') NZBGET_CATEGORY = check_setting_str(CFG, 'NZBget', 'nzbget_category', '') NZBGET_HOST = check_setting_str(CFG, 'NZBget', 'nzbget_host', '') - + NZBMATRIX = bool(check_setting_int(CFG, 'NZBMatrix', 'nzbmatrix', 0)) NZBMATRIX_USERNAME = check_setting_str(CFG, 'NZBMatrix', 'nzbmatrix_username', '') NZBMATRIX_APIKEY = check_setting_str(CFG, 'NZBMatrix', 'nzbmatrix_apikey', '') @@ -446,9 +443,7 @@ def initialize(): NZBSRUS = bool(check_setting_int(CFG, 'NZBsRus', 'nzbsrus', 0)) NZBSRUS_UID = check_setting_str(CFG, 'NZBsRus', 'nzbsrus_uid', '') NZBSRUS_APIKEY = check_setting_str(CFG, 'NZBsRus', 'nzbsrus_apikey', '') - - NZBX = bool(check_setting_int(CFG, 'nzbX', 'nzbx', 0)) - + PREFERRED_WORDS = check_setting_str(CFG, 'General', 'preferred_words', '') IGNORED_WORDS = check_setting_str(CFG, 'General', 'ignored_words', '') REQUIRED_WORDS = check_setting_str(CFG, 'General', 'required_words', '') @@ -554,7 +549,7 @@ def initialize(): if ENCODERFOLDER: ENCODER_PATH = os.path.join(ENCODERFOLDER, ENCODER) CONFIG_VERSION = '3' - + if CONFIG_VERSION == '3': #Update the BLACKHOLE option to the NZB_DOWNLOADER format if BLACKHOLE: @@ -763,7 +758,7 @@ def config_write(): new_config['SABnzbd']['sab_password'] = SAB_PASSWORD new_config['SABnzbd']['sab_apikey'] = SAB_APIKEY new_config['SABnzbd']['sab_category'] = SAB_CATEGORY - + new_config['NZBget'] = {} new_config['NZBget']['nzbget_username'] = NZBGET_USERNAME new_config['NZBget']['nzbget_password'] = NZBGET_PASSWORD @@ -802,10 +797,7 @@ def config_write(): new_config['NZBsRus']['nzbsrus'] = int(NZBSRUS) new_config['NZBsRus']['nzbsrus_uid'] = NZBSRUS_UID new_config['NZBsRus']['nzbsrus_apikey'] = NZBSRUS_APIKEY - - new_config['nzbX'] = {} - new_config['nzbX']['nzbx'] = int(NZBX) - + new_config['General']['preferred_words'] = PREFERRED_WORDS new_config['General']['ignored_words'] = IGNORED_WORDS new_config['General']['required_words'] = REQUIRED_WORDS @@ -1069,17 +1061,17 @@ def dbcheck(): for artist in artists: if artist[1]: c.execute('UPDATE artists SET Extras=? WHERE ArtistID=?', ("1,2,3,4,5,6,7,8", artist[0])) - + try: c.execute('SELECT Kind from snatched') except sqlite3.OperationalError: c.execute('ALTER TABLE snatched ADD COLUMN Kind TEXT DEFAULT NULL') - + try: c.execute('SELECT SearchTerm from albums') except sqlite3.OperationalError: c.execute('ALTER TABLE albums ADD COLUMN SearchTerm TEXT DEFAULT NULL') - + conn.commit() c.close() diff --git a/headphones/searcher.py b/headphones/searcher.py index 8ec70247..9ee0ac30 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -80,7 +80,7 @@ def getNewzbinURL(url): f.close() return data - + def url_fix(s, charset='utf-8'): if isinstance(s, unicode): s = s.encode(charset, 'ignore') @@ -88,7 +88,7 @@ def url_fix(s, charset='utf-8'): path = urllib.quote(path, '/%') qs = urllib.quote_plus(qs, ':&=') return urlparse.urlunsplit((scheme, netloc, path, qs, anchor)) - + def patch_http_response_read(func): def inner(*args): try: @@ -98,20 +98,20 @@ def patch_http_response_read(func): return inner httplib.HTTPResponse.read = patch_http_response_read(httplib.HTTPResponse.read) - - + + def searchforalbum(albumid=None, new=False, lossless=False): - + if not albumid: myDB = db.DBConnection() - + results = myDB.select('SELECT AlbumID, Status from albums WHERE Status="Wanted" OR Status="Wanted Lossless"') new = True - + for result in results: foundNZB = "none" - if (headphones.NEWZNAB or headphones.NZBSORG or headphones.NZBX or headphones.NZBSRUS) and (headphones.SAB_HOST or headphones.BLACKHOLE_DIR or headphones.NZBGET_HOST): + if (headphones.NEWZNAB or headphones.NZBSORG or headphones.NZBSRUS) and (headphones.SAB_HOST or headphones.BLACKHOLE_DIR or headphones.NZBGET_HOST): if result['Status'] == "Wanted Lossless": foundNZB = searchNZB(result['AlbumID'], new, losslessOnly=True) else: @@ -123,11 +123,11 @@ def searchforalbum(albumid=None, new=False, lossless=False): searchTorrent(result['AlbumID'], new, losslessOnly=True) else: searchTorrent(result['AlbumID'], new) - - else: - + + else: + foundNZB = "none" - if (headphones.NZBMATRIX or headphones.NEWZNAB or headphones.NZBSORG or headphones.NEWZBIN or headphones.NZBX or headphones.NZBSRUS) and (headphones.SAB_HOST or headphones.BLACKHOLE_DIR or headphones.NZBGET_HOST): + if (headphones.NZBMATRIX or headphones.NEWZNAB or headphones.NZBSORG or headphones.NEWZBIN or headphones.NZBSRUS) and (headphones.SAB_HOST or headphones.BLACKHOLE_DIR or headphones.NZBGET_HOST): foundNZB = searchNZB(albumid, new, lossless) if (headphones.KAT or headphones.ISOHUNT or headphones.MININOVA or headphones.WAFFLES or headphones.RUTRACKER or headphones.WHATCD) and foundNZB == "none": @@ -136,32 +136,32 @@ def searchforalbum(albumid=None, new=False, lossless=False): def searchNZB(albumid=None, new=False, losslessOnly=False): myDB = db.DBConnection() - + if albumid: results = myDB.select('SELECT ArtistName, AlbumTitle, AlbumID, ReleaseDate, Type, SearchTerm from albums WHERE AlbumID=?', [albumid]) else: results = myDB.select('SELECT ArtistName, AlbumTitle, AlbumID, ReleaseDate, Type, SearchTerm from albums WHERE Status="Wanted" OR Status="Wanted Lossless"') new = True - + for albums in results: - + albumid = albums[2] reldate = albums[3] - + try: year = reldate[:4] except TypeError: year = '' - + dic = {'...':'', ' & ':' ', ' = ': ' ', '?':'', '$':'s', ' + ':' ', '"':'', ',':'', '*':'', '.':'', ':':''} cleanalbum = helpers.latinToAscii(helpers.replace_all(albums[1], dic)).strip() cleanartist = helpers.latinToAscii(helpers.replace_all(albums[0], dic)).strip() - + # Use the provided search term if available, otherwise build a search term if albums[5]: term = albums[5] - + else: # FLAC usually doesn't have a year for some reason so I'll leave it out # Various Artist albums might be listed as VA, so I'll leave that out too @@ -172,24 +172,24 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): term = cleanalbum + ' ' + year else: term = cleanartist + ' ' + cleanalbum - + # Replace bad characters in the term and unicode it term = re.sub('[\.\-\/]', ' ', term).encode('utf-8') - + artistterm = re.sub('[\.\-\/]', ' ', cleanartist).encode('utf-8') - + logger.info("Searching for %s since it was marked as wanted" % term) - + resultlist = [] - + if headphones.NEWZNAB: - + newznab_hosts = [(headphones.NEWZNAB_HOST, headphones.NEWZNAB_APIKEY, headphones.NEWZNAB_ENABLED)] - + for newznab_host in headphones.EXTRA_NEWZNABS: if newznab_host[2] == '1' or newznab_host[2] == 1: newznab_hosts.append(newznab_host) - + provider = "newznab" if headphones.PREFERRED_QUALITY == 3 or losslessOnly: categories = "3040" @@ -197,13 +197,13 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): categories = "3040,3010" else: categories = "3010" - + if albums['Type'] == 'Other': categories = "3030" logger.info("Album type is audiobook/spokenword. Using audiobook category") - + for newznab_host in newznab_hosts: - + # Add a little mod for kere.ws if newznab_host[0] == "http://kere.ws": if categories == "3040": @@ -221,43 +221,43 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): "maxage": headphones.USENET_RETENTION, "q": term } - + searchURL = newznab_host[0] + '/api?' + urllib.urlencode(params) - + # Add a user-agent request = urllib2.Request(searchURL) request.add_header('User-Agent', 'headphones/0.0 +https://github.com/rembo10/headphones') opener = urllib2.build_opener() - + logger.info(u'Parsing results from %s' % (searchURL, newznab_host[0])) - + try: data = opener.open(request).read() except Exception, e: logger.warn('Error fetching data from %s: %s' % (newznab_host[0], e)) data = False - + if data: - + d = feedparser.parse(data) - + if not len(d.entries): logger.info(u"No results found from %s for %s" % (newznab_host[0], term)) pass - + else: for item in d.entries: try: url = item.link title = item.title size = int(item.links[1]['length']) - + resultlist.append((title, size, url, provider)) - logger.info('Found %s. Size: %s' % (title, helpers.bytes_to_mb(size))) - + logger.info('Found %s. Size: %s' % (title, helpers.bytes_to_mb(size))) + except Exception, e: logger.error(u"An unknown error occurred trying to parse the feed: %s" % e) - + if headphones.NZBSORG: provider = "nzbsorg" if headphones.PREFERRED_QUALITY == 3 or losslessOnly: @@ -266,7 +266,7 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): categories = "3040,3010" else: categories = "3010" - + if albums['Type'] == 'Other': categories = "3030" logger.info("Album type is audiobook/spokenword. Using audiobook category") @@ -277,50 +277,50 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): "maxage": headphones.USENET_RETENTION, "q": term } - + searchURL = 'http://beta.nzbs.org/api?' + urllib.urlencode(params) - + logger.info(u'Parsing results from nzbs.org' % searchURL) - + try: data = urllib2.urlopen(searchURL, timeout=20).read() except urllib2.URLError, e: logger.warn('Error fetching data from nzbs.org: %s' % e) data = False - + if data: - + d = feedparser.parse(data) - + if not len(d.entries): logger.info(u"No results found from nzbs.org for %s" % term) pass - + else: for item in d.entries: try: url = item.link title = item.title size = int(item.links[1]['length']) - + resultlist.append((title, size, url, provider)) logger.info('Found %s. Size: %s' % (title, helpers.bytes_to_mb(size))) - + except Exception, e: logger.error(u"An unknown error occurred trying to parse the feed: %s" % e) - + if headphones.NZBSRUS: - + provider = "nzbsrus" categories = "54" - + if headphones.PREFERRED_QUALITY == 3 or losslessOnly: sub = "16" elif headphones.PREFERRED_QUALITY: sub = "" else: sub = "15" - + if albums['Type'] == 'Other': sub = "" logger.info("Album type is audiobook/spokenword. Searching all music categories") @@ -332,89 +332,40 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): "age": headphones.USENET_RETENTION, "searchtext": term } - + searchURL = 'https://www.nzbsrus.com/api.php?' + urllib.urlencode(params) - + # Add a user-agent request = urllib2.Request(searchURL) request.add_header('User-Agent', 'headphones/0.0 +https://github.com/rembo10/headphones') opener = urllib2.build_opener() - + logger.info(u'Parsing results from NZBsRus' % searchURL) - + try: data = opener.open(request).read() except Exception, e: logger.warn('Error fetching data from NZBsRus: %s' % e) data = False - + if data: - + d = json.loads(data) - + if d['matches'] <= 0: logger.info(u"No results found from NZBsRus for %s" % term) pass - + else: for item in d['results']: try: url = "http://www.nzbsrus.com/nzbdownload_rss.php/" + item['id'] + "/" + headphones.NZBSRUS_UID + "/" + item['key'] title = item['name'] size = int(item['size']) - - resultlist.append((title, size, url, provider)) - logger.info('Found %s. Size: %s' % (title, helpers.bytes_to_mb(size))) - - except Exception, e: - logger.error(u"An unknown error occurred trying to parse the feed: %s" % e) - - if headphones.NZBX: - provider = "nzbx" - if headphones.PREFERRED_QUALITY == 3 or losslessOnly: - categories = "3040" - elif headphones.PREFERRED_QUALITY: - categories = "3040,3010" - else: - categories = "3010" - - if albums['Type'] == 'Other': - categories = "3030" - logger.info("Album type is audiobook/spokenword. Using audiobook category") - params = { "source" : "headphones", - "cat": categories, - "q": term - } - - searchURL = 'https://nzbx.co/api/search?' + urllib.urlencode(params) - - logger.info(u'Parsing results from nzbx.co' % searchURL) - - try: - data = urllib2.urlopen(searchURL, timeout=20).read() - except urllib2.URLError, e: - logger.warn('Error fetching data from nzbx.co: %s' % str(e)) - data = False - - if data: - - d = json.loads(data) - - if not len(d): - logger.info(u"No results found from nzbx.co for %s" % term) - pass - - else: - for item in d: - try: - url = item['nzb'] - title = item['name'] - size = item['size'] - resultlist.append((title, size, url, provider)) logger.info('Found %s. Size: %s' % (title, helpers.bytes_to_mb(size))) - + except Exception, e: logger.error(u"An unknown error occurred trying to parse the feed: %s" % e) @@ -427,9 +378,9 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): if len(resultlist): resultlist[:] = [result for result in resultlist if verifyresult(result[0], artistterm, term, losslessOnly)] - - if len(resultlist): - + + if len(resultlist): + # Add a priority if it has any of the preferred words temp_list = [] for result in resultlist: @@ -437,9 +388,9 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): temp_list.append((result[0],result[1],result[2],result[3],1)) else: temp_list.append((result[0],result[1],result[2],result[3],0)) - + resultlist = temp_list - + if headphones.PREFERRED_QUALITY == 2 and headphones.PREFERRED_BITRATE: logger.debug('Target bitrate: %s kbps' % headphones.PREFERRED_BITRATE) @@ -450,16 +401,16 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): albumlength = sum([pair[0] for pair in tracks]) targetsize = albumlength/1000 * int(headphones.PREFERRED_BITRATE) * 128 - + if not targetsize: logger.info('No track information for %s - %s. Defaulting to highest quality' % (albums[0], albums[1])) nzblist = sorted(resultlist, key=lambda title: (-title[4] , -title[1])) - + else: logger.info('Target size: %s' % helpers.bytes_to_mb(targetsize)) newlist = [] flac_list = [] - + if headphones.PREFERRED_BITRATE_HIGH_BUFFER: high_size_limit = targetsize * int(headphones.PREFERRED_BITRATE_HIGH_BUFFER)/100 else: @@ -468,69 +419,69 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): low_size_limit = targetsize * int(headphones.PREFERRED_BITRATE_LOW_BUFFER)/100 else: low_size_limit = None - + for result in resultlist: - + if high_size_limit and (result[1] > high_size_limit): logger.info(result[0] + " is too large for this album - not considering it. (Size: " + helpers.bytes_to_mb(result[1]) + ", Maxsize: " + helpers.bytes_to_mb(high_size_limit)) - + # Add lossless nzbs to the "flac list" which we can use if there are no good lossy matches if 'flac' in result[0].lower(): flac_list.append((result[0], result[1], result[2], result[3], result[4])) - + continue - + if low_size_limit and (result[1] < low_size_limit): logger.info(result[0] + " is too small for this album - not considering it. (Size: " + helpers.bytes_to_mb(result[1]) + ", Minsize: " + helpers.bytes_to_mb(low_size_limit)) continue - + delta = abs(targetsize - result[1]) newlist.append((result[0], result[1], result[2], result[3], result[4], delta)) - + nzblist = sorted(newlist, key=lambda title: (-title[4], title[5])) - + if not len(nzblist) and len(flac_list) and headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS: logger.info("Since there were no appropriate lossy matches (and at least one lossless match), going to use lossless instead") nzblist = sorted(flac_list, key=lambda title: (-title[4], -title[1])) - + except Exception, e: - + logger.debug('Error: %s' % str(e)) logger.info('No track information for %s - %s. Defaulting to highest quality' % (albums[0], albums[1])) - + nzblist = sorted(resultlist, key=lambda title: (-title[4], -title[1])) - + else: - + nzblist = sorted(resultlist, key=lambda title: (-title[4], -title[1])) - - + + if new: - + while True: - + if len(nzblist): - + alreadydownloaded = myDB.select('SELECT * from snatched WHERE URL=?', [nzblist[0][2]]) - + if len(alreadydownloaded): logger.info('%s has already been downloaded. Skipping.' % nzblist[0][0]) nzblist.pop(0) - + else: break else: logger.info('No more results found for %s' % term) return "none" - + if not len(nzblist): logger.info('No appropriate matches found for %s' % term) return "none" logger.info(u"Pre-processing result") - + (data, bestqual) = preprocess(nzblist) - + if data and bestqual: logger.info(u'Found best result: %s - %s' % (bestqual[2], bestqual[0], helpers.bytes_to_mb(bestqual[1]))) # Get rid of any dodgy chars here so we can prevent sab from renaming our downloads @@ -548,7 +499,7 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): nzb.extraInfo.append(data) nzb.name = nzb_folder_name sab.sendNZB(nzb) - + # If we sent the file to sab, we can check how it was renamed and insert that into the snatched table (replace_spaces, replace_dots) = sab.checkConfig() @@ -558,7 +509,7 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): nzb_folder_name = helpers.sab_replace_spaces(nzb_folder_name) else: - + nzb_name = nzb_folder_name + '.nzb' download_path = os.path.join(headphones.BLACKHOLE_DIR, nzb_name) try: @@ -571,23 +522,23 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): except Exception, e: logger.error('Couldn\'t write NZB file: %s' % e) break - + myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [albums[2]]) myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?, ?)', [albums[2], bestqual[0], bestqual[1], bestqual[2], "Snatched", nzb_folder_name, "nzb"]) return "found" else: return "none" - else: + else: return "none" def verifyresult(title, artistterm, term, lossless): - + title = re.sub('[\.\-\/\_]', ' ', title) - + #if artistterm != 'Various Artists': - # + # # if not re.search('^' + re.escape(artistterm), title, re.IGNORECASE): # #logger.info("Removed from results: " + title + " (artist not at string start).") # #return False @@ -599,29 +550,29 @@ def verifyresult(title, artistterm, term, lossless): # return False #another attempt to weed out substrings. We don't want "Vol III" when we were looking for "Vol II" - + # Filter out remix search results (if we're not looking for it) if 'remix' not in term.lower() and 'remix' in title.lower(): logger.info("Removed " + title + " from results because it's a remix album and we're not looking for a remix album right now") return False - + # Filter out FLAC if we're not specifically looking for it if headphones.PREFERRED_QUALITY == (0 or '0') and 'flac' in title.lower() and not lossless: logger.info("Removed " + title + " from results because it's a lossless album and we're not looking for a lossless album right now") return False - + if headphones.IGNORED_WORDS: for each_word in helpers.split_string(headphones.IGNORED_WORDS): if each_word.lower() in title.lower(): logger.info("Removed " + title + " from results because it contains ignored word: '" + each_word + "'") return False - + if headphones.REQUIRED_WORDS: for each_word in helpers.split_string(headphones.REQUIRED_WORDS): if each_word.lower() not in title.lower(): logger.info("Removed " + title + " from results because it doesn't contain required word: '" + each_word + "'") return False - + tokens = re.split('\W', term, re.IGNORECASE | re.UNICODE) for token in tokens: @@ -637,13 +588,13 @@ def verifyresult(title, artistterm, term, lossless): if not not re.search('(?:\W|^)+' + dumbtoken + '(?:\W|$)+', title, re.IGNORECASE | re.UNICODE): logger.info("Removed from results: " + title + " (missing tokens: " + token + " and " + cleantoken + ")") return False - + return True def getresultNZB(result): - + nzb = None - + if result[3] == 'newzbin': params = urllib.urlencode({"username": headphones.NEWZBIN_UID, "password": headphones.NEWZBIN_PASSWORD, "reportid": result[2]}) url = "https://www.newzbin2.es" + "/api/dnzb/" @@ -662,24 +613,24 @@ def getresultNZB(result): request = urllib2.Request(result[2]) request.add_header('User-Agent', 'headphones/0.0 +https://github.com/rembo10/headphones') opener = urllib2.build_opener() - + try: nzb = opener.open(request).read() except Exception, e: logger.warn('Error fetching nzb from url: ' + result[2] + ' %s' % e) return nzb - + def preprocess(resultlist): if not headphones.USENET_RETENTION: usenet_retention = 2000 else: usenet_retention = int(headphones.USENET_RETENTION) - + for result in resultlist: nzb = getresultNZB(result) if nzb: - try: + try: d = minidom.parseString(nzb) node = d.documentElement nzbfiles = d.getElementsByTagName("file") @@ -707,42 +658,42 @@ def preprocess(resultlist): def searchTorrent(albumid=None, new=False, losslessOnly=False): myDB = db.DBConnection() - + if albumid: results = myDB.select('SELECT ArtistName, AlbumTitle, AlbumID, ReleaseDate, SearchTerm from albums WHERE AlbumID=?', [albumid]) else: results = myDB.select('SELECT ArtistName, AlbumTitle, AlbumID, ReleaseDate, SearchTerm from albums WHERE Status="Wanted" OR Status="Wanted Lossless"') new = True - + # rutracker login - + if headphones.RUTRACKER and results: rulogin = rutracker.login(headphones.RUTRACKER_USER, headphones.RUTRACKER_PASSWORD) if not rulogin: logger.info(u'Could not login to rutracker, search results will exclude this provider') - + for albums in results: - + albumid = albums[2] reldate = albums[3] - + try: year = reldate[:4] except TypeError: year = '' - + dic = {'...':'', ' & ':' ', ' = ': ' ', '?':'', '$':'s', ' + ':' ', '"':'', ',':' ', '*':''} semi_cleanalbum = helpers.replace_all(albums[1], dic) cleanalbum = helpers.latinToAscii(semi_cleanalbum) semi_cleanartist = helpers.replace_all(albums[0], dic) cleanartist = helpers.latinToAscii(semi_cleanartist) - + # Use provided term if available, otherwise build our own (this code needs to be cleaned up since a lot # of these torrent providers are just using cleanartist/cleanalbum terms if albums[4]: term = albums[4] - + else: # FLAC usually doesn't have a year for some reason so I'll leave it out # Various Artist albums might be listed as VA, so I'll leave that out too @@ -768,7 +719,7 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): albumterm = re.sub('[\.\-\/]', ' ', cleanalbum).encode('utf-8', 'replace') logger.info("Searching torrents for %s since it was marked as wanted" % term) - + resultlist = [] pre_sorted_results = False minimumseeders = int(headphones.NUMBEROFSEEDERS) - 1 @@ -787,32 +738,32 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): else: categories = "7" #music format = "8" #mp3 - maxsize = 300000000 + maxsize = 300000000 - params = { + params = { "categories[0]": "music", "field": "seeders", "sorder": "desc", "rss": "1" } searchURL = providerurl + "/?%s" % urllib.urlencode(params) - + try: data = urllib2.urlopen(searchURL, timeout=20) except urllib2.URLError, e: logger.warn('Error fetching data from %s: %s' % (provider, e)) data = False - + if data: - + logger.info(u'Parsing results from KAT' % searchURL) - + d = feedparser.parse(data) if not len(d.entries): logger.info(u"No results found from %s for %s" % (provider, term)) pass - + else: for item in d.entries: try: @@ -841,8 +792,8 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): resultlist.append((title, size, url, provider)) logger.info('Found %s. Size: %s' % (title, helpers.bytes_to_mb(size))) else: - logger.info('%s is larger than the maxsize, the wrong format or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i, Format: %s)' % (title, size, int(seeders), rightformat)) - + logger.info('%s is larger than the maxsize, the wrong format or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i, Format: %s)' % (title, size, int(seeders), rightformat)) + except Exception, e: logger.error(u"An unknown error occurred in the KAT parser: %s" % e) @@ -894,9 +845,9 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): data = False if data: - + logger.info(u'Parsing results from Waffles.fm' % searchURL) - + d = feedparser.parse(data) if not len(d.entries): logger.info(u"No results found from %s for %s" % (provider, term)) @@ -918,21 +869,21 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): logger.info('Found %s. Size: %s' % (title, helpers.bytes_to_mb(size))) except Exception, e: logger.error(u"An error occurred while trying to parse the response from Waffles.fm: %s" % e) - + # rutracker.org - + if headphones.RUTRACKER and rulogin: - + provider = "rutracker.org" - + # Ignore if release date not specified, results too unpredictable - + if not year and not usersearchterm: logger.info(u'Release date not specified, ignoring for rutracker.org') else: - + bitrate = False - + if headphones.PREFERRED_QUALITY == 3 or losslessOnly: format = 'lossless' maxsize = 10000000000 @@ -944,7 +895,7 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): maxsize = 300000000 if headphones.PREFERRED_QUALITY == 2 and headphones.PREFERRED_BITRATE: bitrate = True - + # build search url based on above if not usersearchterm: @@ -953,13 +904,13 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): searchURL = rutracker.searchurl(usersearchterm, ' ', ' ', format) logger.info(u'Parsing results from rutracker.org' % searchURL) - + # parse results and get best match - + rulist = rutracker.search(searchURL, maxsize, minimumseeders, albumid, bitrate) - + # add best match to overall results list - + if rulist: for ru in rulist: title = ru[0].decode('utf-8') @@ -1046,7 +997,7 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): provider)) if headphones.ISOHUNT: - provider = "isoHunt" + provider = "isoHunt" providerurl = url_fix("http://isohunt.com/js/rss/" + term) if headphones.PREFERRED_QUALITY == 3 or losslessOnly: categories = "7" #music @@ -1059,29 +1010,29 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): else: categories = "7" #music format = "8" #mp3 - maxsize = 300000000 + maxsize = 300000000 - params = { + params = { "iht": "2", "sort": "seeds" } searchURL = providerurl + "?%s" % urllib.urlencode(params) - + try: data = urllib2.urlopen(searchURL, timeout=20).read() except urllib2.URLError, e: logger.warn('Error fetching data from %s: %s' % (provider, e)) data = False - + if data: - + logger.info(u'Parsing results from isoHunt' % searchURL) - + d = feedparser.parse(data) if not len(d.entries): logger.info(u"No results found from %s for %s" % (provider, term)) pass - + else: for item in d.entries: try: @@ -1114,13 +1065,13 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): resultlist.append((title, size, url, provider)) logger.info('Found %s. Size: %s' % (title, helpers.bytes_to_mb(size))) else: - logger.info('%s is larger than the maxsize, the wrong format or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i, Format: %s)' % (title, size, int(seeds), rightformat)) - + logger.info('%s is larger than the maxsize, the wrong format or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i, Format: %s)' % (title, size, int(seeds), rightformat)) + except Exception, e: logger.error(u"An unknown error occurred in the isoHunt parser: %s" % e) if headphones.MININOVA: - provider = "Mininova" + provider = "Mininova" providerurl = url_fix("http://www.mininova.org/rss/" + term + "/5") if headphones.PREFERRED_QUALITY == 3 or losslessOnly: categories = "7" #music @@ -1133,25 +1084,25 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): else: categories = "7" #music format = "8" #mp3 - maxsize = 300000000 + maxsize = 300000000 + + searchURL = providerurl - searchURL = providerurl - try: data = urllib2.urlopen(searchURL, timeout=20).read() except urllib2.URLError, e: logger.warn('Error fetching data from %s: %s' % (provider, e)) data = False - + if data: - + logger.info(u'Parsing results from Mininova' % searchURL) - + d = feedparser.parse(data) if not len(d.entries): logger.info(u"No results found from %s for %s" % (provider, term)) pass - + else: for item in d.entries: try: @@ -1183,8 +1134,8 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): resultlist.append((title, size, url, provider)) logger.info('Found %s. Size: %s' % (title, helpers.bytes_to_mb(size))) else: - logger.info('%s is larger than the maxsize, the wrong format or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i, Format: %s)' % (title, size, int(seeds), rightformat)) - + logger.info('%s is larger than the maxsize, the wrong format or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i, Format: %s)' % (title, size, int(seeds), rightformat)) + except Exception, e: logger.error(u"An unknown error occurred in the Mininova Parser: %s" % e) @@ -1195,9 +1146,9 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): #this should be less of an issue when it isn't a self-titled album so we'll only check vs artist if len(resultlist): resultlist[:] = [result for result in resultlist if verifyresult(result[0], artistterm, term, losslessOnly)] - - if len(resultlist): - + + if len(resultlist): + if headphones.PREFERRED_QUALITY == 2 and headphones.PREFERRED_BITRATE and not pre_sorted_results: logger.debug('Target bitrate: %s kbps' % headphones.PREFERRED_BITRATE) @@ -1209,43 +1160,43 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): targetsize = albumlength/1000 * int(headphones.PREFERRED_BITRATE) * 128 logger.info('Target size: %s' % helpers.bytes_to_mb(targetsize)) - + newlist = [] for result in resultlist: delta = abs(targetsize - result[1]) newlist.append((result[0], result[1], result[2], result[3], delta)) - + torrentlist = sorted(newlist, key=lambda title: title[4]) - + except Exception, e: - + logger.debug('Error: %s' % str(e)) logger.info('No track information for %s - %s. Defaulting to highest quality' % (albums[0], albums[1])) - + torrentlist = sorted(resultlist, key=lambda title: title[1], reverse=True) elif pre_sorted_results: torrentlist = resultlist - + else: - + torrentlist = sorted(resultlist, key=lambda title: title[1], reverse=True) - - + + if new: - + while True: - + if len(torrentlist): - + alreadydownloaded = myDB.select('SELECT * from snatched WHERE URL=?', [torrentlist[0][2]]) - + if len(alreadydownloaded): logger.info('%s has already been downloaded. Skipping.' % torrentlist[0][0]) torrentlist.pop(0) - + else: break else: @@ -1253,12 +1204,12 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): return logger.info(u"Pre-processing result") - + (data, bestqual) = preprocesstorrent(torrentlist, pre_sorted_results) - + if data and bestqual: logger.info(u'Found best result from %s: %s - %s' % (bestqual[3], bestqual[2], bestqual[0], helpers.bytes_to_mb(bestqual[1]))) - torrent_folder_name = '%s - %s [%s]' % (helpers.latinToAscii(albums[0]).encode('UTF-8').replace('/', '_'), helpers.latinToAscii(albums[1]).encode('UTF-8').replace('/', '_'), year) + torrent_folder_name = '%s - %s [%s]' % (helpers.latinToAscii(albums[0]).encode('UTF-8').replace('/', '_'), helpers.latinToAscii(albums[1]).encode('UTF-8').replace('/', '_'), year) if headphones.TORRENTBLACKHOLE_DIR == "sendtracker": torrent = classes.TorrentDataSearchResult() @@ -1267,7 +1218,7 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): sab.sendTorrent(torrent) elif headphones.TORRENTBLACKHOLE_DIR != "": - + # Get torrent name from .torrent, this is usually used by the torrent client as the folder name torrent_name = torrent_folder_name + '.torrent' @@ -1277,14 +1228,14 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): download_path = rutracker.get_torrent(bestqual[2], headphones.TORRENTBLACKHOLE_DIR) if not download_path: break - else: + else: #Write the torrent file to a path derived from the TORRENTBLACKHOLE_DIR and file name. prev = os.umask(headphones.UMASK) torrent_file = open(download_path, 'wb') torrent_file.write(data) torrent_file.close() os.umask(prev) - + #Open the fresh torrent file again so we can extract the proper torrent name #Used later in post-processing. torrent_file = open(download_path, 'rb') @@ -1295,7 +1246,7 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): except Exception, e: logger.error('Couldn\'t get name from Torrent file: %s' % e) break - + myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [albums[2]]) myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?, ?)', [albums[2], bestqual[0], bestqual[1], bestqual[2], "Snatched", torrent_folder_name, "torrent"]) @@ -1306,12 +1257,12 @@ def preprocesstorrent(resultlist, pre_sorted_list=False): selresult = result elif int(selresult[1]) < int(result[1]): # if size is lower than new result replace previous selected result (bigger size = better quality?) selresult = result - + # get outta here if rutracker - + if selresult[3] == 'rutracker.org': return True, selresult - + if pre_sorted_list: selresult = resultlist[0] else: @@ -1324,10 +1275,10 @@ def preprocesstorrent(resultlist, pre_sorted_list=False): try: request = urllib2.Request(selresult[2]) request.add_header('Accept-encoding', 'gzip') - + if selresult[3] == 'Kick Ass Torrent': request.add_header('Referer', 'http://kat.ph/') - + response = urllib2.urlopen(request) if response.info().get('Content-Encoding') == 'gzip': buf = StringIO(response.read()) @@ -1337,5 +1288,5 @@ def preprocesstorrent(resultlist, pre_sorted_list=False): torrent = response.read() except ExpatError: logger.error('Unable to torrent %s' % selresult[2]) - + return torrent, selresult diff --git a/headphones/webserve.py b/headphones/webserve.py index 1f8167ab..fac897f9 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -40,17 +40,17 @@ def serve_template(templatename, **kwargs): interface_dir = os.path.join(str(headphones.PROG_DIR), 'data/interfaces/') template_dir = os.path.join(str(interface_dir), headphones.INTERFACE) - + _hplookup = TemplateLookup(directories=[template_dir]) - + try: template = _hplookup.get_template(templatename) return template.render(**kwargs) except: return exceptions.html_error_template().render() - + class WebInterface(object): - + def index(self): raise cherrypy.HTTPRedirect("home") index.exposed=True @@ -65,11 +65,11 @@ class WebInterface(object): myDB = db.DBConnection() artist = myDB.action('SELECT * FROM artists WHERE ArtistID=?', [ArtistID]).fetchone() albums = myDB.select('SELECT * from albums WHERE ArtistID=? order by ReleaseDate DESC', [ArtistID]) - - # Don't redirect to the artist page until it has the bare minimum info inserted - # Redirect to the home page if we still can't get it after 5 seconds + + # Don't redirect to the artist page until it has the bare minimum info inserted + # Redirect to the home page if we still can't get it after 5 seconds retry = 0 - + while retry < 5: if not artist: time.sleep(1) @@ -77,19 +77,19 @@ class WebInterface(object): retry += 1 else: break - + if not artist: raise cherrypy.HTTPRedirect("home") - + # Serve the extras up as a dict to make things easier for new templates extras_list = ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook"] extras_dict = {} - + if not artist['Extras']: artist_extras = "" else: artist_extras = artist['Extras'] - + i = 1 for extra in extras_list: if str(i) in artist_extras: @@ -100,8 +100,8 @@ class WebInterface(object): return serve_template(templatename="artist.html", title=artist['ArtistName'], artist=artist, albums=albums, extras=extras_dict) artistPage.exposed = True - - + + def albumPage(self, AlbumID): myDB = db.DBConnection() album = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone() @@ -110,8 +110,8 @@ class WebInterface(object): title = album['ArtistName'] + ' - ' + album['AlbumTitle'] return serve_template(templatename="album.html", title=title, album=album, tracks=tracks, description=description) albumPage.exposed = True - - + + def search(self, name, type): if len(name) == 0: raise cherrypy.HTTPRedirect("home") @@ -127,7 +127,7 @@ class WebInterface(object): threading.Thread(target=lastfm.getSimilar).start() raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % artistid) addArtist.exposed = True - + def getExtras(self, ArtistID, newstyle=False, **kwargs): # if calling this function without the newstyle, they're using the old format # which doesn't separate extras, so we'll grab all of them @@ -144,7 +144,7 @@ class WebInterface(object): temp_extras_list.append(i) i += 1 extras = ','.join(str(n) for n in temp_extras_list) - + myDB = db.DBConnection() controlValueDict = {'ArtistID': ArtistID} newValueDict = {'IncludeExtras': 1, @@ -153,7 +153,7 @@ class WebInterface(object): threading.Thread(target=importer.addArtisttoDB, args=[ArtistID, True]).start() raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) getExtras.exposed = True - + def removeExtras(self, ArtistID): myDB = db.DBConnection() controlValueDict = {'ArtistID': ArtistID} @@ -165,7 +165,7 @@ class WebInterface(object): myDB.action('DELETE from albums WHERE ArtistID=? AND AlbumID=?', [ArtistID, album['AlbumID']]) raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) removeExtras.exposed = True - + def pauseArtist(self, ArtistID): logger.info(u"Pausing artist: " + ArtistID) myDB = db.DBConnection() @@ -174,7 +174,7 @@ class WebInterface(object): myDB.upsert("artists", newValueDict, controlValueDict) raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) pauseArtist.exposed = True - + def resumeArtist(self, ArtistID): logger.info(u"Resuming artist: " + ArtistID) myDB = db.DBConnection() @@ -183,7 +183,7 @@ class WebInterface(object): myDB.upsert("artists", newValueDict, controlValueDict) raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) resumeArtist.exposed = True - + def deleteArtist(self, ArtistID): logger.info(u"Deleting all traces of artist: " + ArtistID) myDB = db.DBConnection() @@ -195,7 +195,7 @@ class WebInterface(object): myDB.action('INSERT OR REPLACE into blacklist VALUES (?)', [ArtistID]) raise cherrypy.HTTPRedirect("home") deleteArtist.exposed = True - + def deleteEmptyArtists(self): logger.info(u"Deleting all empty artists") myDB = db.DBConnection() @@ -208,13 +208,13 @@ class WebInterface(object): myDB.action('DELETE from allalbums WHERE ArtistID=?', [ArtistID]) myDB.action('DELETE from alltracks WHERE ArtistID=?', [ArtistID]) myDB.action('INSERT OR REPLACE into blacklist VALUES (?)', [ArtistID]) - deleteEmptyArtists.exposed = True + deleteEmptyArtists.exposed = True def refreshArtist(self, ArtistID): - threading.Thread(target=importer.addArtisttoDB, args=[ArtistID]).start() + threading.Thread(target=importer.addArtisttoDB, args=[ArtistID]).start() raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) - refreshArtist.exposed=True - + refreshArtist.exposed=True + def markAlbums(self, ArtistID=None, action=None, **args): myDB = db.DBConnection() if action == 'WantedNew' or action == 'WantedLossless': @@ -237,12 +237,12 @@ class WebInterface(object): else: raise cherrypy.HTTPRedirect("upcoming") markAlbums.exposed = True - + def addArtists(self, **args): threading.Thread(target=importer.artistlist_to_mbids, args=[args, True]).start() raise cherrypy.HTTPRedirect("home") addArtists.exposed = True - + def queueAlbum(self, AlbumID, ArtistID=None, new=False, redirect=None, lossless=False): logger.info(u"Marking album: " + AlbumID + " as wanted...") myDB = db.DBConnection() @@ -250,7 +250,7 @@ class WebInterface(object): if lossless: newValueDict = {'Status': 'Wanted Lossless'} logger.info("...lossless only!") - else: + else: newValueDict = {'Status': 'Wanted'} myDB.upsert("albums", newValueDict, controlValueDict) searcher.searchforalbum(AlbumID, new) @@ -268,7 +268,7 @@ class WebInterface(object): myDB.upsert("albums", newValueDict, controlValueDict) raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) unqueueAlbum.exposed = True - + def deleteAlbum(self, AlbumID, ArtistID=None): logger.info(u"Deleting all traces of album: " + AlbumID) myDB = db.DBConnection() @@ -279,7 +279,7 @@ class WebInterface(object): else: raise cherrypy.HTTPRedirect("home") deleteAlbum.exposed = True - + def switchAlbum(self, AlbumID, ReleaseID): ''' Take the values from allalbums/alltracks (based on the ReleaseID) and swap it into the album & track tables @@ -288,7 +288,7 @@ class WebInterface(object): albumswitcher.switch(AlbumID, ReleaseID) raise cherrypy.HTTPRedirect("albumPage?AlbumID=%s" % AlbumID) switchAlbum.exposed = True - + def editSearchTerm(self, AlbumID, SearchTerm): logger.info(u"Updating search term for albumid: " + AlbumID) myDB = db.DBConnection() @@ -304,19 +304,19 @@ class WebInterface(object): wanted = myDB.select("SELECT * from albums WHERE Status='Wanted'") return serve_template(templatename="upcoming.html", title="Upcoming", upcoming=upcoming, wanted=wanted) upcoming.exposed = True - + def manage(self): myDB = db.DBConnection() emptyArtists = myDB.select("SELECT * FROM artists WHERE LatestAlbum IS NULL") return serve_template(templatename="manage.html", title="Manage", emptyArtists=emptyArtists) manage.exposed = True - + def manageArtists(self): myDB = db.DBConnection() artists = myDB.select('SELECT * from artists order by ArtistSortName COLLATE NOCASE') return serve_template(templatename="manageartists.html", title="Manage Artists", artists=artists) manageArtists.exposed = True - + def manageAlbums(self, Status=None): myDB = db.DBConnection() if Status == "Upcoming": @@ -327,13 +327,13 @@ class WebInterface(object): albums = myDB.select('SELECT * from albums') return serve_template(templatename="managealbums.html", title="Manage Albums", albums=albums) manageAlbums.exposed = True - + def manageNew(self): myDB = db.DBConnection() newartists = myDB.select('SELECT * from newartists') return serve_template(templatename="managenew.html", title="Manage New Artists", newartists=newartists) - manageNew.exposed = True - + manageNew.exposed = True + def markArtists(self, action=None, **args): myDB = db.DBConnection() artistsToAdd = [] @@ -350,7 +350,7 @@ class WebInterface(object): elif action == 'resume': controlValueDict = {'ArtistID': ArtistID} newValueDict = {'Status': 'Active'} - myDB.upsert("artists", newValueDict, controlValueDict) + myDB.upsert("artists", newValueDict, controlValueDict) else: artistsToAdd.append(ArtistID) if len(artistsToAdd) > 0: @@ -358,14 +358,14 @@ class WebInterface(object): threading.Thread(target=importer.addArtistIDListToDB, args=[artistsToAdd]).start() raise cherrypy.HTTPRedirect("home") markArtists.exposed = True - + def importLastFM(self, username): headphones.LASTFM_USERNAME = username headphones.config_write() threading.Thread(target=lastfm.getArtists).start() raise cherrypy.HTTPRedirect("home") importLastFM.exposed = True - + def importLastFMTag(self, tag, limit): threading.Thread(target=lastfm.getTagTopArtists, args=(tag, limit)).start() raise cherrypy.HTTPRedirect("home") @@ -378,14 +378,14 @@ class WebInterface(object): time.sleep(10) raise cherrypy.HTTPRedirect("home") importItunes.exposed = True - + def musicScan(self, path, scan=0, redirect=None, autoadd=0, libraryscan=0): headphones.LIBRARYSCAN = libraryscan headphones.ADD_ARTISTS = autoadd headphones.MUSIC_DIR = path - headphones.config_write() + headphones.config_write() if scan: - try: + try: threading.Thread(target=librarysync.libraryScan).start() except Exception, e: logger.error('Unable to complete the scan: %s' % e) @@ -394,41 +394,41 @@ class WebInterface(object): else: raise cherrypy.HTTPRedirect("home") musicScan.exposed = True - + def forceUpdate(self): from headphones import updater threading.Thread(target=updater.dbUpdate).start() raise cherrypy.HTTPRedirect("home") forceUpdate.exposed = True - + def forceSearch(self): from headphones import searcher threading.Thread(target=searcher.searchforalbum).start() raise cherrypy.HTTPRedirect("home") forceSearch.exposed = True - + def forcePostProcess(self): from headphones import postprocessor threading.Thread(target=postprocessor.forcePostProcess).start() raise cherrypy.HTTPRedirect("home") forcePostProcess.exposed = True - + def checkGithub(self): from headphones import versioncheck versioncheck.checkGithub() raise cherrypy.HTTPRedirect("home") checkGithub.exposed = True - + def history(self): myDB = db.DBConnection() history = myDB.select('''SELECT * from snatched order by DateAdded DESC''') return serve_template(templatename="history.html", title="History", history=history) history.exposed = True - + def logs(self): return serve_template(templatename="logs.html", title="Log", lineList=headphones.LOG_LIST) logs.exposed = True - + def getLog(self,iDisplayStart=0,iDisplayLength=100,iSortCol_0=0,sSortDir_0="desc",sSearch="",**kwargs): @@ -446,7 +446,7 @@ class WebInterface(object): sortcolumn = 2 elif iSortCol_0 == '2': sortcolumn = 1 - filtered.sort(key=lambda x:x[sortcolumn],reverse=sSortDir_0 == "desc") + filtered.sort(key=lambda x:x[sortcolumn],reverse=sSortDir_0 == "desc") rows = filtered[iDisplayStart:(iDisplayStart+iDisplayLength)] rows = [[row[0],row[2],row[1]] for row in rows] @@ -463,10 +463,10 @@ class WebInterface(object): iDisplayStart = int(iDisplayStart) iDisplayLength = int(iDisplayLength) filtered = [] - totalcount = 0 + totalcount = 0 myDB = db.DBConnection() - - + + sortcolumn = 'ArtistSortName' sortbyhavepercent = False if iSortCol_0 == '2': @@ -477,9 +477,9 @@ class WebInterface(object): sortbyhavepercent = True if sSearch == "": - query = 'SELECT * from artists order by %s COLLATE NOCASE %s' % (sortcolumn,sSortDir_0) + query = 'SELECT * from artists order by %s COLLATE NOCASE %s' % (sortcolumn,sSortDir_0) filtered = myDB.select(query) - totalcount = len(filtered) + totalcount = len(filtered) else: query = 'SELECT * from artists WHERE ArtistSortName LIKE "%' + sSearch + '%" OR LatestAlbum LIKE "%' + sSearch +'%"' + 'ORDER BY %s COLLATE NOCASE %s' % (sortcolumn,sSortDir_0) filtered = myDB.select(query) @@ -488,11 +488,11 @@ class WebInterface(object): if sortbyhavepercent: filtered.sort(key=lambda x:(float(x['HaveTracks'])/x['TotalTracks'] if x['TotalTracks'] > 0 else 0.0,x['HaveTracks'] if x['HaveTracks'] else 0.0),reverse=sSortDir_0 == "asc") - #can't figure out how to change the datatables default sorting order when its using an ajax datasource so ill + #can't figure out how to change the datatables default sorting order when its using an ajax datasource so ill #just reverse it here and the first click on the "Latest Album" header will sort by descending release date if sortcolumn == 'ReleaseDate': filtered.reverse() - + artists = filtered[iDisplayStart:(iDisplayStart+iDisplayLength)] rows = [] @@ -502,7 +502,7 @@ class WebInterface(object): "Status":artist["Status"], "TotalTracks":artist["TotalTracks"], "HaveTracks":artist["HaveTracks"], - "LatestAlbum":"", + "LatestAlbum":"", "ReleaseDate":"", "ReleaseInFuture":"False", "AlbumID":"", @@ -520,7 +520,7 @@ class WebInterface(object): row['ReleaseDate'] = '' row['LatestAlbum'] = artist['LatestAlbum'] row['AlbumID'] = artist['AlbumID'] - + rows.append(row) @@ -543,23 +543,23 @@ class WebInterface(object): myDB.action('DELETE from snatched WHERE Status=?', [type]) raise cherrypy.HTTPRedirect("history") clearhistory.exposed = True - + def generateAPI(self): import hashlib, random - + apikey = hashlib.sha224( str(random.getrandbits(256)) ).hexdigest()[0:32] logger.info("New API generated") return apikey - + generateAPI.exposed = True - + def config(self): - + interface_dir = os.path.join(headphones.PROG_DIR, 'data/interfaces/') interface_list = [ name for name in os.listdir(interface_dir) if os.path.isdir(os.path.join(interface_dir, name)) ] - config = { + config = { "http_host" : headphones.HTTP_HOST, "http_user" : headphones.HTTP_USERNAME, "http_port" : headphones.HTTP_PORT, @@ -597,7 +597,6 @@ class WebInterface(object): "use_nzbsrus" : checked(headphones.NZBSRUS), "nzbsrus_uid" : headphones.NZBSRUS_UID, "nzbsrus_apikey" : headphones.NZBSRUS_APIKEY, - "use_nzbx" : checked(headphones.NZBX), "preferred_words" : headphones.PREFERRED_WORDS, "ignored_words" : headphones.IGNORED_WORDS, "required_words" : headphones.REQUIRED_WORDS, @@ -684,11 +683,11 @@ class WebInterface(object): "hppass": headphones.HPPASS, "cache_sizemb":headphones.CACHE_SIZEMB, } - + # Need to convert EXTRAS to a dictionary we can pass to the config: it'll come in as a string like 2,5,6,8 extras_list = ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook"] extras_dict = {} - + i = 1 for extra in extras_list: if str(i) in headphones.EXTRAS: @@ -696,26 +695,26 @@ class WebInterface(object): else: extras_dict[extra] = "" i+=1 - + config["extras"] = extras_dict - - return serve_template(templatename="config.html", title="Settings", config=config) + + return serve_template(templatename="config.html", title="Settings", config=config) config.exposed = True - def configUpdate(self, http_host='0.0.0.0', http_username=None, http_port=8181, http_password=None, launch_browser=0, api_enabled=0, api_key=None, - download_scan_interval=None, nzb_search_interval=None, libraryscan_interval=None, sab_host=None, sab_username=None, sab_apikey=None, sab_password=None, - sab_category=None, nzbget_host=None, nzbget_username='nzbget', nzbget_password=None, nzbget_category=None, nzb_downloader=0, download_dir=None, blackhole=0, blackhole_dir=None, usenet_retention=None, newznab=0, newznab_host=None, newznab_apikey=None, - newznab_enabled=0, nzbsorg=0, nzbsorg_uid=None, nzbsorg_hash=None, nzbsrus=0, nzbsrus_uid=None, nzbsrus_apikey=None, nzbx=0, preferred_words=None, required_words=None, ignored_words=None, - preferred_quality=0, preferred_bitrate=None, detect_bitrate=0, move_files=0, torrentblackhole_dir=None, download_torrent_dir=None, + def configUpdate(self, http_host='0.0.0.0', http_username=None, http_port=8181, http_password=None, launch_browser=0, api_enabled=0, api_key=None, + download_scan_interval=None, nzb_search_interval=None, libraryscan_interval=None, sab_host=None, sab_username=None, sab_apikey=None, sab_password=None, + sab_category=None, nzbget_host=None, nzbget_username='nzbget', nzbget_password=None, nzbget_category=None, nzb_downloader=0, download_dir=None, blackhole=0, blackhole_dir=None, usenet_retention=None, newznab=0, newznab_host=None, newznab_apikey=None, + newznab_enabled=0, nzbsorg=0, nzbsorg_uid=None, nzbsorg_hash=None, nzbsrus=0, nzbsrus_uid=None, nzbsrus_apikey=None, preferred_words=None, required_words=None, ignored_words=None, + preferred_quality=0, preferred_bitrate=None, detect_bitrate=0, move_files=0, torrentblackhole_dir=None, download_torrent_dir=None, numberofseeders=10, use_isohunt=0, use_kat=0, use_mininova=0, waffles=0, waffles_uid=None, waffles_passkey=None, whatcd=0, whatcd_username=None, whatcd_password=None, - rutracker=0, rutracker_user=None, rutracker_password=None, rename_files=0, correct_metadata=0, cleanup_files=0, add_album_art=0, album_art_format=None, embed_album_art=0, embed_lyrics=0, + rutracker=0, rutracker_user=None, rutracker_password=None, rename_files=0, correct_metadata=0, cleanup_files=0, add_album_art=0, album_art_format=None, embed_album_art=0, embed_lyrics=0, destination_dir=None, lossless_destination_dir=None, folder_format=None, file_format=None, include_extras=0, single=0, ep=0, compilation=0, soundtrack=0, live=0, remix=0, spokenword=0, audiobook=0, autowant_upcoming=False, autowant_all=False, keep_torrent_files=False, 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, - delete_lossless_files=0, 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, synoindex_enabled=False, - pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, mirror=None, customhost=None, customport=None, + bitrate=None, samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0, + delete_lossless_files=0, 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, synoindex_enabled=False, + pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, 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, **kwargs): headphones.HTTP_HOST = http_host @@ -730,7 +729,7 @@ class WebInterface(object): headphones.LIBRARYSCAN_INTERVAL = libraryscan_interval headphones.SAB_HOST = sab_host headphones.SAB_USERNAME = sab_username - headphones.SAB_PASSWORD = sab_password + headphones.SAB_PASSWORD = sab_password headphones.SAB_APIKEY = sab_apikey headphones.SAB_CATEGORY = sab_category headphones.NZBGET_HOST = nzbget_host @@ -752,7 +751,6 @@ class WebInterface(object): headphones.NZBSRUS = nzbsrus headphones.NZBSRUS_UID = nzbsrus_uid headphones.NZBSRUS_APIKEY = nzbsrus_apikey - headphones.NZBX = nzbx headphones.PREFERRED_WORDS = preferred_words headphones.IGNORED_WORDS = ignored_words headphones.REQUIRED_WORDS = required_words @@ -836,9 +834,9 @@ class WebInterface(object): headphones.CACHE_SIZEMB = int(cache_sizemb) # Handle the variable config options. Note - keys with False values aren't getting passed - + headphones.EXTRA_NEWZNABS = [] - + for kwarg in kwargs: if kwarg.startswith('newznab_host'): newznab_number = kwarg[12:] @@ -848,26 +846,26 @@ class WebInterface(object): newznab_enabled = int(kwargs['newznab_enabled' + newznab_number]) except KeyError: newznab_enabled = 0 - + headphones.EXTRA_NEWZNABS.append((newznab_host, newznab_api, newznab_enabled)) - + # Convert the extras to list then string. Coming in as 0 or 1 temp_extras_list = [] extras_list = [single, ep, compilation, soundtrack, live, remix, spokenword, audiobook] - + i = 1 for extra in extras_list: if extra: temp_extras_list.append(i) i+=1 - - headphones.EXTRAS = ','.join(str(n) for n in temp_extras_list) - + + headphones.EXTRAS = ','.join(str(n) for n in temp_extras_list) + # Sanity checking if headphones.SEARCH_INTERVAL < 360: logger.info("Search interval too low. Resetting to 6 hour minimum") headphones.SEARCH_INTERVAL = 360 - + # Write the config headphones.config_write() @@ -875,7 +873,7 @@ class WebInterface(object): mb.startmb() raise cherrypy.HTTPRedirect("config") - + configUpdate.exposed = True def shutdown(self): @@ -891,14 +889,14 @@ class WebInterface(object): message = 'Restarting...' return serve_template(templatename="shutdown.html", title="Restarting", message=message, timer=30) restart.exposed = True - + def update(self): headphones.SIGNAL = 'update' message = 'Updating...' return serve_template(templatename="shutdown.html", title="Updating", message=message, timer=120) return page update.exposed = True - + def extras(self): myDB = db.DBConnection() cloudlist = myDB.select('SELECT * from lastfmcloud') @@ -910,59 +908,59 @@ class WebInterface(object): threading.Thread(target=importer.addReleaseById, args=[rid]).start() raise cherrypy.HTTPRedirect("home") addReleaseById.exposed = True - + def updateCloud(self): - + lastfm.getSimilar() raise cherrypy.HTTPRedirect("extras") - + updateCloud.exposed = True - + def api(self, *args, **kwargs): - + from headphones.api import Api - + a = Api() - + a.checkParams(*args, **kwargs) - + data = a.fetchData() - + return data - + api.exposed = True - + def getInfo(self, ArtistID=None, AlbumID=None): - + from headphones import cache info_dict = cache.getInfo(ArtistID, AlbumID) - + return simplejson.dumps(info_dict) - + getInfo.exposed = True - + def getArtwork(self, ArtistID=None, AlbumID=None): - + from headphones import cache return cache.getArtwork(ArtistID, AlbumID) - + getArtwork.exposed = True - + def getThumb(self, ArtistID=None, AlbumID=None): - + from headphones import cache return cache.getThumb(ArtistID, AlbumID) - + getThumb.exposed = True - + # If you just want to get the last.fm image links for an album, make sure to pass a releaseid and not a releasegroupid def getImageLinks(self, ArtistID=None, AlbumID=None): - + from headphones import cache image_dict = cache.getImageLinks(ArtistID, AlbumID) - + return simplejson.dumps(image_dict) - + getImageLinks.exposed = True class Artwork(object): @@ -978,7 +976,7 @@ class Artwork(object): ArtistID = ID elif ArtistOrAlbum == "album": AlbumID = ID - + relpath = cache.getArtwork(ArtistID,AlbumID) if not relpath: @@ -1011,7 +1009,7 @@ class Artwork(object): ArtistID = ID elif ArtistOrAlbum == "album": AlbumID = ID - + relpath = cache.getThumb(ArtistID,AlbumID) if not relpath: @@ -1031,8 +1029,8 @@ class Artwork(object): f = open(path,'rb') return f.read() default.exposed = True - + thumbs = Thumbs() - - + + WebInterface.artwork = Artwork()