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 00000000..16abf29b Binary files /dev/null and b/data/interfaces/default/images/MusicBrainz_Album_Icon.png differ diff --git a/data/interfaces/default/images/MusicBrainz_Artist_Icon.png b/data/interfaces/default/images/MusicBrainz_Artist_Icon.png new file mode 100644 index 00000000..82e3381d Binary files /dev/null and b/data/interfaces/default/images/MusicBrainz_Artist_Icon.png differ 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