From 0fdc62c914b67d2d6cc6f67dc55da0a92d38bd19 Mon Sep 17 00:00:00 2001 From: Ade Date: Sat, 17 May 2014 11:30:18 +1200 Subject: [PATCH 1/2] searcher filtering changes (please review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Filtering of Lossless min/max, Preferred bitrate % and already downloaded check stops the searcher moving on to torrents if prefer NZB set (and vice versa). Moved from sort_search_results to new routine - Preferred bitrate low/high calc not quite right, should be target size + high % - Please correct me here but I think this logic ‘elif headphones.PREFERRED_QUALITY’ is True if Preferred Bitrate (PREFERRED_QUALITY = 2) which means it will always get lossy and lossless. Surprised that no one has complained about this. Anyway, what this means is that the ‘Allow lossless if no good lossy match found’ option kind of works although it shouldn’t! Changed to explicitly state PREFERRED_QUALITY == 1 and added a flag to allow_lossless which is set if Preferred Bitrate and High Limit and Allow Lossless are all set - removed unused bitrate from rutracker --- headphones/searcher.py | 221 ++++++++++++++++--------------- headphones/searcher_rutracker.py | 2 +- 2 files changed, 114 insertions(+), 109 deletions(-) diff --git a/headphones/searcher.py b/headphones/searcher.py index d733a2f3..e6f4c45f 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -94,22 +94,24 @@ def do_sorted_search(album, new, losslessOnly, choose_specific_download=False): TORRENT_PROVIDERS = (headphones.KAT or headphones.PIRATEBAY or headphones.ISOHUNT or headphones.MININOVA or headphones.WAFFLES or headphones.RUTRACKER or headphones.WHATCD) results = [] + myDB = db.DBConnection() + albumlength = myDB.select('SELECT sum(TrackDuration) from tracks WHERE AlbumID=?', [album['AlbumID']])[0][0] if headphones.PREFER_TORRENTS == 0: if NZB_PROVIDERS and NZB_DOWNLOADERS: - results = searchNZB(album, new, losslessOnly) + results = searchNZB(album, new, losslessOnly, albumlength) if not results and TORRENT_PROVIDERS: - results = searchTorrent(album, new, losslessOnly) + results = searchTorrent(album, new, losslessOnly, albumlength) elif headphones.PREFER_TORRENTS == 1: if TORRENT_PROVIDERS: - results = searchTorrent(album, new, losslessOnly) + results = searchTorrent(album, new, losslessOnly, albumlength) if not results and NZB_PROVIDERS and NZB_DOWNLOADERS: - results = searchNZB(album, new, losslessOnly) + results = searchNZB(album, new, losslessOnly, albumlength) else: @@ -117,10 +119,10 @@ def do_sorted_search(album, new, losslessOnly, choose_specific_download=False): torrent_results = None if NZB_PROVIDERS and NZB_DOWNLOADERS: - nzb_results = searchNZB(album, new, losslessOnly) + nzb_results = searchNZB(album, new, losslessOnly, albumlength) if TORRENT_PROVIDERS: - torrent_results = searchTorrent(album, new, losslessOnly) + torrent_results = searchTorrent(album, new, losslessOnly, albumlength) if not nzb_results: nzb_results = [] @@ -134,7 +136,7 @@ def do_sorted_search(album, new, losslessOnly, choose_specific_download=False): if choose_specific_download: return results - sorted_search_results = sort_search_results(results, album, new) + sorted_search_results = sort_search_results(results, album, new, albumlength) if not sorted_search_results: return @@ -145,10 +147,69 @@ def do_sorted_search(album, new, losslessOnly, choose_specific_download=False): if data and bestqual: send_to_downloader(data, bestqual, album) -def sort_search_results(resultlist, album, new): +def more_filtering(results, album, albumlength, new): + low_size_limit = None + high_size_limit = None + allow_lossless = False myDB = db.DBConnection() + # Lossless - ignore results if target size outside bitrate range + if headphones.PREFERRED_QUALITY == 3 and albumlength and (headphones.LOSSLESS_BITRATE_FROM or headphones.LOSSLESS_BITRATE_TO): + if headphones.LOSSLESS_BITRATE_FROM: + low_size_limit = albumlength/1000 * int(headphones.LOSSLESS_BITRATE_FROM) * 128 + if headphones.LOSSLESS_BITRATE_TO: + high_size_limit = albumlength/1000 * int(headphones.LOSSLESS_BITRATE_TO) * 128 + + # Preferred Bitrate - ignore results if target size outside % buffer + elif headphones.PREFERRED_QUALITY == 2 and headphones.PREFERRED_BITRATE: + logger.debug('Target bitrate: %s kbps' % headphones.PREFERRED_BITRATE) + if albumlength: + targetsize = albumlength/1000 * int(headphones.PREFERRED_BITRATE) * 128 + logger.info('Target size: %s' % helpers.bytes_to_mb(targetsize)) + if headphones.PREFERRED_BITRATE_LOW_BUFFER: + low_size_limit = targetsize - (targetsize * int(headphones.PREFERRED_BITRATE_LOW_BUFFER)/100) + if headphones.PREFERRED_BITRATE_HIGH_BUFFER: + high_size_limit = targetsize + (targetsize * int(headphones.PREFERRED_BITRATE_HIGH_BUFFER)/100) + if headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS: + allow_lossless = True + + if low_size_limit or high_size_limit or new: + + newlist = [] + + for result in results: + + if low_size_limit and (int(result[1]) < low_size_limit): + logger.info("%s from %s is too small for this album - not considering it. (Size: %s, Minsize: %s)", result[0], result[3], helpers.bytes_to_mb(result[1]), helpers.bytes_to_mb(low_size_limit)) + continue + + if high_size_limit and (int(result[1]) > high_size_limit): + logger.info("%s from %s is too large for this album - not considering it. (Size: %s, Maxsize: %s)", result[0], result[3], helpers.bytes_to_mb(result[1]), helpers.bytes_to_mb(high_size_limit)) + + # Keep lossless results if there are no good lossy matches + if not (allow_lossless and 'flac' in result[0].lower()): + continue + + if new: + alreadydownloaded = myDB.select('SELECT * from snatched WHERE URL=?', [result[2]]) + + if len(alreadydownloaded): + logger.info('%s has already been downloaded from %s. Skipping.' % (result[0], result[3])) + continue + + newlist.append(result) + + results = newlist + + return results + +def sort_search_results(resultlist, album, new, albumlength): + + if new and not len(resultlist): + logger.info('No more results found for: %s - %s' % (album['ArtistName'], album['AlbumTitle'])) + return None + # Add a priority if it has any of the preferred words temp_list = [] preferred_words = None @@ -170,13 +231,7 @@ def sort_search_results(resultlist, album, new): if headphones.PREFERRED_QUALITY == 2 and headphones.PREFERRED_BITRATE: - logger.debug('Target bitrate: %s kbps' % headphones.PREFERRED_BITRATE) - - tracks = myDB.select('SELECT TrackDuration from tracks WHERE AlbumID=?', [album['AlbumID']]) - try: - albumlength = sum([pair[0] for pair in tracks]) - targetsize = albumlength/1000 * int(headphones.PREFERRED_BITRATE) * 128 if not targetsize: @@ -184,33 +239,14 @@ def sort_search_results(resultlist, album, new): finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True) 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: - high_size_limit = None - if headphones.PREFERRED_BITRATE_LOW_BUFFER: - 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 (int(result[1]) > high_size_limit): - - 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(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], result[5])) - - continue - - if low_size_limit and (int(result[1]) < low_size_limit): - 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(low_size_limit)) + # Add lossless results 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], result[5])) continue delta = abs(targetsize - int(result[1])) @@ -227,56 +263,10 @@ 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) - if new: - - while True: - - if len(finallist): - - alreadydownloaded = myDB.select('SELECT * from snatched WHERE URL=?', [finallist[0][2]]) - - if len(alreadydownloaded): - logger.info('%s has already been downloaded. Skipping.' % finallist[0][0]) - finallist.pop(0) - - else: - break - else: - logger.info('No more results found for: %s - %s' % (album['ArtistName'], album['AlbumTitle'])) - return None - if not len(finallist): logger.info('No appropriate matches found for %s - %s', album['ArtistName'], album['AlbumTitle']) return None @@ -292,7 +282,7 @@ def get_year_from_release_date(release_date): return year -def searchNZB(album, new=False, losslessOnly=False): +def searchNZB(album, new=False, losslessOnly=False, albumlength=None): albumid = album['AlbumID'] reldate = album['ReleaseDate'] @@ -322,6 +312,12 @@ def searchNZB(album, new=False, losslessOnly=False): term = re.sub('[\.\-\/]', ' ', term).encode('utf-8') artistterm = re.sub('[\.\-\/]', ' ', cleanartist).encode('utf-8') + # If Preferred Bitrate and High Limit and Allow Lossless then get both lossy and lossless + if headphones.PREFERRED_QUALITY == 2 and headphones.PREFERRED_BITRATE and headphones.PREFERRED_BITRATE_HIGH_BUFFER and headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS: + allow_lossless = True + else: + allow_lossless = False + logger.debug("Using search term: %s" % term) resultlist = [] @@ -331,7 +327,7 @@ def searchNZB(album, new=False, losslessOnly=False): if headphones.PREFERRED_QUALITY == 3 or losslessOnly: categories = "3040" - elif headphones.PREFERRED_QUALITY: + elif headphones.PREFERRED_QUALITY == 1 or allow_lossless: categories = "3040,3010" else: categories = "3010" @@ -389,7 +385,7 @@ def searchNZB(album, new=False, losslessOnly=False): if headphones.PREFERRED_QUALITY == 3 or losslessOnly: categories = "3040" - elif headphones.PREFERRED_QUALITY: + elif headphones.PREFERRED_QUALITY == 1 or allow_lossless: categories = "3040,3010" else: categories = "3010" @@ -451,7 +447,7 @@ def searchNZB(album, new=False, losslessOnly=False): provider = "nzbsorg" if headphones.PREFERRED_QUALITY == 3 or losslessOnly: categories = "3040" - elif headphones.PREFERRED_QUALITY: + elif headphones.PREFERRED_QUALITY == 1 or allow_lossless: categories = "3040,3010" else: categories = "3010" @@ -500,7 +496,7 @@ def searchNZB(album, new=False, losslessOnly=False): if headphones.PREFERRED_QUALITY == 3 or losslessOnly: sub = "16" - elif headphones.PREFERRED_QUALITY: + elif headphones.PREFERRED_QUALITY ==1 or allow_lossless: sub = "" else: sub = "15" @@ -551,7 +547,7 @@ def searchNZB(album, new=False, losslessOnly=False): if headphones.PREFERRED_QUALITY == 3 or losslessOnly: categories = "22" - elif headphones.PREFERRED_QUALITY: + elif headphones.PREFERRED_QUALITY == 1 or allow_lossless: categories = "22,7" else: categories = "7" @@ -600,7 +596,13 @@ def searchNZB(album, new=False, losslessOnly=False): # # Also will filter flac & remix albums if not specifically looking for it # This code also checks the ignored words and required words - return [result for result in resultlist if verifyresult(result[0], artistterm, term, losslessOnly)] + results = [result for result in resultlist if verifyresult(result[0], artistterm, term, losslessOnly)] + + # Additional filtering for size etc + if results: + results = more_filtering(results, album, albumlength, new) + + return results def send_to_downloader(data, bestqual, album): @@ -922,7 +924,7 @@ def getresultNZB(result): return nzb -def searchTorrent(album, new=False, losslessOnly=False): +def searchTorrent(album, new=False, losslessOnly=False, albumlength=None): global gazelle # persistent what.cd api object to reduce number of login attempts # rutracker login @@ -974,6 +976,12 @@ def searchTorrent(album, new=False, losslessOnly=False): artistterm = re.sub('[\.\-\/]', ' ', cleanartist).encode('utf-8', 'replace') albumterm = re.sub('[\.\-\/]', ' ', cleanalbum).encode('utf-8', 'replace') + # If Preferred Bitrate and High Limit and Allow Lossless then get both lossy and lossless + if headphones.PREFERRED_QUALITY == 2 and headphones.PREFERRED_BITRATE and headphones.PREFERRED_BITRATE_HIGH_BUFFER and headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS: + allow_lossless = True + else: + allow_lossless = False + logger.debug("Using search term: %s" % term) resultlist = [] @@ -987,7 +995,7 @@ def searchTorrent(album, new=False, losslessOnly=False): categories = "7" #music format = "2" #flac maxsize = 10000000000 - elif headphones.PREFERRED_QUALITY: + elif headphones.PREFERRED_QUALITY == 1 or allow_lossless: categories = "7" #music format = "10" #mp3+flac maxsize = 10000000000 @@ -1045,7 +1053,7 @@ def searchTorrent(album, new=False, losslessOnly=False): format = "FLAC" bitrate = "(Lossless)" maxsize = 10000000000 - elif headphones.PREFERRED_QUALITY: + elif headphones.PREFERRED_QUALITY == 1 or allow_lossless: format = "FLAC OR MP3" maxsize = 10000000000 else: @@ -1102,33 +1110,26 @@ def searchTorrent(album, new=False, losslessOnly=False): 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 - elif headphones.PREFERRED_QUALITY == 1: + elif headphones.PREFERRED_QUALITY == 1 or allow_lossless: format = 'lossless+mp3' maxsize = 10000000000 else: format = 'mp3' maxsize = 300000000 - if headphones.PREFERRED_QUALITY == 2 and headphones.PREFERRED_BITRATE: - bitrate = True # build search url based on above - if not usersearchterm: searchURL = rutracker.searchurl(artistterm, albumterm, year, format) else: @@ -1137,11 +1138,9 @@ def searchTorrent(album, new=False, losslessOnly=False): logger.info(u'Parsing results from rutracker.org' % searchURL) # parse results and get best match - - rulist = rutracker.search(searchURL, maxsize, minimumseeders, albumid, bitrate) + rulist = rutracker.search(searchURL, maxsize, minimumseeders, albumid) # add best match to overall results list - if rulist: for ru in rulist: title = ru[0].decode('utf-8') @@ -1172,7 +1171,7 @@ def searchTorrent(album, new=False, losslessOnly=False): if bitrate_string not in gazelleencoding.ALL_ENCODINGS: logger.info(u"Your preferred bitrate is not one of the available What.cd filters, so not using it as a search parameter.") maxsize = 10000000000 - elif headphones.PREFERRED_QUALITY == 1: # Highest quality including lossless + elif headphones.PREFERRED_QUALITY == 1 or allow_lossless: # Highest quality including lossless search_formats = [gazelleformat.FLAC, gazelleformat.MP3] maxsize = 10000000000 else: # Highest quality excluding lossless @@ -1251,7 +1250,7 @@ def searchTorrent(album, new=False, losslessOnly=False): if headphones.PREFERRED_QUALITY == 3 or losslessOnly: category = '104' #flac maxsize = 10000000000 - elif headphones.PREFERRED_QUALITY: + elif headphones.PREFERRED_QUALITY == 1 or allow_lossless: category = '100' #audio cat maxsize = 10000000000 else: @@ -1318,7 +1317,7 @@ def searchTorrent(album, new=False, losslessOnly=False): categories = "7" #music format = "2" #flac maxsize = 10000000000 - elif headphones.PREFERRED_QUALITY: + elif headphones.PREFERRED_QUALITY == 1 or allow_lossless: categories = "7" #music format = "10" #mp3+flac maxsize = 10000000000 @@ -1384,7 +1383,7 @@ def searchTorrent(album, new=False, losslessOnly=False): categories = "7" #music format = "2" #flac maxsize = 10000000000 - elif headphones.PREFERRED_QUALITY: + elif headphones.PREFERRED_QUALITY == 1 or allow_lossless: categories = "7" #music format = "10" #mp3+flac maxsize = 10000000000 @@ -1434,7 +1433,13 @@ def searchTorrent(album, new=False, losslessOnly=False): #attempt to verify that this isn't a substring result #when looking for "Foo - Foo" we don't want "Foobar" #this should be less of an issue when it isn't a self-titled album so we'll only check vs artist - return [result for result in resultlist if verifyresult(result[0], artistterm, term, losslessOnly)] + results = [result for result in resultlist if verifyresult(result[0], artistterm, term, losslessOnly)] + + # Additional filtering for size etc + if results: + results = more_filtering(results, album, albumlength, new) + + return results # THIS IS KIND OF A MESS AND PROBABLY NEEDS TO BE CLEANED UP def preprocess(resultlist): diff --git a/headphones/searcher_rutracker.py b/headphones/searcher_rutracker.py index 3c799f24..dffb638b 100644 --- a/headphones/searcher_rutracker.py +++ b/headphones/searcher_rutracker.py @@ -88,7 +88,7 @@ class Rutracker(): return searchurl - def search(self, searchurl, maxsize, minseeders, albumid, bitrate): + def search(self, searchurl, maxsize, minseeders, albumid): """ Parse the search results and return valid torrent list """ From 66b9da36ddb582a30f4c87b819dd5ba50671180c Mon Sep 17 00:00:00 2001 From: Ade Date: Sat, 28 Jun 2014 08:18:54 +1200 Subject: [PATCH 2/2] Album Search Results Changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mb findRelease get additional info incl Date, Formats, Tracks, Rgid etc, pass to searchresults added optional artist to findrelease, if searching by album can now enter album:artist in the search bar, went for this for now as I didn’t want to change the design too much but maybe in the future we could have 2 boxes, artist album and then there’s no need for the dropdown artist, album - base position at search box and persist selected option (uses local storage) when refreshing, useful if entering multiple albums increased the search box size a little - searchresults album results - new fields from mb, fall back to cover art archive rgid url if last.fm not found (should get more results), musicbrainz album icon link, pass mb rgid to addReleaseById to redirect to album page artist results - musicbrainz artist icon - importer addreleaseById - added rgid param to create the album record upfront with status Loading if from searchresults - webserve redirect to album page using rgid from searchresults - album spinner while album is loading --- data/interfaces/default/album.html | 61 +++++++++++++++++- data/interfaces/default/base.html | 24 ++++++- data/interfaces/default/css/style.css | 57 +++++++++++++++- .../default/images/MusicBrainz_Album_Icon.png | Bin 0 -> 4266 bytes .../images/MusicBrainz_Artist_Icon.png | Bin 0 -> 3823 bytes data/interfaces/default/searchresults.html | 56 ++++++++++------ headphones/cache.py | 6 +- headphones/importer.py | 43 ++++++++++-- headphones/mb.py | 53 +++++++++++++-- headphones/webserve.py | 44 ++++++++++++- 10 files changed, 297 insertions(+), 47 deletions(-) create mode 100644 data/interfaces/default/images/MusicBrainz_Album_Icon.png create mode 100644 data/interfaces/default/images/MusicBrainz_Artist_Icon.png diff --git a/data/interfaces/default/album.html b/data/interfaces/default/album.html index ba8bf33b..5127cf28 100644 --- a/data/interfaces/default/album.html +++ b/data/interfaces/default/album.html @@ -89,9 +89,15 @@
- -

${album['AlbumTitle']}

-

${album['ArtistName']}

+ +

+ ${album['AlbumTitle']} +

+ +

+ ${album['ArtistName']} +

+ <% totalduration = myDB.action("SELECT SUM(TrackDuration) FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0] totaltracks = len(myDB.select("SELECT TrackTitle from tracks WHERE AlbumID=?", [album['AlbumID']])) @@ -275,9 +281,58 @@ feedback.fadeIn(); } + var loadingMessage = false; + var spinner_active = false; + var loadingtext_active = false; + var refreshInterval; + var wasLoading = false; + var x = 0; + + function checkAlbumStatus() { + $.getJSON("getAlbumjson?AlbumID=${album['AlbumID']}", function(data) { + if (data['Status'] == "Loading"){ + wasLoading = true; + $('#albumnamelink').text(data["AlbumTitle"]); + $('#artistnamelink').text(data["ArtistName"]); + if (loadingMessage == false){ + $("#ajaxMsg").after( "
" ); + showArtistMsg("Getting album information"); + loadingMessage = true; + } + if (spinner_active == false){ + $('#albumname').prepend('') + spinner_active = true; + } + if (loadingtext_active == false){ + $('#albumname').append('

(Album information is currently being loaded)

') + loadingtext_active = true; + } + } + else{ + if (++x === 5) { + clearInterval(refreshInterval); + } + var sts = $("#artistname").text(); + if (wasLoading == true || sts == "Loading"){ + location.reload(); + $('#albumnamespinner').remove() + $('#loadingtext').remove() + $('#ajaxMsg2').remove() + spinner_active = false + loadingtext_active = false + loadingMessage = false + } + } + }); + } + $(document).ready(function() { getAlbumInfo(); initThisPage(); + checkAlbumStatus(); + refreshInterval = setInterval(function(){ + checkAlbumStatus(); + }, 3000); }); diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index e606a02b..2920f0a5 100644 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -58,7 +58,7 @@
- @@ -115,3 +115,25 @@ <%def name="javascriptIncludes()"> <%def name="headIncludes()"> <%def name="headerIncludes()"> + + + diff --git a/data/interfaces/default/css/style.css b/data/interfaces/default/css/style.css index 6789726f..60baca70 100644 --- a/data/interfaces/default/css/style.css +++ b/data/interfaces/default/css/style.css @@ -758,7 +758,7 @@ div#searchbar input[type=text] { line-height: normal; margin-right: 5px; padding: 4px 5px 4px 25px; - width: 150px; + width: 200px; } div#searchbar .mini-icon { color: #999; @@ -1126,13 +1126,34 @@ div#artistheader h2 a { padding: 2px 10px; } #searchresults_table th#albumname { - min-width: 225px; + min-width: 250px; text-align: left; + font-size: 14px; } #searchresults_table th#artistname { min-width: 325px; text-align: left; } +#searchresults_table th#artistnamesmall { + min-width: 125px; + text-align: left; + font-size: 14px; +} +#searchresults_table th#reldate { + min-width: 80px; + text-align: center; + font-size: 14px; +} +#searchresults_table th#format { + min-width: 50px; + text-align: left; + font-size: 14px; +} +#searchresults_table th#tracks { + min-width: 50px; + text-align: left; + font-size: 14px; +} #searchresults_table #artistImg { background: url("../images/loader_black.gif") no-repeat scroll center center #ffffff; border: 3px solid #FFFFFF; @@ -1144,15 +1165,40 @@ div#artistheader h2 a { width: 50px; } #searchresults_table td#albumname { - min-width: 200px; + min-width: 250px; text-align: left; vertical-align: middle; + font-size: 14px; } #searchresults_table td#artistname { min-width: 300px; text-align: left; vertical-align: middle; } +#searchresults_table td#artistnamesmall { + min-width: 100px; + text-align: left; + vertical-align: middle; + font-size: 14px; +} +#searchresults_table td#reldate { + min-width: 80px; + text-align: center; + vertical-align: middle; + font-size: 14px; +} +#searchresults_table td#format { + min-width: 50px; + text-align: left; + vertical-align: middle; + font-size: 14px; +} +#searchresults_table td#tracks { + min-width: 50px; + text-align: left; + vertical-align: middle; + font-size: 12px; +} #searchresults_table td#add { vertical-align: middle; } @@ -1416,6 +1462,11 @@ div#artistheader h3 span { min-width: 75px; text-align: center; } +#searchresults_table th#scoresmall { + min-width: 50px; + text-align: center; + font-size: 14px; +} #track_table td#bitrate, #track_table td#format { font-size: 12px; diff --git a/data/interfaces/default/images/MusicBrainz_Album_Icon.png b/data/interfaces/default/images/MusicBrainz_Album_Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..16abf29b38f2372fce6238986253209e772670ab GIT binary patch literal 4266 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 literal 0 HcmV?d00001 diff --git a/data/interfaces/default/searchresults.html b/data/interfaces/default/searchresults.html index b790f644..2c772b41 100644 --- a/data/interfaces/default/searchresults.html +++ b/data/interfaces/default/searchresults.html @@ -11,37 +11,51 @@ %if type == 'album': Album Name + Artist Name + Format + Tracks + Date + Score + %else: + Artist Name + Score %endif - Artist Name - Score - + %if searchresults: - %for result in searchresults: - <% - if result['score'] == 100: - grade = 'A' - else: - grade = 'Z' - %> + %for result in searchresults: + <% + if result['score'] == 100: + grade = 'A' + else: + grade = 'Z' + + if type == 'album': + albuminfo = 'Type: ' + result['rgtype'] + ', Country: ' + result['country'] + caa_group_url = "http://coverartarchive.org/release-group/%s/front-250.jpg" %result['rgid'] + %> %if type == 'album': -
+
%else:
%endif %if type == 'album': - ${result['title']} - %endif - ${result['uniquename']} -
${result['score']}
- %if type == 'album': - Add this album - %else: - Add this artist + ${result['title']} + ${result['uniquename']} + ${result['formats']} + ${result['tracks']} + ${result['date']} +
${result['score']}
+ + %else: + ${result['uniquename']} +
${result['score']}
+ %endif + %endfor %endif @@ -75,7 +89,7 @@ { "bDestroy": true, "aoColumnDefs": [ - { 'bSortable': false, 'aTargets': [ 0,3 ] } + { 'bSortable': false, 'aTargets': [ 0 ] } ], "oLanguage": { "sLengthMenu":"Show _MENU_ results per page", @@ -91,7 +105,7 @@ resetFilters("album"); } $(document).ready(function(){ - initThisPage(); + initThisPage(); }); $(window).load(function(){ initFancybox(); diff --git a/headphones/cache.py b/headphones/cache.py index a762fd20..4eba6034 100644 --- a/headphones/cache.py +++ b/headphones/cache.py @@ -233,15 +233,15 @@ class Cache(object): return try: - image_url = data['artist']['image'][-1]['#text'] + image_url = data['album']['image'][-1]['#text'] except Exception: - logger.debug('No artist image found') + logger.debug('No album image found on last.fm') image_url = None thumb_url = self._get_thumb_url(data) if not thumb_url: - logger.debug('No artist thumbnail image found') + logger.debug('No album thumbnail image found on last.fm') return {'artwork' : image_url, 'thumbnail' : thumb_url } diff --git a/headphones/importer.py b/headphones/importer.py index 6a7f79ed..525935b2 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -389,6 +389,8 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False): releaseid = rg['id'] else: releaseid = rg_exists['ReleaseID'] + if not releaseid: + releaseid = rg['id'] album = myDB.action('SELECT * from allalbums WHERE ReleaseID=?', [releaseid]).fetchone() @@ -526,10 +528,23 @@ def finalize_update(artistid, artistname, errors=False): myDB.upsert("artists", newValueDict, controlValueDict) -def addReleaseById(rid): +def addReleaseById(rid, rgid=None): myDB = db.DBConnection() + # Create minimum info upfront if added from searchresults + status = '' + if rgid: + dbalbum = myDB.select("SELECT * from albums WHERE AlbumID=?", [rgid]) + if not dbalbum: + status = 'Loading' + controlValueDict = {"AlbumID": rgid} + newValueDict = {"AlbumTitle": rgid, + "ArtistName": status, + "Status": status} + myDB.upsert("albums", newValueDict, controlValueDict) + time.sleep(1) + rgid = None artistid = None release_dict = None @@ -545,9 +560,13 @@ def addReleaseById(rid): release_dict = mb.getRelease(rid) except Exception, e: logger.info('Unable to get release information for Release %s: %s', rid, e) + if status == 'Loading': + myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid]) return if not release_dict: logger.info('Unable to get release information for Release %s: no dict', rid) + if status == 'Loading': + myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid]) return rgid = release_dict['rgid'] @@ -565,7 +584,6 @@ def addReleaseById(rid): else: sortname = release_dict['artist_name'] - logger.info(u"Now manually adding: " + release_dict['artist_name'] + " - with status Paused") controlValueDict = {"ArtistID": release_dict['artist_id']} newValueDict = {"ArtistName": release_dict['artist_name'], @@ -581,12 +599,16 @@ def addReleaseById(rid): elif not artist_exists and not release_dict: logger.error("Artist does not exist in the database and did not get a valid response from MB. Skipping release.") + if status == 'Loading': + myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid]) return - if not rg_exists and release_dict: #it should never be the case that we have an rg and not the artist - #but if it is this will fail + if not rg_exists and release_dict or status == 'Loading' and release_dict: #it should never be the case that we have an rg and not the artist + #but if it is this will fail logger.info(u"Now adding-by-id album (" + release_dict['title'] + ") from id: " + rgid) controlValueDict = {"AlbumID": rgid} + if status != 'Loading': + status = 'Wanted' newValueDict = {"ArtistID": release_dict['artist_id'], "ArtistName": release_dict['artist_name'], @@ -594,8 +616,9 @@ def addReleaseById(rid): "AlbumASIN": release_dict['asin'], "ReleaseDate": release_dict['date'], "DateAdded": helpers.today(), - "Status": 'Wanted', - "Type": release_dict['rg_type'] + "Status": status, + "Type": release_dict['rg_type'], + "ReleaseID": rid } myDB.upsert("albums", newValueDict, controlValueDict) @@ -635,11 +658,19 @@ def addReleaseById(rid): myDB.upsert("tracks", newValueDict, controlValueDict) + # Reset status + if status == 'Loading': + controlValueDict = {"AlbumID": rgid} + newValueDict = {"Status": "Wanted"} + myDB.upsert("albums", newValueDict, controlValueDict) + #start a search for the album import searcher searcher.searchforalbum(rgid, False) elif not rg_exists and not release_dict: logger.error("ReleaseGroup does not exist in the database and did not get a valid response from MB. Skipping release.") + if status == 'Loading': + myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid]) return else: logger.info('Release ' + str(rid) + " already exists in the database!") diff --git a/headphones/mb.py b/headphones/mb.py index d9cc96e6..80467e5f 100644 --- a/headphones/mb.py +++ b/headphones/mb.py @@ -23,6 +23,8 @@ from headphones.helpers import multikeysort, replace_all import lib.musicbrainzngs as musicbrainzngs from lib.musicbrainzngs import WebServiceError +from lib.simplejson import OrderedDict + mb_lock = threading.Lock() @@ -117,7 +119,7 @@ def findArtist(name, limit=1): }) return artistlist -def findRelease(name, limit=1): +def findRelease(name, limit=1, artist=None): with mb_lock: releaselist = [] @@ -126,25 +128,62 @@ def findRelease(name, limit=1): chars = set('!?') if any((c in chars) for c in name): name = '"'+name+'"' - + + # additional artist search + if not artist and ':' in name: + name, artist = name.rsplit(":",1) + try: - releaseResults = musicbrainzngs.search_releases(query=name,limit=limit)['release-list'] + releaseResults = musicbrainzngs.search_releases(query=name,limit=limit,artist=artist)['release-list'] except WebServiceError, e: #need to update exceptions logger.warn('Attempt to query MusicBrainz for "%s" failed: %s' % (name, str(e))) time.sleep(5) if not releaseResults: return False + for result in releaseResults: - releaselist.append({ + + title = result['title'] + if 'disambiguation' in result: + title += ' (' + result['disambiguation'] + ')' + + # Get formats and track counts + format_dict = OrderedDict() + formats = '' + tracks = '' + for medium in result['medium-list']: + if 'format' in medium: + format = medium['format'] + if format not in format_dict: + format_dict[format] = 0 + format_dict[format] += 1 + if 'track-count' in medium: + if tracks: + tracks += ' + ' + tracks += str(medium['track-count']) + for format, count in format_dict.items(): + if formats: + formats += ' + ' + if count > 1: + formats += str(count) + 'x' + formats += format + + releaselist.append({ 'uniquename': unicode(result['artist-credit'][0]['artist']['name']), - 'title': unicode(result['title']), + 'title': unicode(title), 'id': unicode(result['artist-credit'][0]['artist']['id']), 'albumid': unicode(result['id']), 'url': unicode("http://musicbrainz.org/artist/" + result['artist-credit'][0]['artist']['id']),#probably needs to be changed 'albumurl': unicode("http://musicbrainz.org/release/" + result['id']),#probably needs to be changed - 'score': int(result['ext:score']) - }) + 'score': int(result['ext:score']), + 'date': unicode(result['date']) if 'date' in result else '', + 'country': unicode(result['country']) if 'country' in result else '', + 'formats': unicode(formats), + 'tracks': unicode(tracks), + 'rgid': unicode(result['release-group']['id']), + 'rgtype': unicode(result['release-group']['type']) if 'type' in result['release-group'] else '' + }) return releaselist def getArtist(artistid, extrasonly=False): diff --git a/headphones/webserve.py b/headphones/webserve.py index 709de4f0..92779e56 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -110,6 +110,19 @@ class WebInterface(object): album = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone() tracks = myDB.select('SELECT * from tracks WHERE AlbumID=? ORDER BY CAST(TrackNumber AS INTEGER)', [AlbumID]) description = myDB.action('SELECT * from descriptions WHERE ReleaseGroupID=?', [AlbumID]).fetchone() + + retry = 0 + while retry < 5: + if not album: + time.sleep(1) + album = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone() + retry += 1 + else: + break + + if not album: + raise cherrypy.HTTPRedirect("home") + if not album['ArtistName']: title = ' - ' else: @@ -864,6 +877,17 @@ class WebInterface(object): return artist_json getArtistjson.exposed=True + def getAlbumjson(self, AlbumID, **kwargs): + myDB = db.DBConnection() + album = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone() + album_json = json.dumps({ + 'AlbumTitle': album['AlbumTitle'], + 'ArtistName': album['ArtistName'], + 'Status': album['Status'] + }) + return album_json + getAlbumjson.exposed=True + def clearhistory(self, type=None, date_added=None, title=None): myDB = db.DBConnection() if type: @@ -1395,9 +1419,12 @@ class WebInterface(object): return page extras.exposed = True - def addReleaseById(self, rid): - threading.Thread(target=importer.addReleaseById, args=[rid]).start() - raise cherrypy.HTTPRedirect("home") + def addReleaseById(self, rid, rgid=None): + threading.Thread(target=importer.addReleaseById, args=[rid, rgid]).start() + if rgid: + raise cherrypy.HTTPRedirect("albumPage?AlbumID=%s" % rgid) + else: + raise cherrypy.HTTPRedirect("home") addReleaseById.exposed = True def updateCloud(self): @@ -1450,6 +1477,17 @@ class WebInterface(object): from headphones import cache image_dict = cache.getImageLinks(ArtistID, AlbumID) + # Return the Cover Art Archive urls if not found on last.fm + if AlbumID and not image_dict: + image_url = "http://coverartarchive.org/release/%s/front-500.jpg" % AlbumID + thumb_url = "http://coverartarchive.org/release/%s/front-250.jpg" % AlbumID + image_dict = {'artwork' : image_url, 'thumbnail' : thumb_url} + elif AlbumID and (not image_dict['artwork'] or not image_dict['thumbnail']): + if not image_dict['artwork']: + image_dict['artwork'] = "http://coverartarchive.org/release/%s/front-500.jpg" % AlbumID + if not image_dict['thumbnail']: + image_dict['thumbnail'] = "http://coverartarchive.org/release/%s/front-250.jpg" % AlbumID + return simplejson.dumps(image_dict) getImageLinks.exposed = True