title: Plex Plugin for Music Videos - Python
body: <p>I'm trying to edit a LastFM music plugin for Plex to make it work with music videos.  I'm almost there with lots of Googling and a few questions on here, but I am stuck on the fact that the artists seem to group together.</p>
  
  <p>My videos are in the format "Artist - Title.ext" and I have set that as the metadata title but it still seems to group.</p>
  
  <p>Even if I split the videos after they have grouped in Plex, they all have the name of the first video.</p>
  
  <p>I'm not sure if this is the right place to ask, but here is the code.  If anyone can help then it would be greatly appreciated.</p>
  
  <pre><code>    import lastfm, re, time, os, urllib
  
  GOOGLE_JSON = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&amp;userip=%s&amp;rsz=large&amp;q=%s+site:last.fm+inurl:music'
  
  def Start():
    HTTP.CacheTime = CACHE_1WEEK
  
  def GetPublicIP():
    return HTTP.Request('http://plexapp.com/ip.php').content.strip()
  
  def GetGoogleArtist(artist):
    try:
      url = GOOGLE_JSON % (GetPublicIP(), String.Quote(artist.encode('utf-8'), usePlus=True))
      jsonObj = JSON.ObjectFromURL(url, headers={'Referer' : 'http://www.plexapp.com'}, sleep=0.5)
      if jsonObj['responseData'] != None:
        jsonObj = jsonObj['responseData']['results']
        if len(jsonObj) &gt; 0:
          result = jsonObj[0]
          url = result['unescapedUrl'].replace('+','%20')
          return re.findall('/music/([^/]+)', url)[0]
    except:
      pass
  
    return None
  
  def CallWithRetries(fun, *args):
    tries = 10
    while tries &gt; 0:
      try:
        return fun(*args)
      except:
        tries = tries - 1
        if tries &gt; 0:
          Log('Call failed, retrying')
          time.sleep(2)
        else:
          raise
  
  class LastFmAgent(Agent.Movies, Agent.Artist):
    name = 'Music Video'
    languages = [Locale.Language.English, Locale.Language.Korean]
  
    def safe_strip(self, ss):
      """
        This method strips the diacritic marks from a string, but if it's too extreme (i.e. would remove everything,
        as is the case with some foreign text), then don't perform the strip.
      """
      s = String.StripDiacritics(ss)
      if len(s.strip()) == 0:
        return ss
      return s
  
    def search(self, results, media, lang):
  
      path = media.filename
      path = urllib.unquote(path)
      filename = os.path.splitext(os.path.basename(path))[0]
      Log(filename)
      artist = filename.split(' - ')[0]
      artist = artist.lower()
      Log(artist)
  
  
      Log("Search for artist")
      CallWithRetries(self.findArtists, lang, results, media, artist)
  
      Log("If the artist starts with 'The', try stripping.")
      if artist.startswith('the '):
        try: CallWithRetries(self.findArtists, lang, results, media, artist[4:])
        except: pass
  
      Log("If the artist has an '&amp;', try with 'and'.")
      if artist.find(' &amp; ') != -1:
        try: CallWithRetries(self.findArtists, lang, results, media, artist.replace(' &amp; ', ' and '))
        except: pass
  
      Log("If the artist has an 'and', try with '&amp;'.")
      if artist.find(' and ') != -1:
        try: CallWithRetries(self.findArtists, lang, results, media, artist.replace(' and ', ' &amp; '))
        except: pass
  
      try: highest_score = max([x.score for x in results])
      except: highest_score = 0
  
      if len(results) == 0 or highest_score &lt; 85:
        artist_id = GetGoogleArtist(artist)
        google_artist = CallWithRetries(lastfm.ArtistInfo, artist_id)
        if google_artist:
          (url, name, image, listeners) = google_artist
          Log("Google said 'you should try %s' (ID: %s)." % (name, artist_id))
          if listeners &gt; 250:
            results.Append(MetadataSearchResult(id=artist_id, name=name, thumb=image, lang=lang, score = 100-Util.LevenshteinDistance(name.lower(), artist.lower())))
  
      Log("Finally, de-dupe the results.")
      toWhack = []
      resultMap = {}
      for result in results:
        if not resultMap.has_key(result.id):
          resultMap[result.id] = True
        else:
          toWhack.append(result)
      for dupe in toWhack:
        results.Remove(dupe)
  
    def findArtists(self, lang, results, media, artist):
      score = 90
      for r in lastfm.SearchArtists(artist,limit=5)[0]:
        id = r[0]
        Log(id)
  
        # Skip artists without many listeners, they're probanly wrong.
        if r[3] &lt; 1000 and id.find('+noredirect') == -1:
          Log("Skipping %s with only %d listeners." % (r[1], r[3]))
          continue
  
        if id.find('+noredirect') == -1:
          id = r[1]
          dist = Util.LevenshteinDistance(r[1].lower(), artist.lower())
          albumBonus = self.bonusArtistMatchUsingAlbums(media, artistID=id, maxBonus=5)
          id = String.Quote(id.encode('utf-8'))
          Log('artist: ' + artist + ' albumBonus: ' + str(albumBonus))
          Log('Artist result: ' + r[1] + ' id: ' + id + ' score: ' + str(score) + ' thumb: ' + str(r[2]))
          results.Append(MetadataSearchResult(id = id.replace('%2B','%20'), name = r[1], thumb = r[2], lang  = lang, score = score + albumBonus - dist))
        else:
          # Get a correction.
          Log('Getting correction to artist.')
          correctArtists = lastfm.CorrectedArtists(artist)
          for result in correctArtists:
            id = String.Quote(result[0].encode('utf-8'))
            dist = Util.LevenshteinDistance(result[0].lower(), artist.lower())
            results.Append(MetadataSearchResult(id = id.replace('%2B','%20'), name = result[0], lang  = lang, score = score - dist + 5))
  
        score = score - 2
  
    def bonusArtistMatchUsingAlbums(self, media, artistID, maxBonus=5):
      Log('bonusArtistMatchUsingAlbums')
      lastFM_artistAlbums = []
      for album in lastfm.ArtistAlbums(artistID):
        (name, artist, thumb, url) = album
        lastFM_artistAlbums.append(name.lower())
      if len(lastFM_artistAlbums) == 0: return 0 #no last.fm albums for the artist, so abort!
      bonus = 0
      for a in media.children:
        album = a.title.lower()
        for lfa in lastFM_artistAlbums:
          score = Util.LevenshteinDistance(lfa, album)
          #Log(lfa, album, score)
          if score &lt;= 2: #pretty solid match
            bonus += 1
            if bonus == maxBonus: break
        if bonus == 0 and album[-1:] == ')': #if we got nothing, let's try again without anything in paranthesis [e.g.'limited edition'] 
          album = album[:album.rfind('(')].strip()
          for lfa in lastFM_artistAlbums:
            score = Util.LevenshteinDistance(lfa, album)
            #Log(lfa, album, score)
            if score &lt;= 2: #pretty solid match
              bonus += 1
              if bonus == maxBonus: break
      return bonus
  
    def update(self, metadata, media, lang):
      artist = CallWithRetries(XML.ElementFromURL, lastfm.ARTIST_INFO % String.Quote(String.Unquote(metadata.id), True))[0]
      summary = artist.xpath('//bio/content')[0]
      metadata.title = String.Unquote(artist.xpath('//artist/name')[0].text, True)
      Log(metadata.title)
      filename = media.items[0].parts[0].file.decode('utf-8')
      cleanname = os.path.splitext(os.path.basename(filename))[0]
      Log(cleanname)
      metadata.title = cleanname
      Log(metadata.title)
      Log("QWERTY")
      if summary.text:
        metadata.summary = decodeXml(re.sub(r'&lt;[^&lt;&gt;]+&gt;', '', summary.text))
      try:
        url = artist.xpath('//artist/image[@size="mega"]//text()')[0]
        if url not in metadata.posters:
          metadata.posters[url] = Proxy.Media(HTTP.Request(url))
      except:
        pass     
      metadata.genres.clear()
      for genre in artist.xpath('//artist/tags/tag/name'):
        metadata.genres.add(genre.text.capitalize())
  
  def decodeXml(text):
    trans = [('&amp;amp;','&amp;'),('&amp;quot;','"'),('&amp;lt;','&lt;'),('&amp;gt;','&gt;'),('&amp;apos;','\''),('\n ','\n')]
    for src, dst in trans:
      text = text.replace(src, dst)
    return text      
  </code></pre>