Repository: beetbox/beets

Issue:
Import Lyrics from Genius
Might it be possible to add http://genius.com as a source to the `lyrics` plugin? They have an API, documented at https://docs.genius.com/ and a vast collection of song lyrics (eg. for “Neongrüner Auswurf” by German group [Trailerpark](http://musicbrainz.org/artist/64d73a2c-714e-48d5-95ab-bc1f406bcc71): [MusicBrainz](http://musicbrainz.org/recording/b387088e-df44-4d6c-a2cb-f7a1dd3598d9), [RapGenius](http://genius.com/Trailerpark-neongruner-auswurf-lyrics)).

(It appears that [Musixmatch has the lyrics as well](https://www.musixmatch.com/lyrics/Trailerpark/Neongr%C3%BCner-Auswurf), but the plugin doesn’t seem to find them.)



Discussion:
Seems like a great source. I can't quite tell from the docs whether the API actually exposes lyric text, but it's worth exploring.

I vaguely recall someone on IRC talking about workin on getting lyrics from Genius. It might be worth searching around to see if they made any progress.


Resolving patch:
diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py
--- a/beetsplug/lyrics.py
+++ b/beetsplug/lyrics.py
@@ -224,6 +224,70 @@ def fetch(self, artist, title):
         return lyrics.strip(',"').replace('\\n', '\n')
 
 
+class Genius(Backend):
+    """Fetch lyrics from Genius via genius-api."""
+    def __init__(self, config, log):
+        super(Genius, self).__init__(config, log)
+        self.api_key = config['genius_api_key'].get(unicode)
+        self.headers = {'Authorization': "Bearer %s" % self.api_key}
+
+    def search_genius(self, artist, title):
+        query = u"%s %s" % (artist, title)
+        url = u'https://api.genius.com/search?q=%s' \
+            % (urllib.quote(query.encode('utf8')))
+
+        data = requests.get(
+            url,
+            headers=self.headers,
+            allow_redirects=True
+        ).content
+
+        return json.loads(data)
+
+    def get_lyrics(self, link):
+        url = u'http://genius-api.com/api/lyricsInfo'
+
+        data = requests.post(
+            url,
+            data={'link': link},
+            headers=self.headers,
+            allow_redirects=True
+        ).content
+
+        return json.loads(data)
+
+    def build_lyric_string(self, lyrics):
+        if 'lyrics' not in lyrics:
+            return
+        sections = lyrics['lyrics']['sections']
+
+        lyrics_list = []
+        for section in sections:
+            lyrics_list.append(section['name'])
+            lyrics_list.append('\n')
+            for verse in section['verses']:
+                if 'content' in verse:
+                    lyrics_list.append(verse['content'])
+
+        return ''.join(lyrics_list)
+
+    def fetch(self, artist, title):
+        search_data = self.search_genius(artist, title)
+
+        if not search_data['meta']['status'] == 200:
+            return
+        else:
+            records = search_data['response']['hits']
+            if not records:
+                return
+
+            record_url = records[0]['result']['url']
+            lyric_data = self.get_lyrics(record_url)
+            lyrics = self.build_lyric_string(lyric_data)
+
+            return lyrics
+
+
 class LyricsWiki(SymbolsReplaced):
     """Fetch lyrics from LyricsWiki."""
     URL_PATTERN = 'http://lyrics.wikia.com/%s:%s'
@@ -444,12 +508,13 @@ def fetch(self, artist, title):
 
 
 class LyricsPlugin(plugins.BeetsPlugin):
-    SOURCES = ['google', 'lyricwiki', 'lyrics.com', 'musixmatch']
+    SOURCES = ['google', 'lyricwiki', 'lyrics.com', 'musixmatch', 'genius']
     SOURCE_BACKENDS = {
         'google': Google,
         'lyricwiki': LyricsWiki,
         'lyrics.com': LyricsCom,
         'musixmatch': MusiXmatch,
+        'genius': Genius,
     }
 
     def __init__(self):
@@ -459,12 +524,16 @@ def __init__(self):
             'auto': True,
             'google_API_key': None,
             'google_engine_ID': u'009217259823014548361:lndtuqkycfu',
+            'genius_api_key':
+                "Ryq93pUGm8bM6eUWwD_M3NOFFDAtp2yEE7W"
+                "76V-uFL5jks5dNvcGCdarqFjDhP9c",
             'fallback': None,
             'force': False,
             'sources': self.SOURCES,
         })
         self.config['google_API_key'].redact = True
         self.config['google_engine_ID'].redact = True
+        self.config['genius_api_key'].redact = True
 
         available_sources = list(self.SOURCES)
         if not self.config['google_API_key'].get() and \


Test patch: