diff --git a/api/lyrics.php b/api/lyrics.php index e2d2270..c50a223 100644 --- a/api/lyrics.php +++ b/api/lyrics.php @@ -11,14 +11,16 @@ * "found": true|false, * "lyrics": "plain text\nwith newlines", * "synced": [{ "time": 12.34, "text": "…" }, …], // [] when unavailable - * "url": "…" + * "url": "…", + * "source": "lrclib" | "dumb" | "genius" * } * * Lyrics are returned as plain text (tags/timestamps stripped) so the * front-end can render them with textContent — no HTML injection from a * third-party page. `synced` (line-level LRC timestamps, seconds) is only * populated when LRCLIB has synced lyrics; the Genius fallback has no timing - * data so it's always [] there. + * data so it's always [] there. `url` is '' for LRCLIB (the front-end only + * shows a "source" link for dumb/genius). * * Note: the Genius fallback scrapes HTML, which can change at any time. If * Genius restructures their page the extraction may need updating. @@ -79,7 +81,7 @@ function lrclib_lyrics($artist, $title, $timeout) { $lrc = lrclib_lyrics($artist, $title, $timeout); if ($lrc !== null) { - json_out(['found' => true, 'lyrics' => $lrc['lyrics'], 'synced' => $lrc['synced'], 'url' => '']); + json_out(['found' => true, 'lyrics' => $lrc['lyrics'], 'synced' => $lrc['synced'], 'url' => '', 'source' => 'lrclib']); } /* -- dumb (Genius-API-free fallback) ----------------------------------------- */ @@ -89,9 +91,9 @@ if (cfg('dumb.enabled')) { $song = dumb_song($path, $timeout); $url = cfg('dumb.url') . $path; if ($song === null || $song['lyrics'] === '') { - json_out(['found' => false, 'lyrics' => '', 'synced' => [], 'url' => $url]); + json_out(['found' => false, 'lyrics' => '', 'synced' => [], 'url' => $url, 'source' => 'dumb']); } - json_out(['found' => true, 'lyrics' => $song['lyrics'], 'synced' => [], 'url' => $url]); + json_out(['found' => true, 'lyrics' => $song['lyrics'], 'synced' => [], 'url' => $url, 'source' => 'dumb']); } /* -- Genius (fallback) ------------------------------------------------------- */ @@ -131,7 +133,7 @@ if ($pageUrl === '') json_out($empty); /* -- Scrape the lyric containers ------------------------------------------- */ $page = http_get($pageUrl, $timeout); if ($page === null) { - json_out(['found' => false, 'lyrics' => '', 'synced' => [], 'url' => $pageUrl]); + json_out(['found' => false, 'lyrics' => '', 'synced' => [], 'url' => $pageUrl, 'source' => 'genius']); } $marker = 'data-lyrics-container="true"'; @@ -153,7 +155,7 @@ $lyrics = implode("\n", array_map('trim', explode("\n", $lyrics))); $lyrics = preg_replace("/\n{3,}/", "\n\n", $lyrics); if ($lyrics === '') { - json_out(['found' => false, 'lyrics' => '', 'synced' => [], 'url' => $pageUrl]); + json_out(['found' => false, 'lyrics' => '', 'synced' => [], 'url' => $pageUrl, 'source' => 'genius']); } -json_out(['found' => true, 'lyrics' => $lyrics, 'synced' => [], 'url' => $pageUrl]); +json_out(['found' => true, 'lyrics' => $lyrics, 'synced' => [], 'url' => $pageUrl, 'source' => 'genius']); diff --git a/assets/css/radio.css b/assets/css/radio.css index 04d347a..c4e2f6a 100644 --- a/assets/css/radio.css +++ b/assets/css/radio.css @@ -227,6 +227,13 @@ audio[controls] { width: 100%; outline: 0; } .lyrics-line:hover, .lyrics-line:focus-visible { opacity: .85; outline: 0; } .lyrics-line.current { opacity: 1; font-weight: 700; } +.lyrics-source { + display: block; margin-top: 14px; + font-size: 0.75rem; opacity: .6; color: inherit; +} +.lyrics-source:hover, .lyrics-source:focus-visible { opacity: 1; text-decoration: underline; } +.lyrics-source[hidden] { display: none; } + .lyrics-sync { position: absolute; top: 48px; right: 10px; z-index: 2; display: flex; align-items: center; gap: 6px; diff --git a/assets/js/player.js b/assets/js/player.js index b96c968..09eefeb 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -37,6 +37,7 @@ lyricsToggle: $('lyrics-toggle'), lyrics: $('lyrics'), lyricsLines: $('lyrics-lines'), + lyricsSource: $('lyrics-source'), lyricsSync: $('lyrics-sync'), lyricsOffset: $('lyrics-offset'), historyBody: $('history-body'), @@ -719,6 +720,9 @@ if (el.lyrics) el.lyrics.hidden = true; if (el.lyricsLines) el.lyricsLines.textContent = ''; if (el.lyricsSync) el.lyricsSync.hidden = true; + if (el.lyricsSource) el.lyricsSource.hidden = true; + + var SOURCE_LABEL = { genius: 'Genius', dumb: 'dumb' }; var body = new URLSearchParams(); body.set('artist', track.artist || ''); @@ -741,6 +745,13 @@ } else { el.lyricsLines.textContent = res.lyrics; } + // No link for LRCLIB (its url is always ''); Genius/dumb link to the + // page the lyrics were scraped from. + if (el.lyricsSource && res.url && SOURCE_LABEL[res.source]) { + el.lyricsSource.href = res.url; + el.lyricsSource.textContent = 'Lyrics via ' + SOURCE_LABEL[res.source]; + el.lyricsSource.hidden = false; + } el.lyricsToggle.hidden = false; }) .catch(function () {}); diff --git a/index.php b/index.php index 6f9b087..29170a2 100644 --- a/index.php +++ b/index.php @@ -41,6 +41,7 @@ $glassFx = $liquidGlass ? '