Add lyrics: paste, fetch, and read them while reviewing
Lyrics are suggested at submission and never imposed. The conversion worker makes one LRCLIB lookup with whatever metadata exists, and the waiting page has a Hae sanoitukset button that re-queries with whatever title and artist are currently typed — which is the case that matters, since our metadata comes from ID3 tags and YouTube uploaders. Neither path overwrites typed text. - lyrics text on both submissions and songs, copied across at publish. Nothing has launched, so the column goes into 001_init.sql rather than a migration - The lock does not cover lyrics: it freezes what the song claims to be, and nobody reviewed the lyrics. So the submitter can still fix them afterwards, or paste them for an old song a year later - The review strip gained a second pane: lyrics on the left, review on the right, so following the words costs no scrolling. No lyrics means no pane, not an empty one. Below 1024px the panes stack - LRC timestamps are stored but stripped for reading — they belong to the player, not the reader - The lyrics box is capped and scrolls inside itself, so a long song cannot stretch the strip past the screen Fixes a real bug found on the way: saveMetadata cleared any field the request did not carry, so publishing wiped the lyrics the worker had just fetched. Fields absent from a request now keep their stored value. The client identifies itself to LRCLIB as "levyraati" and nothing more. Tests cover cleanLyrics keeping line breaks, and fetchLyrics against a local server: synced beats plain, instrumentals and wrong-length takes are skipped, and a miss is empty with no error.
This commit is contained in:
@@ -96,6 +96,29 @@ func clean(s string, max int) string {
|
||||
return s
|
||||
}
|
||||
|
||||
const maxLyrics = 20000
|
||||
|
||||
// Lyrics are the one field where line breaks carry meaning — LRC timestamps are per line — so they
|
||||
// survive, and only the other control characters go.
|
||||
func cleanLyrics(s string) string {
|
||||
s = strings.ReplaceAll(s, "\r\n", "\n")
|
||||
s = strings.ReplaceAll(s, "\r", "\n")
|
||||
s = strings.Map(func(r rune) rune {
|
||||
if r == '\n' || r == '\t' {
|
||||
return r
|
||||
}
|
||||
if r < 0x20 || r == 0x7f {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, s)
|
||||
s = strings.TrimSpace(s)
|
||||
if r := []rune(s); len(r) > maxLyrics {
|
||||
s = strings.TrimSpace(string(r[:maxLyrics]))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Hosts yt-dlp is allowed to see. Validated before the URL goes anywhere near a subprocess
|
||||
// argument list — and it never goes through a shell.
|
||||
var allowedHosts = map[string]bool{
|
||||
|
||||
Reference in New Issue
Block a user