from functools import partial
from urllib.parse import (
quote,
urlencode,
urlparse,
)
from PyQt5 import QtWidgets
from picard import (
config,
log,
)
from picard.config import TextOption
from picard.metadata import register_track_metadata_processor
from picard.ui.options import (
OptionsPage,
register_options_page,
)
from picard.webservice import ratecontrol
PLUGIN_NAME = 'Happi.dev Lyrics'
PLUGIN_AUTHOR = 'Andrea Avallone, Philipp Wolfer'
PLUGIN_DESCRIPTION = 'Fetch lyrics from Happi.dev Lyrics, which provides millions of lyrics from artist all around the world. ' \
'Lyrics provided are for educational purposes and personal use only. Commercial use is not allowed.
' \
'In order to use Happi.dev you need to get a free API key at happi.dev'
PLUGIN_VERSION = '2.0'
PLUGIN_API_VERSIONS = ['2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6']
PLUGIN_LICENSE = 'MIT'
PLUGIN_LICENSE_URL = 'https://opensource.org/licenses/MIT'
class HappidevLyricsMetadataProcessor:
happidev_host = 'api.happi.dev'
happidev_port = 443
happidev_delay = 60 * 1000 / 100 # 100 requests per minute
def __init__(self):
super().__init__()
ratecontrol.set_minimum_delay(
(self.happidev_host, self.happidev_port), self.happidev_delay)
def process_metadata(self, album, metadata, track, release):
artist = metadata['artist']
if not artist:
log.debug(
'{}: artist is missing, please provide a valid value'.format(PLUGIN_NAME))
return
title = metadata['title']
if not title:
log.debug(
'{}: title is missing, please provide a valid value'.format(PLUGIN_NAME))
return
path = '/v1/music'
queryargs = {
'q': '"{}" "{}"'.format(artist, title),
'lyrics': 'true',
'type': 'track',
}
album._requests += 1
log.debug('{}: GET {}?{}'.format(PLUGIN_NAME, quote(path), urlencode(queryargs)))
self._request(album.tagger.webservice, path,
partial(self.process_search_response, album, metadata), queryargs)
def _request(self, ws, path, callback, queryargs=None):
if not queryargs:
queryargs = {}
apikey = config.setting['happidev_apikey']
if not apikey:
error = 'API key is missing, please provide a valid value'
log.debug('{}: {}'.format(PLUGIN_NAME, error))
callback(None, None, error)
return
queryargs['apikey'] = apikey
ws.get(self.happidev_host, self.happidev_port, path, callback,
parse_response_type='json', priority=True, queryargs=queryargs)
def process_search_response(self, album, metadata, response, reply, error):
if error or (response and (not response.get('success', False) or not response.get('length', 0))):
log.debug('{}: lyrics NOT found for track {}'.format(
PLUGIN_NAME, metadata['title']))
album._requests -= 1
album._finalize_loading(None)
return
try:
lyrics_url = response['result'][0]['api_lyrics']
log.debug('{}: lyrics found for track {} at {}'.format(
PLUGIN_NAME, metadata['title'], lyrics_url))
path = urlparse(lyrics_url).path
album._requests += 1
self._request(album.tagger.webservice, path,
partial(self.process_lyrics_response, album, metadata))
except (TypeError, KeyError, ValueError):
log.warn('{}: failed parsing search response for {}'.format(
PLUGIN_NAME, metadata['title']), exc_info=True)
finally:
album._requests -= 1
album._finalize_loading(None)
@staticmethod
def process_lyrics_response(album, metadata, response, reply, error):
if error or (response and not response.get('success', False)):
log.debug('{}: lyrics NOT loaded for track {}'.format(
PLUGIN_NAME, metadata['title']))
album._requests -= 1
album._finalize_loading(None)
return
try:
lyrics = response['result']['lyrics']
metadata['lyrics'] = lyrics
log.debug('{}: lyrics loaded for track {}'.format(
PLUGIN_NAME, metadata['title']))
except (TypeError, KeyError):
log.warn('{}: failed parsing search response for {}'.format(
PLUGIN_NAME, metadata['title']), exc_info=True)
finally:
album._requests -= 1
album._finalize_loading(None)
class HappidevLyricsOptionsPage(OptionsPage):
NAME = 'apiseeds_lyrics'
TITLE = 'Happi.dev Lyrics'
PARENT = 'plugins'
options = [TextOption('setting', 'happidev_apikey', '')]
def __init__(self, parent=None):
super().__init__(parent)
self.box = QtWidgets.QVBoxLayout(self)
self.label = QtWidgets.QLabel(self)
self.label.setText('Apiseeds API key')
self.box.addWidget(self.label)
self.description = QtWidgets.QLabel(self)
self.description.setText('Happi.dev Music provides millions of lyrics from artist all around the world. '
'Lyrics provided are for educational purposes and personal use only. Commercial use is not allowed. '
'In order to use Happi.dev Music you need to get a free API key here.')
self.description.setOpenExternalLinks(True)
self.box.addWidget(self.description)
self.input = QtWidgets.QLineEdit(self)
self.box.addWidget(self.input)
self.spacer = QtWidgets.QSpacerItem(
0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.box.addItem(self.spacer)
def load(self):
self.input.setText(config.setting['happidev_apikey'])
def save(self):
config.setting['happidev_apikey'] = self.input.text()
register_track_metadata_processor(HappidevLyricsMetadataProcessor().process_metadata)
register_options_page(HappidevLyricsOptionsPage)