mirror of
https://github.com/navidrome/navidrome.git
synced 2026-11-08 11:18:41 +01:00
Move the cue-normalization machinery out of lyrics.go into a dedicated lyrics_normalize.go (with lyrics_normalize_test.go), leaving lyrics.go to hold just the shared lyric types and their methods. lyrics.go was mixing the domain type/contract definitions with format-agnostic post-processing. Unexport normalizeLyrics, normalizeCueLines, and normalizeLineTiming: they have no callers outside the model package, so they should not be part of its public API. NormalizeCueEnds stays exported because the Subsonic enhanced-lyrics serializer (server/subsonic/lyrics.go) resolves cue ends per agent group while building the response; that is the only legitimate cross-package caller. Also includes a small no-op robustness tweak in parseLRC: len(times) == 0 instead of times == nil (equivalent here, more idiomatic). No behavior change.
93 lines
3.1 KiB
Go
93 lines
3.1 KiB
Go
package model
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
)
|
|
|
|
type Cue struct {
|
|
Start *int64 `structs:"start,omitempty" json:"start,omitempty"`
|
|
End *int64 `structs:"end,omitempty" json:"end,omitempty"`
|
|
Value string `structs:"value" json:"value"`
|
|
ByteStart int `structs:"byteStart" json:"byteStart"`
|
|
ByteEnd int `structs:"byteEnd" json:"byteEnd"`
|
|
AgentID string `structs:"agentId,omitempty" json:"agentId,omitempty"`
|
|
}
|
|
|
|
type Agent struct {
|
|
ID string `structs:"id" json:"id"`
|
|
Role string `structs:"role" json:"role"`
|
|
Name string `structs:"name,omitempty" json:"name,omitempty"`
|
|
}
|
|
|
|
type Line struct {
|
|
Start *int64 `structs:"start,omitempty" json:"start,omitempty"`
|
|
End *int64 `structs:"end,omitempty" json:"end,omitempty"`
|
|
Value string `structs:"value" json:"value"`
|
|
Cue []Cue `structs:"cue,omitempty" json:"cue,omitempty"`
|
|
}
|
|
|
|
type Lyrics struct {
|
|
DisplayArtist string `structs:"displayArtist,omitempty" json:"displayArtist,omitempty"`
|
|
DisplayTitle string `structs:"displayTitle,omitempty" json:"displayTitle,omitempty"`
|
|
Kind string `structs:"kind,omitempty" json:"kind,omitempty"`
|
|
Lang string `structs:"lang" json:"lang"`
|
|
Agents []Agent `structs:"agents,omitempty" json:"agents,omitempty"`
|
|
Line []Line `structs:"line" json:"line"`
|
|
Offset *int64 `structs:"offset,omitempty" json:"offset,omitempty"`
|
|
Synced bool `structs:"synced" json:"synced"`
|
|
}
|
|
|
|
// Lyric kinds, as defined by the OpenSubsonic songLyrics v2 contract. These are
|
|
// the canonical wire values; keep them in sync with the spec.
|
|
const (
|
|
LyricKindMain = "main"
|
|
LyricKindTranslation = "translation"
|
|
LyricKindPronunciation = "pronunciation"
|
|
)
|
|
|
|
func (l Lyrics) IsEmpty() bool {
|
|
return len(l.Line) == 0
|
|
}
|
|
|
|
// IsMainKind reports whether the lyric is the main track. A blank kind is an
|
|
// untyped (single-track) lyric, which the contract treats as main.
|
|
func (l Lyrics) IsMainKind() bool {
|
|
return l.EffectiveKind() == LyricKindMain
|
|
}
|
|
|
|
// EffectiveKind returns the lyric kind, defaulting to LyricKindMain when blank.
|
|
// A blank kind means an untyped (single-track) lyric, which the contract treats
|
|
// as main.
|
|
func (l Lyrics) EffectiveKind() string {
|
|
if strings.TrimSpace(l.Kind) == "" {
|
|
return LyricKindMain
|
|
}
|
|
return l.Kind
|
|
}
|
|
|
|
type LyricList []Lyrics
|
|
|
|
// MarshalJSON keeps the lyrics column invariant: empty/nil serializes to [], never null.
|
|
func (ll LyricList) MarshalJSON() ([]byte, error) {
|
|
if len(ll) == 0 {
|
|
return []byte("[]"), nil
|
|
}
|
|
return json.Marshal([]Lyrics(ll))
|
|
}
|
|
|
|
// Main returns the main-kind lyric, falling back to the first entry so untyped
|
|
// lyrics still resolve. The bool is false only when the list is empty. It is
|
|
// used to surface a single lyric through the plain-text legacy getLyrics
|
|
// endpoint, which has no notion of translation/pronunciation tracks.
|
|
func (ll LyricList) Main() (Lyrics, bool) {
|
|
if len(ll) == 0 {
|
|
return Lyrics{}, false
|
|
}
|
|
for _, l := range ll {
|
|
if l.IsMainKind() {
|
|
return l, true
|
|
}
|
|
}
|
|
return ll[0], true
|
|
}
|