Hi, For a while I had a some code in my EMMS config for fetching synced lyrics from LRCLIB (lrclib.net). The main function can be used both interactively and as a track initialization function (interactively it fetches lyrics, optionally overwriting, for the currently playing track).
This service used to be non-free, though the developer promised to free it at some point in the future. Well, it turns out this actually happened (in April actually, I didn't notice), and the server software is now licensed under MIT, so I separated this code into its own file and integrated it into EMMS (patch attached). I think this will be a nice addition to EMMS, should this be added? Daniel
>From b2b64c7d41e35434fc6ce36228eb4dadb84e43ac Mon Sep 17 00:00:00 2001 From: Daniel Semyonov <[email protected]> Date: Thu, 28 Nov 2024 01:49:13 +0200 Subject: [PATCH] Add an EMMS module for fetching synchronized lyrics from LRCLIB --- emms-lyrics-lrclib.el | 113 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 emms-lyrics-lrclib.el diff --git a/emms-lyrics-lrclib.el b/emms-lyrics-lrclib.el new file mode 100644 index 0000000..272f6cb --- /dev/null +++ b/emms-lyrics-lrclib.el @@ -0,0 +1,113 @@ +;;; emms-lyrics-lrclib.el --- Fetch synchronized lyrics through LRCLIB -*- lexical-binding: t; -*- + +;; Copyright (C) 2024 Free Software Foundation, Inc. + +;; Author: Daniel Semyonov <[email protected]> + +;; This file is part of EMMS. + +;; EMMS is free software; you can redistribute it and/or modify it +;; under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; EMMS is distributed in the hope that it will be useful, but WITHOUT +;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +;; License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with EMMS; see the file COPYING. If not, write to the Free +;; Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +;; MA 02110-1301, USA. + +;;; Commentary: + +;; This file provides a command/track initialization function which +;; automatically fetches synchronized lyrics for tracks (the current +;; track interactively) through an LRCLIB server. + +;;; Code: + +(require 'emms-lyrics) +(require 'emms-later-do) + +(defgroup emms-lyrics-lrclib nil + "EMMS module for fetching synchronized lyrics through LRCLIB servers." + :group 'emms-lyrics + :prefix "emms-lyrics-lrclib-") + +(defcustom emms-lyrics-lrclib-url "https://lrclib.net/api/" + "Base URL for LRCLIB API requests." + :type 'string) + +(defconst emms-lyrics-lrclib-max-requests 250 + "Maximum number of concurrent requests to LRCLIB.") + +(defvar emms-lyrics-lrclib-requests 0 + "Current number of concurrent requests to LRCLIB.") + +(defun emms-lyrics-lrclib-encode-name (name) + "Encode (artist/album/track) NAME for an LRCLIB search." + (and (stringp name) (string-replace " " "+" name))) + +(defun emms-lyrics-lrclib-parse (_ file track interactive) + "Parse and save synced lyrics in FILE. +If TRACK is the selected track in the current playlist, catch up. +When INTERACTIVE is non-nil, display messages and confirm overwrite." + (unwind-protect + (progn + (search-forward "\n\n") + (let* ((p (json-parse-buffer)) + (lyrics (and (hash-table-p p) (gethash "syncedLyrics" p)))) + (if (not (stringp lyrics)) + (when interactive (message "No lyrics found")) + (or + (and (file-exists-p file) interactive + (not (y-or-n-p + (format "Overwrite existing file (\"%s\")?" file)))) + (with-temp-file file + (insert (gethash "syncedLyrics" p)) + (when interactive + (message "Saves synced lyrics at \"%s\"" file)) + (and (boundp 'emms-lyrics-display-p) + emms-lyrics-display-p emms-player-playing-p + (equal track (emms-playlist-current-selected-track)) + (emms-lyrics-catchup file))))))) + (setq emms-lyrics-lrclib-requests (1- emms-lyrics-lrclib-requests)))) + +;;;###autoload +(defun emms-lyrics-lrclib-get (&optional track force interactive) + "Search for synchronized lyrics for TRACK through LRCLIB's API. +If TRACK is omitted or nil, try the selected track in the current playlist. +The lyrics are saved in an \".lrc\" file alongside the track, unless the +file already exists (in which case the search isn't performed). +With non-nil FORCE, overwrite existing \".lrc\" files. +With non-nil INTERACTIVE, display messages and confirm overwrite." + (interactive (list nil current-prefix-arg t)) + (if (> emms-lyrics-lrclib-requests emms-lyrics-lrclib-max-requests) + (emms-later-do #'emms-lyrics-lrclib-get track force interactive) + (when-let* ((track (or track (emms-playlist-current-selected-track))) + ((eq (emms-track-type track) 'file)) + (file (emms-track-name track)) + (lrc (replace-regexp-in-string "\\.[^.]+\\'" ".lrc" file)) + ((or force (not (file-exists-p lrc)))) + (file-writable-p lrc) + (title (emms-lyrics-lrclib-encode-name + (emms-track-get track 'info-title))) + (artist (emms-lyrics-lrclib-encode-name + (emms-track-get track 'info-artist))) + (album (emms-lyrics-lrclib-encode-name + (emms-track-get track 'info-album))) + (time (emms-track-get track 'info-playing-time))) + (setq emms-lyrics-lrclib-requests (1+ emms-lyrics-lrclib-requests)) + (when interactive (message "Searching for lyrics...")) + (url-retrieve + (url-encode-url + (format "%sget?artist_name=%s&track_name=%s&album_name=%s&duration=%d" + emms-lyrics-lrclib-url artist title album time)) + #'emms-lyrics-lrclib-parse (list lrc track interactive))))) + +(provide 'emms-lyrics-lrclib) + +;;; emms-lyrics-lrclib.el ends here -- 2.47.0
