Last active
July 21, 2024 13:24
-
-
Save Dylancyclone/72fdb3515ed6b4150365c46fe07f4de8 to your computer and use it in GitHub Desktop.
MusicBrainz Picard LRCLIB Lyrics Plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from functools import partial | |
from urllib.parse import ( | |
quote, | |
urlencode, | |
) | |
from PyQt5.QtNetwork import QNetworkRequest | |
from picard import config, log | |
from picard.metadata import register_track_metadata_processor | |
from picard.track import Track | |
from picard.album import Album | |
from picard.ui.itemviews import ( | |
BaseAction, | |
register_track_action, | |
register_album_action, | |
) | |
from PyQt5 import QtWidgets | |
from picard.ui.options import ( | |
OptionsPage, | |
register_options_page, | |
) | |
from picard.config import BoolOption | |
PLUGIN_NAME = "LRCLIB Lyrics" | |
PLUGIN_AUTHOR = "Dylancyclone" | |
PLUGIN_DESCRIPTION = ( | |
"Fetch lyrics from LRCLIB.<br/>" | |
"Lyrics provided are for educational purposes and personal use only. Commercial use is not allowed." | |
) | |
PLUGIN_VERSION = "1.0.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" | |
lrclib_url = "https://lrclib.net/api/get" | |
def _request(ws, callback, queryargs=None, important=False): | |
if not queryargs: | |
queryargs = {} | |
ws.get_url( | |
url=lrclib_url, | |
handler=callback, | |
parse_response_type="json", | |
priority=True, | |
important=important, | |
queryargs=queryargs, | |
cacheloadcontrol=QNetworkRequest.PreferCache, | |
) | |
def search_for_lyrics(album, metadata, length, linked_files): | |
artist = metadata["artist"] | |
title = metadata["title"] | |
albumName = metadata["album"] | |
duration = int(length) | |
if not (artist and title and albumName and duration): | |
log.debug( | |
"{}: artist, title, album name, and duration are required to obtain lyrics".format( | |
PLUGIN_NAME | |
) | |
) | |
return | |
queryargs = { | |
"track_name": title, | |
"artist_name": artist, | |
"album_name": albumName, | |
"duration": duration, | |
} | |
album._requests += 1 | |
log.debug( | |
"{}: GET {}?{}".format(PLUGIN_NAME, quote(lrclib_url), urlencode(queryargs)) | |
) | |
_request( | |
album.tagger.webservice, | |
partial(process_search_response, album, metadata, linked_files), | |
queryargs, | |
) | |
def process_search_response(album, metadata, linked_files, response, reply, error): | |
if error or (response and not response.get("id", False)): | |
album._requests -= 1 | |
album._finalize_loading(None) | |
log.warning( | |
'{}: lyrics NOT found for track "{}" by {}'.format( | |
PLUGIN_NAME, metadata["title"], metadata["artist"] | |
) | |
) | |
return | |
try: | |
lyrics = ( | |
response["syncedLyrics"] | |
if response.get("syncedLyrics") | |
else response["plainLyrics"] | |
) | |
metadata["lyrics"] = lyrics | |
if linked_files: | |
for file in linked_files: | |
file.metadata["lyrics"] = lyrics | |
log.debug( | |
'{}: lyrics loaded for track "{}" by {}'.format( | |
PLUGIN_NAME, metadata["title"], metadata["artist"] | |
) | |
) | |
except (TypeError, KeyError, ValueError): | |
log.warn( | |
'{}: lyrics NOT loaded for track "{}" by {}'.format( | |
PLUGIN_NAME, metadata["title"], metadata["artist"] | |
), | |
exc_info=True, | |
) | |
finally: | |
album._requests -= 1 | |
album._finalize_loading(None) | |
class LrclibLyricsOptionsPage(OptionsPage): | |
NAME = "lrclib_lyrics" | |
TITLE = "LRCLIB Lyrics" | |
PARENT = "plugins" | |
options = [BoolOption("setting", "search_on_load", False)] | |
def __init__(self, parent=None): | |
super().__init__(parent) | |
self.box = QtWidgets.QVBoxLayout(self) | |
self.input = QtWidgets.QCheckBox("Search for lyrics when loading tracks", self) | |
self.box.addWidget(self.input) | |
self.spacer = QtWidgets.QSpacerItem( | |
0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding | |
) | |
self.box.addItem(self.spacer) | |
self.description = QtWidgets.QLabel(self) | |
self.description.setText( | |
"LRCLIB Music provides millions of lyrics from artist all around the world.\n" | |
"Lyrics provided are for educational purposes and personal use only. Commercial use is not allowed.\n" | |
"If searching for lyrics when loading tracks, the loading process will be slowed significantly." | |
) | |
self.description.setOpenExternalLinks(True) | |
self.box.addWidget(self.description) | |
def load(self): | |
self.input.setChecked(config.setting["search_on_load"]) | |
def save(self): | |
config.setting["search_on_load"] = self.input.checkState() | |
class LrclibLyricsMetadataProcessor: | |
def __init__(self): | |
super().__init__() | |
def process_metadata(self, album, metadata, track, release): | |
if config.setting["search_on_load"]: | |
search_for_lyrics(album, metadata, int(track["length"]) / 1000) | |
class LrclibLyricsTrackAction(BaseAction): | |
NAME = "Search for lyrics with LRCLIB..." | |
def execute_on_track(self, track): | |
length = track.metadata["~length"].split(":") | |
length = int(length[0]) * 60 + int(length[1]) | |
search_for_lyrics(track.album, track.metadata, length, track.linked_files) | |
def callback(self, objs): | |
for item in (t for t in objs if isinstance(t, Track) or isinstance(t, Album)): | |
if isinstance(item, Track): | |
log.debug("{}: {}, {}".format(PLUGIN_NAME, item, item.album)) | |
self.execute_on_track(item) | |
elif isinstance(item, Album): | |
for track in item.tracks: | |
log.debug("{}: {}, {}".format(PLUGIN_NAME, track, item)) | |
self.execute_on_track(track) | |
register_track_metadata_processor(LrclibLyricsMetadataProcessor().process_metadata) | |
register_track_action(LrclibLyricsTrackAction()) | |
register_album_action(LrclibLyricsTrackAction()) | |
register_options_page(LrclibLyricsOptionsPage) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment