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:
@@ -148,6 +148,7 @@ func (a *app) browsePage(w http.ResponseWriter, r *http.Request) {
|
||||
type songDetail struct {
|
||||
songSummary
|
||||
Description string
|
||||
Lyrics string
|
||||
SourceURL *string
|
||||
Reviews []*review // nil when the reveal rule is withholding them
|
||||
ViewerReview *review
|
||||
@@ -161,12 +162,13 @@ func (s *songDetail) Locked() bool { return s.ReviewCount > 0 }
|
||||
|
||||
func (a *app) song(ctx context.Context, viewerID, songID int64) (*songDetail, error) {
|
||||
var d songDetail
|
||||
err := a.pool.QueryRow(ctx, `select`+songColumns+`, coalesce(s.description, ''), s.source_url
|
||||
err := a.pool.QueryRow(ctx, `select`+songColumns+`,
|
||||
coalesce(s.description, ''), coalesce(s.lyrics, ''), s.source_url
|
||||
from songs s join users u on u.id = s.submitted_by
|
||||
where s.id = $2`, viewerID, songID).
|
||||
Scan(&d.ID, &d.Title, &d.Artist, &d.Genre, &d.Duration, &d.CreatedAt,
|
||||
&d.SubmitterID, &d.Submitter, &d.ReviewCount, &d.Average, &d.Own, &d.Reviewed,
|
||||
&d.Description, &d.SourceURL)
|
||||
&d.Description, &d.Lyrics, &d.SourceURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -231,6 +233,31 @@ func (a *app) songPage(w http.ResponseWriter, r *http.Request) {
|
||||
a.render(w, r, http.StatusOK, "song.html", page{Title: d.Title, Data: d})
|
||||
}
|
||||
|
||||
// Lyrics are not covered by the lock: it freezes what the song claims to be, and nobody reviewed
|
||||
// the lyrics. So this checks the submitter and nothing else, which also lets someone paste them for
|
||||
// an old song a year later.
|
||||
func (a *app) editLyrics(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
tag, err := a.pool.Exec(r.Context(),
|
||||
`update songs set lyrics = nullif($3, '') where id = $1 and submitted_by = $2`,
|
||||
id, memberFrom(r.Context()).ID, cleanLyrics(r.FormValue("lyrics")))
|
||||
if err != nil {
|
||||
slog.Error("edit lyrics", "ctx", "songs", "error", err, "song", id)
|
||||
http.Error(w, "virhe", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
a.flash(w, "Sanoitukset tallennettu.")
|
||||
http.Redirect(w, r, fmt.Sprintf("/songs/%d", id), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// --- edit and delete ---
|
||||
|
||||
// The submitter may change the four text fields while the song is unlocked. Once people have
|
||||
|
||||
Reference in New Issue
Block a user