CoverArt Browser  v2.0
Browse your cover-art albums in Rhythmbox
/home/foss/Downloads/coverart-browser/coverart_artistinfo.py
00001 # -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
00002 #
00003 # Copyright (C) 2014 fossfreedom
00004 # this module has been heavily modifed from rhythmbox context plugin
00005 # Copyright (C) 2009 John Iacona
00006 #
00007 # This program is free software; you can redistribute it and/or modify
00008 # it under the terms of the GNU General Public License as published by
00009 # the Free Software Foundation; either version 2, or (at your option)
00010 # any later version.
00011 #
00012 # This program is distributed in the hope that it will be useful,
00013 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 # GNU General Public License for more details.
00016 #
00017 # You should have received a copy of the GNU General Public License
00018 # along with this program; if not, write to the Free Software
00019 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
00020 
00021 import os
00022 import urllib.request
00023 import urllib.parse
00024 import json
00025 import gettext
00026 
00027 from mako.template import Template
00028 from gi.repository import WebKit
00029 from gi.repository import GObject
00030 from gi.repository import Gtk
00031 from gi.repository import Gdk
00032 from gi.repository import GLib
00033 from gi.repository import RB
00034 from gi.repository import Gio
00035 
00036 import rb
00037 import rb_lastfm as LastFM  # from coverart-search-providers
00038 from coverart_utils import get_stock_size
00039 from coverart_browser_prefs import GSetting
00040 from coverart_browser_prefs import CoverLocale
00041 from coverart_utils import create_button_image
00042 
00043 
00044 gettext.install('rhythmbox', RB.locale_dir())
00045 
00046 
00047 def artist_exceptions(artist):
00048     exceptions = ['various']
00049 
00050     for exception in exceptions:
00051         if exception in artist.lower():
00052             return True
00053 
00054     return False
00055 
00056 
00057 def lastfm_datasource_link(path):
00058     return "<a href='http://last.fm/'><img src='%s/img/lastfm.png'></a>" % path
00059 
00060 
00061 LASTFM_NO_ACCOUNT_ERROR = _(
00062     "Enable LastFM plugin and log in first")
00063 
00064 
00065 class ArtistInfoWebView(WebKit.WebView):
00066     def __init(self, *args, **kwargs):
00067         super(ArtistInfoWebView, self).__init__(*args, **kwargs)
00068 
00069     def initialise(self, source, shell):
00070         self.source = source
00071         self.shell = shell
00072 
00073         self.connect("navigation-requested", self.navigation_request_cb)
00074         self.connect("notify::title", self.view_title_change)
00075 
00076     def view_title_change(self, webview, param):
00077         print ("view_title_change")
00078         title = webview.get_title()
00079 
00080         if title:
00081             print ("title %s" % title)
00082             args = json.loads(title)
00083             artist = args['artist']
00084 
00085             if args['toggle']:
00086                 self.source.album_manager.model.replace_filter('similar_artist', artist)
00087             else:
00088                 self.source.album_manager.model.remove_filter('similar_artist')
00089         else:
00090             print ("removing filter")
00091             self.source.album_manager.model.remove_filter('similar_artist')
00092         print ("end view_title_change")
00093 
00094     def navigation_request_cb(self, view, frame, request):
00095         # open HTTP URIs externally.  this isn't a web browser.
00096         print ("navigation_request_cb")
00097         if request.get_uri().startswith('http'):
00098             print("opening uri %s" % request.get_uri())
00099             Gtk.show_uri(self.shell.props.window.get_screen(), request.get_uri(), Gdk.CURRENT_TIME)
00100 
00101             return 1  # WEBKIT_NAVIGATION_RESPONSE_IGNORE
00102         else:
00103             return 0  # WEBKIT_NAVIGATION_RESPONSE_ACCEPT
00104 
00105     def do_button_release_event(self, *args):
00106         print ("do_release_button")
00107         WebKit.WebView.do_button_release_event(self, *args)
00108 
00109         return True
00110 
00111 
00112 class ArtistInfoPane(GObject.GObject):
00113     __gsignals__ = {
00114         'selected': (GObject.SIGNAL_RUN_LAST, None,
00115                      (GObject.TYPE_STRING, GObject.TYPE_STRING))
00116     }
00117 
00118     paned_pos = GObject.property(type=str)
00119 
00120     min_paned_pos = 100
00121 
00122     def __init__(self, button_box, stack, info_paned, source):
00123         GObject.GObject.__init__(self)
00124 
00125         self.ds = {}
00126         self.view = {}
00127 
00128         #self.buttons = button_box
00129         self.source = source
00130         self.plugin = source.plugin
00131         self.shell = source.shell
00132         self.info_paned = info_paned
00133         self.current_artist = None
00134         self.current_album_title = None
00135         self.current = 'artist'
00136         self._from_paned_handle = False
00137 
00138         self.stack = stack
00139         self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
00140         stack_switcher = Gtk.StackSwitcher()
00141         stack_switcher.set_stack(self.stack)
00142         self.stack.connect('notify::visible-child-name', self.change_stack)
00143         button_box.pack_start(stack_switcher, False, False, 0)
00144         button_box.show_all()
00145 
00146         # cache for artist/album information: valid for a month, can be used indefinitely
00147         # if offline, discarded if unused for six months
00148         self.info_cache = rb.URLCache(name='info',
00149                                       path=os.path.join('coverart_browser', 'info'),
00150                                       refresh=30,
00151                                       discard=180)
00152         # cache for rankings (artist top tracks and top albums): valid for a week,
00153         # can be used for a month if offline
00154         self.ranking_cache = rb.URLCache(name='ranking',
00155                                          path=os.path.join('coverart_browser', 'ranking'),
00156                                          refresh=7,
00157                                          lifetime=30)
00158 
00159         self.info_cache.clean()
00160         self.ranking_cache.clean()
00161 
00162         self.ds['link'] = LinksDataSource()
00163         self.ds['artist'] = ArtistDataSource(self.info_cache,
00164                                              self.ranking_cache)
00165 
00166         self.view['artist'] = ArtistInfoView()
00167         self.view['artist'].initialise(self.source,
00168                                        self.shell,
00169                                        self.plugin,
00170                                        self.stack,
00171                                        self.ds['artist'],
00172                                        self.ds['link'])
00173 
00174         self.ds['album'] = AlbumDataSource(self.info_cache,
00175                                            self.ranking_cache)
00176         self.view['album'] = AlbumInfoView()
00177         self.view['album'].initialise(self.source,
00178                                       self.shell,
00179                                       self.plugin,
00180                                       self.stack,
00181                                       self.ds['album'])
00182 
00183         self.ds['echoartist'] = EchoArtistDataSource(
00184             self.info_cache,
00185             self.ranking_cache)
00186         self.view['echoartist'] = EchoArtistInfoView()
00187         self.view['echoartist'].initialise(self.source,
00188                                            self.shell,
00189                                            self.plugin,
00190                                            self.stack,
00191                                            self.ds['echoartist'],
00192                                            self.ds['link'])
00193 
00194         self.gs = GSetting()
00195         self.connect_properties()
00196         self.connect_signals()
00197         Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE,
00198                                 50,
00199                                 self._change_paned_pos,
00200                                 self.source.viewmgr.view_name)
00201         self.view[self.current].activate()
00202 
00203     def connect_properties(self):
00204         '''
00205         Connects the source properties to the saved preferences.
00206         '''
00207         setting = self.gs.get_setting(self.gs.Path.PLUGIN)
00208 
00209         setting.bind(
00210             self.gs.PluginKey.ARTIST_INFO_PANED_POSITION,
00211             self,
00212             'paned-pos',
00213             Gio.SettingsBindFlags.DEFAULT)
00214 
00215     def connect_signals(self):
00216         self.tab_cb_ids = []
00217 
00218         # Listen for switch-tab signal from each tab
00219         '''
00220         for key, value in self.tab.items():
00221             self.tab_cb_ids.append(( key, 
00222                                     self.tab[key].connect ('switch-tab', 
00223                                                             self.change_tab)
00224                                     ))
00225         '''
00226 
00227         # Listen for selected signal from the views
00228         self.connect('selected', self.select_artist)
00229 
00230         # lets remember info paned click
00231         self.info_paned.connect('button_press_event',
00232                                 self.paned_button_press_callback)
00233         self.info_paned.connect('button-release-event',
00234                                 self.paned_button_release_callback)
00235 
00236         # lets also listen for changes to the view to set the paned position
00237         self.source.viewmgr.connect('new-view', self.on_view_changed)
00238 
00239     def on_view_changed(self, widget, view_name):
00240         self._change_paned_pos(view_name)
00241 
00242     def _change_paned_pos(self, view_name):
00243         print (self.paned_pos)
00244         paned_positions = eval(self.paned_pos)
00245 
00246         found = None
00247         for viewpos in paned_positions:
00248             if view_name in viewpos:
00249                 found = viewpos
00250                 break
00251 
00252         if not found:
00253             return
00254 
00255         child_width = int(found.split(":")[1])
00256 
00257         calc_pos = self.source.page.get_allocated_width() - child_width
00258         self.info_paned.set_position(calc_pos)
00259         self.info_paned.set_visible(True)
00260 
00261     def _get_child_width(self):
00262         child = self.info_paned.get_child2()
00263         return child.get_allocated_width()
00264 
00265     def paned_button_press_callback(self, *args):
00266         print ('paned_button_press_callback')
00267         self._from_paned_handle = True
00268 
00269     def paned_button_release_callback(self, widget, *args):
00270         '''
00271         Callback when the artist paned handle is released from its mouse click.
00272         '''
00273         if not self._from_paned_handle:
00274             return False
00275         else:
00276             self._from_paned_handle = False
00277 
00278         print ("paned_button_release_callback")
00279         child_width = self._get_child_width()
00280 
00281         paned_positions = eval(self.paned_pos)
00282 
00283         found = None
00284         for viewpos in paned_positions:
00285             if self.source.viewmgr.view_name in viewpos:
00286                 found = viewpos
00287                 break
00288 
00289         if not found:
00290             return True
00291 
00292         paned_positions.remove(found)
00293         if child_width <= self.min_paned_pos:
00294             if int(found.split(':')[1]) == 0:
00295                 child_width = self.min_paned_pos + 1
00296                 calc_pos = self.source.page.get_allocated_width() - child_width
00297                 print ("opening")
00298             else:
00299                 child_width = 0
00300                 calc_pos = self.source.page.get_allocated_width()
00301                 print ("smaller")
00302 
00303             self.info_paned.set_position(calc_pos)
00304 
00305         paned_positions.append(self.source.viewmgr.view_name + ":" + str(child_width))
00306 
00307         self.paned_pos = repr(paned_positions)
00308         print ("End artist_info_paned_button_release_callback")
00309 
00310     def select_artist(self, widget, artist, album_title):
00311         print ("artist %s title %s" % (artist, album_title))
00312         if self._get_child_width() > self.min_paned_pos:
00313             self.view[self.current].reload(artist, album_title)
00314         else:
00315             self.view[self.current].blank_view()
00316 
00317         self.current_album_title = album_title
00318         self.current_artist = artist
00319 
00320     def change_stack(self, widget, value):
00321         child_name = self.stack.get_visible_child_name()
00322         if child_name and self.current != child_name:
00323             self.view[self.current].deactivate()
00324             if self._get_child_width() > self.min_paned_pos:
00325                 self.view[child_name].activate(self.current_artist, self.current_album_title)
00326             else:
00327                 self.view[child_name].blank_view()
00328 
00329             self.current = child_name
00330 
00331 
00332 class BaseInfoView(GObject.Object):
00333     def __init__(self, *args, **kwargs):
00334         super(BaseInfoView, self).__init__()
00335 
00336     def initialise(self, source, shell, plugin, stack, ds, view_name, view_image):
00337         self.stack = stack
00338 
00339         self.webview = ArtistInfoWebView()
00340         self.webview.initialise(source, shell)
00341 
00342         self.info_scrolled_window = Gtk.ScrolledWindow()
00343         self.info_scrolled_window.props.hexpand = True
00344         self.info_scrolled_window.props.vexpand = True
00345         self.info_scrolled_window.set_shadow_type(Gtk.ShadowType.IN)
00346         self.info_scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
00347         self.info_scrolled_window.add(self.webview)
00348         self.info_scrolled_window.show_all()
00349         self.stack.add_named(self.info_scrolled_window, view_name)
00350 
00351         theme = Gtk.IconTheme()
00352         default = theme.get_default()
00353         image_name = 'coverart_browser_' + view_name
00354         width, height = get_stock_size()
00355         pixbuf = create_button_image(plugin, view_image)
00356         default.add_builtin_icon(image_name, width, pixbuf)
00357 
00358         self.stack.child_set_property(self.info_scrolled_window, "icon-name", image_name)
00359 
00360         self.ds = ds
00361         self.shell = shell
00362         self.plugin = plugin
00363         self.file = ""
00364         self.album_title = None
00365         self.artist = None
00366         self.active = False
00367 
00368         plugindir = plugin.plugin_info.get_data_dir()
00369         self.basepath = "file://" + urllib.request.pathname2url(plugindir)
00370         self.link_images = self.basepath + '/img/links/'
00371 
00372         self.load_tmpl()
00373         self.connect_signals()
00374 
00375     def load_tmpl(self):
00376         pass
00377 
00378     def connect_signals(self):
00379         pass
00380 
00381     def load_view(self):
00382         print ("load_view")
00383         self.webview.load_string(self.file, 'text/html', 'utf-8', self.basepath)
00384         print ("end load_view")
00385 
00386     def blank_view(self):
00387         render_file = self.empty_template.render(stylesheet=self.styles)
00388         self.webview.load_string(render_file, 'text/html', 'utf-8', self.basepath)
00389 
00390     def loading(self, current_artist, current_album_title):
00391         pass
00392 
00393     def activate(self, artist=None, album_title=None):
00394         print("activating Artist Tab")
00395         self.active = True
00396         self.reload(artist, album_title)
00397 
00398     def deactivate(self):
00399         print("deactivating Artist Tab")
00400         self.active = False
00401 
00402 
00403 class ArtistInfoView(BaseInfoView):
00404     def __init__(self, *args, **kwargs):
00405         super(ArtistInfoView, self).__init__(self, *args, **kwargs)
00406 
00407     def initialise(self, source, shell, plugin, stack, ds, link_ds):
00408         super(ArtistInfoView, self).initialise(source, shell, plugin, stack, ds, "artist", "microphone.png")
00409 
00410         self.link_ds = link_ds
00411 
00412     def loading(self, current_artist, current_album_title):
00413         cl = CoverLocale()
00414         cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
00415 
00416         self.link_ds.set_artist(current_artist)
00417         self.link_ds.set_album(current_album_title)
00418         self.loading_file = self.loading_template.render(
00419             artist=current_artist,
00420             info=_("Loading biography for %s") % current_artist,
00421             song="",
00422             basepath=self.basepath)
00423         self.webview.load_string(self.loading_file, 'text/html', 'utf-8', self.basepath)
00424 
00425     def load_tmpl(self):
00426         cl = CoverLocale()
00427         cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
00428 
00429         path = rb.find_plugin_file(self.plugin, 'tmpl/artist-tmpl.html')
00430         empty_path = rb.find_plugin_file(self.plugin, 'tmpl/artist_empty-tmpl.html')
00431         loading_path = rb.find_plugin_file(self.plugin, 'tmpl/loading.html')
00432         self.template = Template(filename=path)
00433         self.loading_template = Template(filename=loading_path)
00434         self.empty_template = Template(filename=empty_path)
00435         self.styles = self.basepath + '/tmpl/artistmain.css'
00436 
00437     def connect_signals(self):
00438         self.air_id = self.ds.connect('artist-info-ready', self.artist_info_ready)
00439 
00440     def artist_info_ready(self, ds):
00441         # Can only be called after the artist-info-ready signal has fired.
00442         # If called any other time, the behavior is undefined
00443         try:
00444             info = ds.get_artist_info()
00445 
00446             small, med, big = info['images'] or (None, None, None)
00447             summary, full_bio = info['bio'] or (None, None)
00448 
00449             link_album = self.link_ds.get_album()
00450             if not link_album:
00451                 link_album = ""
00452 
00453             links = self.link_ds.get_album_links()
00454             if not links:
00455                 links = {}
00456 
00457             self.file = self.template.render(artist=ds.get_current_artist(),
00458                                              error=ds.get_error(),
00459                                              image=med,
00460                                              fullbio=full_bio,
00461                                              shortbio=summary,
00462                                              datasource=lastfm_datasource_link(self.basepath),
00463                                              stylesheet=self.styles,
00464                                              album=link_album,
00465                                              art_links=self.link_ds.get_artist_links(),
00466                                              alb_links=links,
00467                                              link_images=self.link_images,
00468                                              similar=ds.get_similar_info())
00469             self.load_view()
00470         except Exception as e:
00471             print("Problem in info ready: %s" % e)
00472 
00473 
00474     def reload(self, artist, album_title):
00475         if not artist:
00476             return
00477 
00478         if self.active and artist_exceptions(artist):
00479             print("blank")
00480             self.blank_view()
00481             return
00482 
00483         #self.stack.set_visible_child_name(self.view_name)
00484         if self.active and (   (not self.artist or self.artist != artist)
00485                                or (not self.album_title or self.album_title != album_title)
00486         ):
00487             print("now loading")
00488             self.loading(artist, album_title)
00489             print("active")
00490             self.ds.fetch_artist_data(artist)
00491         else:
00492             print("load_view")
00493             self.load_view()
00494 
00495         self.album_title = album_title
00496         self.artist = artist
00497 
00498 
00499 class ArtistDataSource(GObject.GObject):
00500     __gsignals__ = {
00501         'artist-info-ready': (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, ())
00502     }
00503 
00504     def __init__(self, info_cache, ranking_cache):
00505         GObject.GObject.__init__(self)
00506 
00507         self.current_artist = None
00508         self.error = None
00509         #'                'signal'    : 'artist-info-ready', '
00510         self.artist = {
00511             'info': {
00512                 'data': None,
00513                 'function': 'getinfo',
00514                 'cache': info_cache,
00515                 'signal': 'artist-info-ready',
00516                 'parsed': False
00517             },
00518             'similar': {
00519                 'data': None,
00520                 'function': 'getsimilar',
00521                 'cache': info_cache,
00522                 'signal': 'artist-info-ready',
00523                 'parsed': False
00524             }
00525         }
00526 
00527     def fetch_artist_data(self, artist):
00528         """
00529         Initiate the fetching of all artist data. Fetches artist info, similar
00530         artists, artist top albums and top tracks. Downloads XML files from last.fm
00531         and saves as parsed DOM documents in self.artist dictionary. Must be called
00532         before any of the get_* methods.
00533         """
00534         self.current_artist = artist
00535         if LastFM.user_has_account() is False:
00536             self.error = LASTFM_NO_ACCOUNT_ERROR
00537             self.emit('artist-info-ready')
00538             return
00539 
00540         self.error = None
00541         artist = urllib.parse.quote_plus(artist)
00542         self.fetched = 0
00543         for key, value in self.artist.items():
00544             print("search")
00545             cachekey = "lastfm:artist:%sjson:%s" % (value['function'], artist)
00546             url = '%s?method=artist.%s&artist=%s&limit=10&api_key=%s&format=json' % (LastFM.API_URL,
00547                                                                                      value['function'], artist,
00548                                                                                      LastFM.API_KEY)
00549             print("fetching %s" % url)
00550             value['cache'].fetch(cachekey, url, self.fetch_artist_data_cb, value)
00551 
00552     def fetch_artist_data_cb(self, data, category):
00553         if data is None:
00554             print("no data fetched for artist %s" % category['function'])
00555             return
00556 
00557         print(category)
00558         try:
00559             category['data'] = json.loads(data.decode('utf-8'))
00560             category['parsed'] = False
00561             self.fetched += 1
00562             if self.fetched == len(self.artist):
00563                 self.emit(category['signal'])
00564 
00565         except Exception as e:
00566             print("Error parsing artist %s: %s" % (category['function'], e))
00567             return False
00568 
00569     def get_current_artist(self):
00570         return self.current_artist
00571 
00572     def get_error(self):
00573         return self.error
00574 
00575     def get_artist_images(self):
00576         """
00577         Returns tuple of image url's for small, medium, and large images.
00578         """
00579         data = self.artist['info']['data']
00580         if data is None:
00581             return None
00582 
00583         images = [img['#text'] for img in data['artist'].get('image', ())]
00584         return images[:3]
00585 
00586     def get_artist_bio(self):
00587         """
00588         Returns tuple of summary and full bio
00589         """
00590         data = self.artist['info']['data']
00591         if data is None:
00592             return None
00593 
00594         if not self.artist['info']['parsed']:
00595             content = data['artist']['bio']['content']
00596             summary = data['artist']['bio']['summary']
00597             return summary, content
00598 
00599         return self.artist['info']['data']['bio']
00600 
00601     def get_similar_info(self):
00602         """
00603         Returns the dictionary { 'images', 'bio' }
00604         """
00605         if not self.artist['similar']['parsed']:
00606             json_artists_data = self.artist['similar']['data']['similarartists']
00607 
00608             results = []
00609             for json_artist in json_artists_data["artist"]:
00610                 name = json_artist["name"]
00611                 image_url = json_artist["image"][1]["#text"]
00612                 similarity = int(100 * float(json_artist["match"]))
00613 
00614                 results.append({'name': name,
00615                                 'image_url': image_url,
00616                                 'similarity': similarity})
00617 
00618             self.artist['similar']['data'] = results
00619             self.artist['similar']['parsed'] = True
00620 
00621         return self.artist['similar']['data']
00622 
00623     def get_artist_info(self):
00624         """
00625         Returns the dictionary { 'images', 'bio' }
00626         """
00627         if not self.artist['info']['parsed']:
00628             images = self.get_artist_images()
00629             bio = self.get_artist_bio()
00630             self.artist['info']['data'] = {'images': images,
00631                                            'bio': bio}
00632             self.artist['info']['parsed'] = True
00633 
00634         return self.artist['info']['data']
00635 
00636 
00637 class LinksDataSource(GObject.GObject):
00638     def __init__(self):
00639         GObject.GObject.__init__(self)
00640         print("init")
00641         self.entry = None
00642         self.error = None
00643 
00644         self.artist = None
00645         self.album = None
00646 
00647     def set_artist(self, artist):
00648         print("set_artist")
00649         self.artist = artist
00650 
00651     def get_artist(self):
00652         print("get_artist")
00653         return self.artist
00654 
00655     def set_album(self, album):
00656         self.album = album
00657 
00658     def get_album(self):
00659         return self.album
00660 
00661     def get_artist_links(self):
00662         """
00663         Return a dictionary with artist URLs to popular music databases and
00664         encyclopedias.
00665         """
00666         print("get_artist_links")
00667         artist = self.get_artist()
00668         if artist is not "" and artist is not None:
00669             wpartist = artist.replace(" ", "_")
00670             artist = urllib.parse.quote_plus(artist)
00671             artist_links = {
00672                 "Wikipedia": "http://www.wikipedia.org/wiki/%s" % wpartist,
00673                 "Discogs": "http://www.discogs.com/artist/%s" % artist,
00674                 "Allmusic": "http://www.allmusic.com/search/artist/%s" % artist
00675             }
00676             return artist_links
00677         print("no links returned")
00678         print(artist)
00679 
00680         return None
00681 
00682     def get_album_links(self):
00683         """
00684         Return a dictionary with album URLs to popular music databases and
00685         encyclopedias.
00686         """
00687         print("get_album_links")
00688         album = self.get_album()
00689         print(album)
00690         if album is not None and album is not "":
00691             print("obtaining links")
00692             wpalbum = album.replace(" ", "_")
00693             album = urllib.parse.quote_plus(album)
00694             album_links = {
00695                 "Wikipedia": "http://www.wikipedia.org/wiki/%s" % wpalbum,
00696                 "Discogs": "http://www.discogs.com/search?type=album&q=%s&f=html" % album,
00697                 "Allmusic": "http://allmusic.com/search/album/%s" % album
00698             }
00699             return album_links
00700         return None
00701 
00702     def get_error(self):
00703         if self.get_artist() is "":
00704             return _("No artist specified.")
00705 
00706 
00707 class AlbumInfoView(BaseInfoView):
00708     def __init__(self, *args, **kwargs):
00709         super(AlbumInfoView, self).__init__(self, *args, **kwargs)
00710 
00711     def initialise(self, source, shell, plugin, stack, ds):
00712         super(AlbumInfoView, self).initialise(source, shell, plugin, stack, ds, "album", "covermgr.png")
00713 
00714     def connect_signals(self):
00715         self.ds.connect('albums-ready', self.album_list_ready)
00716 
00717     def loading(self, current_artist, current_album_title):
00718         cl = CoverLocale()
00719         cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
00720 
00721         self.loading_file = self.loading_template.render(
00722             artist=current_artist,
00723             # Translators: 'top' here means 'most popular'.  %s is replaced by the artist name.
00724             info=_("Loading top albums for %s") % current_artist,
00725             song="",
00726             basepath=self.basepath)
00727         self.webview.load_string(self.loading_file, 'text/html', 'utf-8', self.basepath)
00728 
00729     def load_tmpl(self):
00730         cl = CoverLocale()
00731         cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
00732 
00733         path = rb.find_plugin_file(self.plugin, 'tmpl/album-tmpl.html')
00734         empty_path = rb.find_plugin_file(self.plugin, 'tmpl/album_empty-tmpl.html')
00735         self.loading_path = rb.find_plugin_file(self.plugin, 'tmpl/loading.html')
00736         self.album_template = Template(filename=path)
00737         self.loading_template = Template(filename=self.loading_path)
00738         self.empty_template = Template(filename=empty_path)
00739         self.styles = self.basepath + '/tmpl/artistmain.css'
00740 
00741     def album_list_ready(self, ds):
00742         print ("album_list_ready")
00743         self.file = self.album_template.render(error=ds.get_error(),
00744                                                albums=ds.get_top_albums(),
00745                                                artist=ds.get_artist(),
00746                                                datasource=lastfm_datasource_link(self.basepath),
00747                                                stylesheet=self.styles)
00748         self.load_view()
00749 
00750     def reload(self, artist, album_title):
00751         print ("reload")
00752         if not artist:
00753             return
00754 
00755         if self.active and artist_exceptions(artist):
00756             print("blank")
00757             self.blank_view()
00758             return
00759 
00760         if self.active and (not self.artist or artist != self.artist):
00761             self.loading(artist, album_title)
00762             self.ds.fetch_album_list(artist)
00763         else:
00764             self.load_view()
00765 
00766         self.album_title = album_title
00767         self.artist = artist
00768 
00769 
00770 class AlbumDataSource(GObject.GObject):
00771     __gsignals__ = {
00772         'albums-ready': (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, ())
00773     }
00774 
00775     def __init__(self, info_cache, ranking_cache):
00776         GObject.GObject.__init__(self)
00777         self.albums = None
00778         self.error = None
00779         self.artist = None
00780         self.max_albums_fetched = 8
00781         self.fetching = 0
00782         self.info_cache = info_cache
00783         self.ranking_cache = ranking_cache
00784 
00785     def get_artist(self):
00786         return self.artist
00787 
00788     def get_error(self):
00789         return self.error
00790 
00791     def fetch_album_list(self, artist):
00792         if LastFM.user_has_account() is False:
00793             self.error = LASTFM_NO_ACCOUNT_ERROR
00794             self.emit('albums-ready')
00795             return
00796 
00797         self.artist = artist
00798         qartist = urllib.parse.quote_plus(artist)
00799         self.error = None
00800         url = "%s?method=artist.gettopalbums&artist=%s&api_key=%s&format=json" % (
00801             LastFM.API_URL, qartist, LastFM.API_KEY)
00802         print(url)
00803         cachekey = 'lastfm:artist:gettopalbumsjson:%s' % qartist
00804         self.ranking_cache.fetch(cachekey, url, self.parse_album_list, artist)
00805 
00806     def parse_album_list(self, data, artist):
00807         if data is None:
00808             print("Nothing fetched for %s top albums" % artist)
00809             return False
00810 
00811         try:
00812             parsed = json.loads(data.decode("utf-8"))
00813         except Exception as e:
00814             print("Error parsing album list: %s" % e)
00815             return False
00816 
00817         self.error = parsed.get('error')
00818         if self.error:
00819             self.emit('albums-ready')
00820             return False
00821 
00822         try:
00823             albums = parsed['topalbums'].get('album', [])[:self.max_albums_fetched]
00824         except:
00825             albums = []
00826 
00827         if len(albums) == 0:
00828             self.error = "No albums found for %s" % artist
00829             self.emit('albums-ready')
00830             return True
00831         print(albums)
00832         self.albums = []
00833         print(len(albums))
00834         #albums = parsed['topalbums'].get('album', [])[:self.max_albums_fetched]
00835         self.fetching = len(albums)
00836         for i, a in enumerate(albums):
00837             try:
00838                 images = [img['#text'] for img in a.get('image', [])]
00839                 self.albums.append({'title': a.get('name'), 'images': images[:3]})
00840                 self.fetch_album_info(artist, a.get('name'), i)
00841             except:
00842                 pass
00843 
00844         return True
00845 
00846     def get_top_albums(self):
00847         return self.albums
00848 
00849     def fetch_album_info(self, artist, album, index):
00850         qartist = urllib.parse.quote_plus(artist)
00851         qalbum = urllib.parse.quote_plus(album)
00852         cachekey = "lastfm:album:getinfojson:%s:%s" % (qartist, qalbum)
00853         url = "%s?method=album.getinfo&artist=%s&album=%s&api_key=%s&format=json" % (
00854             LastFM.API_URL, qartist, qalbum, LastFM.API_KEY)
00855         self.info_cache.fetch(cachekey, url, self.parse_album_info, album, index)
00856 
00857     def parse_album_info(self, data, album, index):
00858         rv = True
00859         try:
00860             parsed = json.loads(data.decode('utf-8'))
00861             self.albums[index]['id'] = parsed['album']['id']
00862 
00863             for k in ('releasedate', 'summary'):
00864                 self.albums[index][k] = parsed['album'].get(k)
00865 
00866             tracklist = []
00867             tracks = parsed['album']['tracks'].get('track', [])
00868             for i, t in enumerate(tracks):
00869                 title = t['name']
00870                 duration = int(t['duration'])
00871                 tracklist.append((i, title, duration))
00872 
00873             self.albums[index]['tracklist'] = tracklist
00874             self.albums[index]['duration'] = sum([t[2] for t in tracklist])
00875 
00876             if 'wiki' in parsed['album']:
00877                 self.albums[index]['wiki-summary'] = parsed['album']['wiki']['summary']
00878                 self.albums[index]['wiki-content'] = parsed['album']['wiki']['content']
00879 
00880         except Exception as e:
00881             print("Error parsing album tracklist: %s" % e)
00882             rv = False
00883 
00884         self.fetching -= 1
00885         print("%s albums left to process" % self.fetching)
00886         if self.fetching == 0:
00887             self.emit('albums-ready')
00888 
00889         return rv
00890 
00891 
00892 class EchoArtistInfoView(BaseInfoView):
00893     def __init__(self, *args, **kwargs):
00894         super(EchoArtistInfoView, self).__init__(self, *args, **kwargs)
00895 
00896     def initialise(self, source, shell, plugin, stack, ds, link_ds):
00897         super(EchoArtistInfoView, self).initialise(source, shell, plugin, stack, ds, "echoartist",
00898                                                    "echonest_minilogo.gif")
00899 
00900         self.link_ds = link_ds
00901 
00902     def load_tmpl(self):
00903         cl = CoverLocale()
00904         cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
00905 
00906         path = rb.find_plugin_file(self.plugin, 'tmpl/echoartist-tmpl.html')
00907         empty_path = rb.find_plugin_file(self.plugin, 'tmpl/artist_empty-tmpl.html')
00908         loading_path = rb.find_plugin_file(self.plugin, 'tmpl/loading.html')
00909         self.template = Template(filename=path)
00910         self.loading_template = Template(filename=loading_path)
00911         self.empty_template = Template(filename=empty_path)
00912         self.styles = self.basepath + '/tmpl/artistmain.css'
00913         print(lastfm_datasource_link(self.basepath))
00914 
00915     def connect_signals(self):
00916         self.air_id = self.ds.connect('artist-info-ready', self.artist_info_ready)
00917 
00918     def artist_info_ready(self, ds):
00919         # Can only be called after the artist-info-ready signal has fired.
00920         # If called any other time, the behavior is undefined
00921         #try:
00922         link_album = self.link_ds.get_album()
00923         if not link_album:
00924             link_album = ""
00925 
00926         links = self.link_ds.get_album_links()
00927         if not links:
00928             links = {}
00929 
00930         print("#############")
00931         print(ds.get_current_artist())
00932         print(self.ds.get_artist_bio())
00933         print(self.styles)
00934         print(self.link_ds.get_artist_links())
00935         print(links)
00936         print(self.link_images)
00937         print(lastfm_datasource_link(self.basepath))
00938         print("##############")
00939         self.file = self.template.render(artist=ds.get_current_artist(),
00940                                          error=ds.get_error(),
00941                                          bio=self.ds.get_artist_bio(),
00942                                          stylesheet=self.styles,
00943                                          album=link_album,
00944                                          art_links=self.link_ds.get_artist_links(),
00945                                          alb_links=links,
00946                                          link_images=self.link_images,
00947                                          datasource=ds.get_attribution())
00948         self.load_view()
00949         #except Exception as e:
00950         #    print("Problem in info ready: %s" % e)
00951 
00952     def reload(self, artist, album_title):
00953         if not artist:
00954             return
00955 
00956         if self.active and artist_exceptions(artist):
00957             print("blank")
00958             self.blank_view()
00959             return
00960 
00961         #self.stack.set_visible_child_name(self.view_name)
00962         if self.active and (   (not self.artist or self.artist != artist)
00963                                or (not self.album_title or self.album_title != album_title)
00964         ):
00965             print("now loading")
00966             self.loading(artist, album_title)
00967             print("active")
00968             self.ds.fetch_artist_data(artist)
00969         else:
00970             print("load_view")
00971             self.load_view()
00972 
00973         self.album_title = album_title
00974         self.artist = artist
00975 
00976 
00977 class EchoArtistDataSource(GObject.GObject):
00978     __gsignals__ = {
00979         'artist-info-ready': (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, ())
00980     }
00981 
00982     def __init__(self, info_cache, ranking_cache):
00983         GObject.GObject.__init__(self)
00984 
00985         self.current_artist = None
00986         self.error = None
00987         self.artist = {
00988             'info': {
00989                 'data': None,
00990                 'cache': info_cache,
00991                 'signal': 'artist-info-ready',
00992                 'parsed': False
00993             }
00994         }
00995 
00996     def fetch_artist_data(self, artist):
00997         """
00998         Initiate the fetching of all artist data. Fetches artist info, similar
00999         artists, artist top albums and top tracks. Downloads XML files from last.fm
01000         and saves as parsed DOM documents in self.artist dictionary. Must be called
01001         before any of the get_* methods.
01002         """
01003         self.current_artist = artist
01004 
01005         self.error = None
01006         artist = urllib.parse.quote_plus(artist)
01007         self.fetched = 0
01008         for key, value in self.artist.items():
01009             print("search")
01010             cachekey = "echonest:artist:json:%s" % (artist)
01011             api_url = "http://developer.echonest.com/api/v4/"
01012             api_key = "N685TONJGZSHBDZMP"
01013             url = '%sartist/biographies?api_key=%s&name=%s&format=json&results=1&start=0' % (api_url,
01014                                                                                              api_key, artist)
01015 
01016             #http://developer.echonest.com/api/v4/artist/biographies?api_key=N685TONJGZSHBDZMP&name=queen&format=json&results=1&start=0
01017             #http://developer.echonest.com/api/v4/artist/biographies?api_key=N685TONJGZSHBDZMP?name=ABBA&format=json&results=1&start=0
01018             print("fetching %s" % url)
01019             value['cache'].fetch(cachekey, url, self.fetch_artist_data_cb, value)
01020 
01021     def fetch_artist_data_cb(self, data, category):
01022         if data is None:
01023             print("no data fetched for artist")
01024             return
01025 
01026         print(category)
01027         try:
01028             category['data'] = json.loads(data.decode('utf-8'))
01029             category['parsed'] = False
01030             self.fetched += 1
01031             if self.fetched == len(self.artist):
01032                 self.emit(category['signal'])
01033 
01034         except Exception as e:
01035             print("Error parsing artist")
01036             return False
01037 
01038     def get_current_artist(self):
01039         return self.current_artist
01040 
01041     def get_error(self):
01042         return self.error
01043 
01044     def get_attribution(self):
01045         data = self.artist['info']['data']
01046         if data is None:
01047             return None
01048 
01049         content = ""
01050 
01051         if not self.artist['info']['parsed']:
01052             print(data)
01053             url = data['response']['biographies'][0]['url']
01054             site = data['response']['biographies'][0]['site']
01055             print(url)
01056             print(site)
01057             return "<a href='%s'>%s</a>" % (url, site)
01058 
01059         return content
01060 
01061     def get_artist_bio(self):
01062         """
01063         Returns tuple of summary and full bio
01064         """
01065         data = self.artist['info']['data']
01066         if data is None:
01067             return None
01068 
01069         if not self.artist['info']['parsed']:
01070             print(data)
01071             content = data['response']['biographies'][0]['text']
01072             return content
01073 
01074         return self.artist['info']['data']['response']['biographies'][0]['text']
 All Classes Functions