mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
refactor(lyrics): move lyric kind logic into model methods
Turn the lyric-kind helpers into methods on the model types so the domain logic lives next to the data instead of in the subsonic layer: - Lyrics.EffectiveKind() replaces the free LyricKindOrMain(string) function - Lyrics.IsMainKind() centralizes the main-kind predicate - LyricList.Main() replaces the subsonic-local mainKindLyric helper, with the main-kind-then-first fallback for the legacy plain-text getLyrics The subsonic build helpers now call these methods, removing the duplicated inline kind check. Behavior is unchanged. Adds model tests for all three.
This commit is contained in:
parent
965daa492e
commit
6f50c280ad
@ -55,15 +55,6 @@ const (
|
||||
LyricKindPronunciation = "pronunciation"
|
||||
)
|
||||
|
||||
// LyricKindOrMain returns kind, defaulting to LyricKindMain when empty. A blank
|
||||
// kind means an untyped (single-track) lyric, which the contract treats as main.
|
||||
func LyricKindOrMain(kind string) string {
|
||||
if strings.TrimSpace(kind) == "" {
|
||||
return LyricKindMain
|
||||
}
|
||||
return kind
|
||||
}
|
||||
|
||||
// support the standard [mm:ss.mm], as well as [hh:*] and [*.mmm]
|
||||
const timeRegexString = `\[([0-9]{1,2}:)?([0-9]{1,2}):([0-9]{1,2})(\.[0-9]{1,3})?\]`
|
||||
|
||||
@ -82,6 +73,22 @@ 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
|
||||
}
|
||||
|
||||
func ToLyrics(language, text string) (*Lyrics, error) {
|
||||
text = str.SanitizeText(text)
|
||||
|
||||
@ -434,6 +441,22 @@ func parseTime(line string, match []int) (int64, error) {
|
||||
|
||||
type LyricList []Lyrics
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func NormalizeLyrics(lyrics Lyrics) Lyrics {
|
||||
lyrics.Line = NormalizeCueLines(lyrics.Line)
|
||||
if len(lyrics.Agents) == 0 {
|
||||
|
||||
@ -255,3 +255,66 @@ var _ = Describe("NormalizeCueLines", func() {
|
||||
Expect(lines[0].Cue[1].End).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Lyrics.EffectiveKind", func() {
|
||||
It("defaults a blank kind to main", func() {
|
||||
Expect(Lyrics{}.EffectiveKind()).To(Equal(LyricKindMain))
|
||||
Expect(Lyrics{Kind: " "}.EffectiveKind()).To(Equal(LyricKindMain))
|
||||
})
|
||||
|
||||
It("returns the kind as-is when set", func() {
|
||||
Expect(Lyrics{Kind: LyricKindTranslation}.EffectiveKind()).To(Equal(LyricKindTranslation))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Lyrics.IsMainKind", func() {
|
||||
It("is true for a blank (untyped) kind", func() {
|
||||
Expect(Lyrics{}.IsMainKind()).To(BeTrue())
|
||||
})
|
||||
|
||||
It("is true for the main kind", func() {
|
||||
Expect(Lyrics{Kind: LyricKindMain}.IsMainKind()).To(BeTrue())
|
||||
})
|
||||
|
||||
It("is false for translation and pronunciation kinds", func() {
|
||||
Expect(Lyrics{Kind: LyricKindTranslation}.IsMainKind()).To(BeFalse())
|
||||
Expect(Lyrics{Kind: LyricKindPronunciation}.IsMainKind()).To(BeFalse())
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("LyricList.Main", func() {
|
||||
It("returns false when the list is empty", func() {
|
||||
_, ok := LyricList{}.Main()
|
||||
Expect(ok).To(BeFalse())
|
||||
})
|
||||
|
||||
It("returns the main-kind entry when present", func() {
|
||||
list := LyricList{
|
||||
{Kind: LyricKindTranslation, Lang: "en"},
|
||||
{Kind: LyricKindMain, Lang: "xxx"},
|
||||
}
|
||||
main, ok := list.Main()
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(main.Kind).To(Equal(LyricKindMain))
|
||||
})
|
||||
|
||||
It("falls back to the first entry when no main kind exists", func() {
|
||||
list := LyricList{
|
||||
{Kind: LyricKindTranslation, Lang: "en"},
|
||||
{Kind: LyricKindPronunciation, Lang: "ja"},
|
||||
}
|
||||
main, ok := list.Main()
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(main.Lang).To(Equal("en"))
|
||||
})
|
||||
|
||||
It("treats a blank kind as main", func() {
|
||||
list := LyricList{
|
||||
{Kind: LyricKindTranslation, Lang: "en"},
|
||||
{Lang: "xxx"},
|
||||
}
|
||||
main, ok := list.Main()
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(main.Lang).To(Equal("xxx"))
|
||||
})
|
||||
})
|
||||
|
||||
@ -19,7 +19,7 @@ func buildLyricsList(mf *model.MediaFile, lyricsList model.LyricList, enhanced b
|
||||
// Without enhanced, only return main-kind entries (a blank kind is main).
|
||||
filtered = nil
|
||||
for _, l := range lyricsList {
|
||||
if model.LyricKindOrMain(l.Kind) == model.LyricKindMain {
|
||||
if l.IsMainKind() {
|
||||
filtered = append(filtered, l)
|
||||
}
|
||||
}
|
||||
@ -32,21 +32,6 @@ func buildLyricsList(mf *model.MediaFile, lyricsList model.LyricList, enhanced b
|
||||
return &responses.LyricsList{StructuredLyrics: lyricList}
|
||||
}
|
||||
|
||||
// mainKindLyric returns the main-kind lyric to surface through the plain-text
|
||||
// legacy getLyrics endpoint, which has no notion of translation/pronunciation
|
||||
// tracks. It falls back to the first entry so untyped lyrics still resolve.
|
||||
func mainKindLyric(lyricsList model.LyricList) (model.Lyrics, bool) {
|
||||
if len(lyricsList) == 0 {
|
||||
return model.Lyrics{}, false
|
||||
}
|
||||
for _, l := range lyricsList {
|
||||
if model.LyricKindOrMain(l.Kind) == model.LyricKindMain {
|
||||
return l, true
|
||||
}
|
||||
}
|
||||
return lyricsList[0], true
|
||||
}
|
||||
|
||||
func buildStructuredLyric(mf *model.MediaFile, lyrics model.Lyrics, enhanced bool) responses.StructuredLyric {
|
||||
agents := newLyricAgents(lyrics.Agents)
|
||||
|
||||
@ -70,7 +55,7 @@ func buildStructuredLyric(mf *model.MediaFile, lyrics model.Lyrics, enhanced boo
|
||||
}
|
||||
|
||||
if enhanced {
|
||||
structured.Kind = model.LyricKindOrMain(lyrics.Kind)
|
||||
structured.Kind = lyrics.EffectiveKind()
|
||||
if len(cueLines) > 0 && len(agents.response) > 0 {
|
||||
structured.Agents = agents.response
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ func (api *Router) GetLyrics(r *http.Request) (*responses.Subsonic, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mainLyric, ok := mainKindLyric(structuredLyrics)
|
||||
mainLyric, ok := structuredLyrics.Main()
|
||||
if !ok {
|
||||
return response, nil
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user