From 917f16a96d2d0c6fc9ac57306966cca6325f73c1 Mon Sep 17 00:00:00 2001 From: Deluan Date: Thu, 18 Jun 2026 19:18:27 -0400 Subject: [PATCH] refactor(lyrics): move pure format parsers from core/lyrics to model Relocates the TTML, SRT, and embedded-dispatch parsers (pure text -> model.LyricList transforms) into package model, alongside the existing ToLyrics (LRC/ELRC) and ParseLyricsfile parsers. This removes the model/metadata -> core/lyrics import edge (a lower layer depending on business logic), which was the only reason model imported core. core/lyrics now contains only the source-priority + plugin service, which is consumed solely by server/subsonic. parseTTML/parseSRT are exported as ParseTTML/ParseSRT so the remaining core/lyrics service can still call them across the package boundary. --- core/lyrics/sources.go | 4 +- .../embedded.go => model/lyrics_embedded.go | 9 ++- .../lyrics_embedded_test.go | 11 ++- core/lyrics/srt.go => model/lyrics_srt.go | 31 ++++---- model/lyrics_test_helpers_test.go | 5 ++ core/lyrics/ttml.go => model/lyrics_ttml.go | 71 +++++++++---------- .../ttml_test.go => model/lyrics_ttml_test.go | 63 ++++++++-------- model/metadata/map_mediafile.go | 3 +- 8 files changed, 98 insertions(+), 99 deletions(-) rename core/lyrics/embedded.go => model/lyrics_embedded.go (87%) rename core/lyrics/embedded_test.go => model/lyrics_embedded_test.go (95%) rename core/lyrics/srt.go => model/lyrics_srt.go (80%) create mode 100644 model/lyrics_test_helpers_test.go rename core/lyrics/ttml.go => model/lyrics_ttml.go (94%) rename core/lyrics/ttml_test.go => model/lyrics_ttml_test.go (86%) diff --git a/core/lyrics/sources.go b/core/lyrics/sources.go index aa9a6c765..18e826cc1 100644 --- a/core/lyrics/sources.go +++ b/core/lyrics/sources.go @@ -40,13 +40,13 @@ func fromExternalFile(ctx context.Context, mf *model.MediaFile, suffix string) ( var list model.LyricList switch { case strings.EqualFold(suffix, ".ttml"): - list, err = parseTTML(contents) + list, err = model.ParseTTML(contents) if err != nil { log.Error(ctx, "error parsing ttml external file", "path", externalLyric, err) return nil, err } case strings.EqualFold(suffix, ".srt"): - list, err = parseSRT(contents) + list, err = model.ParseSRT(contents) if err != nil { log.Error(ctx, "error parsing srt external file", "path", externalLyric, err) return nil, err diff --git a/core/lyrics/embedded.go b/model/lyrics_embedded.go similarity index 87% rename from core/lyrics/embedded.go rename to model/lyrics_embedded.go index 4cdd898ee..b9af377e1 100644 --- a/core/lyrics/embedded.go +++ b/model/lyrics_embedded.go @@ -1,17 +1,16 @@ -package lyrics +package model import ( "encoding/xml" "strings" "github.com/navidrome/navidrome/log" - "github.com/navidrome/navidrome/model" ) // ParseEmbedded parses lyrics read from media-file metadata tags. It detects rich // payloads before falling back to the generic LRC/plain-text parser, because // text sanitization would otherwise strip TTML XML markup. -func ParseEmbedded(language, text string) (model.LyricList, error) { +func ParseEmbedded(language, text string) (LyricList, error) { text = strings.TrimPrefix(text, "\ufeff") if isTTMLDocument(text) { @@ -32,14 +31,14 @@ func ParseEmbedded(language, text string) (model.LyricList, error) { log.Warn("Error parsing embedded SRT lyrics, falling back to plain lyrics", "error", err) } - lyric, err := model.ToLyrics(language, text) + lyric, err := ToLyrics(language, text) if err != nil { return nil, err } if lyric == nil || lyric.IsEmpty() { return nil, nil } - return model.LyricList{*lyric}, nil + return LyricList{*lyric}, nil } func isTTMLDocument(text string) bool { diff --git a/core/lyrics/embedded_test.go b/model/lyrics_embedded_test.go similarity index 95% rename from core/lyrics/embedded_test.go rename to model/lyrics_embedded_test.go index d875bbdcf..d0dc41c53 100644 --- a/core/lyrics/embedded_test.go +++ b/model/lyrics_embedded_test.go @@ -1,9 +1,8 @@ -package lyrics +package model import ( "strings" - "github.com/navidrome/navidrome/model" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -34,7 +33,7 @@ var _ = Describe("ParseEmbedded", func() { Expect(list[0].Kind).To(Equal("main")) Expect(list[0].Lang).To(Equal("eng")) Expect(list[0].Synced).To(BeTrue()) - Expect(list[0].Agents).To(Equal([]model.Agent{{ID: "lead", Role: "main", Name: "Lead Vocal"}})) + Expect(list[0].Agents).To(Equal([]Agent{{ID: "lead", Role: "main", Name: "Lead Vocal"}})) Expect(list[0].Line).To(HaveLen(1)) Expect(list[0].Line[0].Start).To(Equal(ptr(int64(1000)))) Expect(list[0].Line[0].End).To(Equal(ptr(int64(3000)))) @@ -100,10 +99,10 @@ Another subtitle line` list, err := ParseEmbedded("POR", content) Expect(err).ToNot(HaveOccurred()) - Expect(list).To(Equal(model.LyricList{ + Expect(list).To(Equal(LyricList{ { Lang: "por", - Line: []model.Line{ + Line: []Line{ { Start: ptr(int64(18800)), End: ptr(int64(22800)), @@ -127,7 +126,7 @@ Another subtitle line` Expect(err).ToNot(HaveOccurred()) Expect(list).To(HaveLen(1)) - Expect(list[0].Line).To(Equal([]model.Line{ + Expect(list[0].Line).To(Equal([]Line{ {Start: ptr(int64(1000)), End: ptr(int64(2000)), Value: "First subtitle"}, {Start: ptr(int64(3000)), End: ptr(int64(4000)), Value: "Second subtitle"}, })) diff --git a/core/lyrics/srt.go b/model/lyrics_srt.go similarity index 80% rename from core/lyrics/srt.go rename to model/lyrics_srt.go index 05a43ca57..db6530ada 100644 --- a/core/lyrics/srt.go +++ b/model/lyrics_srt.go @@ -1,4 +1,4 @@ -package lyrics +package model import ( "bytes" @@ -6,7 +6,6 @@ import ( "strconv" "strings" - "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/utils/str" ) @@ -15,16 +14,16 @@ var ( srtBlockSeparatorRegex = regexp.MustCompile(`\n\s*\n`) ) -func parseSRT(contents []byte) (model.LyricList, error) { +func ParseSRT(contents []byte) (LyricList, error) { return parseSRTWithLanguage(contents, "xxx") } -func parseSRTWithLanguage(contents []byte, language string) (model.LyricList, error) { +func parseSRTWithLanguage(contents []byte, language string) (LyricList, error) { raw := strings.ReplaceAll(string(contents), "\r\n", "\n") raw = strings.ReplaceAll(raw, "\r", "\n") blocks := splitSRTBlocks(raw) - lines := make([]model.Line, 0, len(blocks)) + lines := make([]Line, 0, len(blocks)) for _, block := range blocks { line, ok, err := parseSRTBlock(block) @@ -40,12 +39,12 @@ func parseSRTWithLanguage(contents []byte, language string) (model.LyricList, er return nil, nil } - lyrics := model.NormalizeLyrics(model.Lyrics{ + lyrics := NormalizeLyrics(Lyrics{ Lang: normalizeEmbeddedLanguage(language), Line: lines, Synced: true, }) - return model.LyricList{lyrics}, nil + return LyricList{lyrics}, nil } func splitSRTBlocks(raw string) []string { @@ -65,10 +64,10 @@ func splitSRTBlocks(raw string) []string { return blocks } -func parseSRTBlock(block string) (model.Line, bool, error) { +func parseSRTBlock(block string) (Line, bool, error) { scanner := bytes.Split([]byte(block), []byte("\n")) if len(scanner) == 0 { - return model.Line{}, false, nil + return Line{}, false, nil } lines := make([]string, 0, len(scanner)) @@ -77,7 +76,7 @@ func parseSRTBlock(block string) (model.Line, bool, error) { } if len(lines) == 0 { - return model.Line{}, false, nil + return Line{}, false, nil } startIdx := 0 @@ -85,21 +84,21 @@ func parseSRTBlock(block string) (model.Line, bool, error) { startIdx = 1 } if startIdx >= len(lines) { - return model.Line{}, false, nil + return Line{}, false, nil } timing := strings.Split(lines[startIdx], "-->") if len(timing) != 2 { - return model.Line{}, false, nil + return Line{}, false, nil } startMs, err := parseSRTTime(timing[0]) if err != nil { - return model.Line{}, false, err + return Line{}, false, err } endMs, err := parseSRTTime(timing[1]) if err != nil { - return model.Line{}, false, err + return Line{}, false, err } textLines := make([]string, 0, len(lines)-startIdx-1) @@ -112,10 +111,10 @@ func parseSRTBlock(block string) (model.Line, bool, error) { value := str.SanitizeText(strings.Join(textLines, "\n")) if value == "" { - return model.Line{}, false, nil + return Line{}, false, nil } - return model.Line{ + return Line{ Start: &startMs, End: &endMs, Value: value, diff --git a/model/lyrics_test_helpers_test.go b/model/lyrics_test_helpers_test.go new file mode 100644 index 000000000..934a5aeeb --- /dev/null +++ b/model/lyrics_test_helpers_test.go @@ -0,0 +1,5 @@ +package model + +func ptr[T any](v T) *T { + return &v +} diff --git a/core/lyrics/ttml.go b/model/lyrics_ttml.go similarity index 94% rename from core/lyrics/ttml.go rename to model/lyrics_ttml.go index 163bd657c..6419b3537 100644 --- a/core/lyrics/ttml.go +++ b/model/lyrics_ttml.go @@ -1,4 +1,4 @@ -package lyrics +package model import ( "bytes" @@ -13,7 +13,6 @@ import ( "unicode" "github.com/navidrome/navidrome/log" - "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/utils/str" ) @@ -58,19 +57,19 @@ type ttmlTimingContext struct { type ttmlLineRef struct { order int - line model.Line + line Line } type ttmlMetadataEntry struct { key string - line model.Line + line Line seq int } type ttmlResolvedMetadataLine struct { order int seq int - line model.Line + line Line } type ttmlDefinedAgent struct { @@ -81,7 +80,7 @@ type ttmlDefinedAgent struct { type ttmlPiece struct { raw string - cue *model.Cue + cue *Cue } type ttmlParser struct { @@ -89,7 +88,7 @@ type ttmlParser struct { params ttmlTimingParams mainLangOrder []string - mainLinesByLang map[string][]model.Line + mainLinesByLang map[string][]Line mainLineRefsByKey map[string]ttmlLineRef mainLineOrder int @@ -105,11 +104,11 @@ type ttmlParser struct { metadataSeq int } -func parseTTML(contents []byte) (model.LyricList, error) { +func ParseTTML(contents []byte) (LyricList, error) { return parseTTMLWithDefaultLang(contents, "xxx") } -func parseTTMLWithDefaultLang(contents []byte, defaultLang string) (model.LyricList, error) { +func parseTTMLWithDefaultLang(contents []byte, defaultLang string) (LyricList, error) { contents = xmlEncodingRegex.ReplaceAll(contents, []byte(``)) p := ttmlParser{ @@ -119,7 +118,7 @@ func parseTTMLWithDefaultLang(contents []byte, defaultLang string) (model.LyricL subFrameRate: defaultTTMLSubFrameRate, tickRate: defaultTTMLTickRate, }, - mainLinesByLang: make(map[string][]model.Line), + mainLinesByLang: make(map[string][]Line), mainLineRefsByKey: make(map[string]ttmlLineRef), translationEntriesByLg: make(map[string][]ttmlMetadataEntry), pronunciationEntriesByLg: make(map[string][]ttmlMetadataEntry), @@ -175,7 +174,7 @@ func (p *ttmlParser) parseElement(start xml.StartElement, parent ttmlTimingConte return nil } - parsedLine := model.Line{Value: lineText} + parsedLine := Line{Value: lineText} if ctx.hasBegin { startMs := ctx.begin parsedLine.Start = &startMs @@ -318,7 +317,7 @@ func (p *ttmlParser) parseMetadataText(start xml.StartElement, parent ttmlTiming } value, tokens := buildTTMLLineFromPieces(pieces) - line := model.Line{Value: value} + line := Line{Value: value} if ctx.hasBegin { startMs := ctx.begin line.Start = &startMs @@ -339,7 +338,7 @@ func (p *ttmlParser) parseMetadataText(start xml.StartElement, parent ttmlTiming return ttmlMetadataEntry{key: forKey, line: line}, true, nil } -func (p *ttmlParser) parseParagraph(parent ttmlTimingContext) (string, []model.Cue, error) { +func (p *ttmlParser) parseParagraph(parent ttmlTimingContext) (string, []Cue, error) { var pieces []ttmlPiece for { @@ -402,7 +401,7 @@ func (p *ttmlParser) parseInlineElement(start xml.StartElement, parent ttmlTimin rawValue := concatTTMLPieceRaw(pieces) tokenText := sanitizeTTMLText(rawValue) if tokenText != "" { - parsedToken := model.Cue{ + parsedToken := Cue{ AgentID: p.resolveCueAgentID(ctx), } if ctx.hasBegin { @@ -428,7 +427,7 @@ func (p *ttmlParser) parseInlineElement(start xml.StartElement, parent ttmlTimin } } -func buildTTMLLineFromPieces(pieces []ttmlPiece) (string, []model.Cue) { +func buildTTMLLineFromPieces(pieces []ttmlPiece) (string, []Cue) { finalized := finalizeTTMLLines(splitTTMLPiecesByNewline(pieces)) for len(finalized) > 0 && finalized[0].text == "" && len(finalized[0].cues) == 0 { finalized = finalized[1:] @@ -442,7 +441,7 @@ func buildTTMLLineFromPieces(pieces []ttmlPiece) (string, []model.Cue) { } var value strings.Builder - cues := make([]model.Cue, 0, 8) + cues := make([]Cue, 0, 8) byteOffset := 0 for i, line := range finalized { if i > 0 { @@ -463,7 +462,7 @@ func buildTTMLLineFromPieces(pieces []ttmlPiece) (string, []model.Cue) { type ttmlFinalLine struct { text string - cues []model.Cue + cues []Cue } func finalizeTTMLLines(lines [][]ttmlPiece) []ttmlFinalLine { @@ -507,7 +506,7 @@ func splitTTMLPiecesByNewline(pieces []ttmlPiece) [][]ttmlPiece { return lines } -func finalizeTTMLLogicalLine(line []ttmlPiece) (string, []model.Cue) { +func finalizeTTMLLogicalLine(line []ttmlPiece) (string, []Cue) { rawLine := concatTTMLPieceRaw(line) if rawLine == "" { return "", nil @@ -521,7 +520,7 @@ func finalizeTTMLLogicalLine(line []ttmlPiece) (string, []model.Cue) { } trimmed := strings.TrimSpace(rawLine) - cues := make([]model.Cue, 0, len(line)) + cues := make([]Cue, 0, len(line)) cursor := 0 for _, piece := range line { pieceEnd := cursor + len(piece.raw) @@ -566,7 +565,7 @@ func ttmlPiecesContainCue(pieces []ttmlPiece) bool { return false } -func cloneTTMLCue(cue *model.Cue) *model.Cue { +func cloneTTMLCue(cue *Cue) *Cue { if cue == nil { return nil } @@ -575,14 +574,14 @@ func cloneTTMLCue(cue *model.Cue) *model.Cue { return &cloned } -func (p *ttmlParser) toLyricList() model.LyricList { - res := make(model.LyricList, 0, len(p.mainLangOrder)+len(p.translationLangOrder)+len(p.pronunciationLangOrder)) +func (p *ttmlParser) toLyricList() LyricList { + res := make(LyricList, 0, len(p.mainLangOrder)+len(p.translationLangOrder)+len(p.pronunciationLangOrder)) for _, lang := range p.mainLangOrder { lines := p.mainLinesByLang[lang] if len(lines) == 0 { continue } - res = append(res, p.finalizeLyrics(model.Lyrics{ + res = append(res, p.finalizeLyrics(Lyrics{ Kind: ttmlLyricKindMain, Lang: lang, Line: lines, @@ -595,8 +594,8 @@ func (p *ttmlParser) toLyricList() model.LyricList { return res } -func (p *ttmlParser) buildMetadataLyrics(kind string, langOrder []string, entriesByLang map[string][]ttmlMetadataEntry) model.LyricList { - res := make(model.LyricList, 0, len(langOrder)) +func (p *ttmlParser) buildMetadataLyrics(kind string, langOrder []string, entriesByLang map[string][]ttmlMetadataEntry) LyricList { + res := make(LyricList, 0, len(langOrder)) for _, lang := range langOrder { entries := entriesByLang[lang] @@ -651,12 +650,12 @@ func (p *ttmlParser) buildMetadataLyrics(kind string, langOrder []string, entrie return resolved[i].seq < resolved[j].seq }) - lines := make([]model.Line, len(resolved)) + lines := make([]Line, len(resolved)) for i := range resolved { lines[i] = resolved[i].line } - res = append(res, p.finalizeLyrics(model.Lyrics{ + res = append(res, p.finalizeLyrics(Lyrics{ Kind: kind, Lang: lang, Line: lines, @@ -667,12 +666,12 @@ func (p *ttmlParser) buildMetadataLyrics(kind string, langOrder []string, entrie return res } -func (p *ttmlParser) finalizeLyrics(lyrics model.Lyrics) model.Lyrics { +func (p *ttmlParser) finalizeLyrics(lyrics Lyrics) Lyrics { lyrics.Line, lyrics.Agents = p.resolveAgents(lyrics.Line) - return model.NormalizeLyrics(lyrics) + return NormalizeLyrics(lyrics) } -func (p *ttmlParser) resolveAgents(lines []model.Line) ([]model.Line, []model.Agent) { +func (p *ttmlParser) resolveAgents(lines []Line) ([]Line, []Agent) { if len(lines) == 0 { return lines, nil } @@ -735,13 +734,13 @@ func (p *ttmlParser) resolveAgents(lines []model.Line) ([]model.Line, []model.Ag } } - agents := make([]model.Agent, 0, len(usedOrder)) + agents := make([]Agent, 0, len(usedOrder)) for _, agentID := range usedOrder { role := p.baseRoleForAgent(agentID) if agentID == mainID { role = "main" } - agent := model.Agent{ + agent := Agent{ ID: agentID, Role: role, Name: p.agentNameForID(agentID), @@ -830,7 +829,7 @@ func appendTTMLRoles(existing string, roles string) string { return existing } -func (p *ttmlParser) addMainLine(lang string, lineKey string, line model.Line) { +func (p *ttmlParser) addMainLine(lang string, lineKey string, line Line) { lang = normalizeTTMLLang(lang) if _, ok := p.mainLinesByLang[lang]; !ok { p.mainLangOrder = append(p.mainLangOrder, lang) @@ -1252,7 +1251,7 @@ func sanitizeTTMLText(raw string) string { return strings.TrimSpace(strings.Join(lines, "\n")) } -func linesAreSynced(lines []model.Line) bool { +func linesAreSynced(lines []Line) bool { for i := range lines { if lines[i].Start != nil { return true @@ -1266,8 +1265,8 @@ func linesAreSynced(lines []model.Line) bool { return false } -func hydrateLineTimingFromTokens(line model.Line) model.Line { - return model.NormalizeLineTiming(line) +func hydrateLineTimingFromTokens(line Line) Line { + return NormalizeLineTiming(line) } func positiveOrDefault(v float64, fallback float64) float64 { diff --git a/core/lyrics/ttml_test.go b/model/lyrics_ttml_test.go similarity index 86% rename from core/lyrics/ttml_test.go rename to model/lyrics_ttml_test.go index 07f41a080..92e500423 100644 --- a/core/lyrics/ttml_test.go +++ b/model/lyrics_ttml_test.go @@ -1,12 +1,11 @@ -package lyrics +package model import ( - "github.com/navidrome/navidrome/model" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) -var _ = Describe("parseTTML", func() { +var _ = Describe("ParseTTML", func() { Describe("Multi-language and timing", func() { It("should parse multiple language divs with inherited offsets and frame/tick timing", func() { content := []byte(` @@ -22,7 +21,7 @@ var _ = Describe("parseTTML", func() { `) - list, err := parseTTML(content) + list, err := ParseTTML(content) Expect(err).ToNot(HaveOccurred()) Expect(list).To(HaveLen(2)) @@ -55,7 +54,7 @@ var _ = Describe("parseTTML", func() { `) - list, err := parseTTML(content) + list, err := ParseTTML(content) Expect(err).ToNot(HaveOccurred()) Expect(list).To(HaveLen(1)) Expect(list[0].Line).To(HaveLen(1)) @@ -76,7 +75,7 @@ var _ = Describe("parseTTML", func() { `) - list, err := parseTTML(content) + list, err := ParseTTML(content) Expect(err).ToNot(HaveOccurred()) Expect(list).To(HaveLen(1)) Expect(list[0].Lang).To(Equal("eng")) @@ -100,7 +99,7 @@ var _ = Describe("parseTTML", func() { `) - list, err := parseTTML(content) + list, err := ParseTTML(content) Expect(err).ToNot(HaveOccurred()) Expect(list).To(HaveLen(1)) Expect(list[0].Line).To(HaveLen(2)) @@ -125,10 +124,10 @@ var _ = Describe("parseTTML", func() { `) - list, err := parseTTML(content) + list, err := ParseTTML(content) Expect(err).ToNot(HaveOccurred()) Expect(list).To(HaveLen(1)) - Expect(list[0].Agents).To(Equal([]model.Agent{ + Expect(list[0].Agents).To(Equal([]Agent{ {ID: "main", Role: "main"}, {ID: "__nd_bg__|main", Role: "bg"}, })) @@ -140,9 +139,9 @@ var _ = Describe("parseTTML", func() { Expect(line.End).To(Equal(ptr(int64(3000)))) Expect(line.Cue).To(HaveLen(3)) - Expect(line.Cue[0]).To(Equal(model.Cue{Start: ptr(int64(1000)), End: ptr(int64(1400)), Value: "He", ByteStart: 0, ByteEnd: 1, AgentID: "main"})) - Expect(line.Cue[1]).To(Equal(model.Cue{Start: ptr(int64(1400)), End: ptr(int64(1800)), Value: "llo", ByteStart: 2, ByteEnd: 4, AgentID: "main"})) - Expect(line.Cue[2]).To(Equal(model.Cue{Start: ptr(int64(2000)), End: ptr(int64(2500)), Value: "echo", ByteStart: 6, ByteEnd: 9, AgentID: "__nd_bg__|main"})) + Expect(line.Cue[0]).To(Equal(Cue{Start: ptr(int64(1000)), End: ptr(int64(1400)), Value: "He", ByteStart: 0, ByteEnd: 1, AgentID: "main"})) + Expect(line.Cue[1]).To(Equal(Cue{Start: ptr(int64(1400)), End: ptr(int64(1800)), Value: "llo", ByteStart: 2, ByteEnd: 4, AgentID: "main"})) + Expect(line.Cue[2]).To(Equal(Cue{Start: ptr(int64(2000)), End: ptr(int64(2500)), Value: "echo", ByteStart: 6, ByteEnd: 9, AgentID: "__nd_bg__|main"})) }) It("should append role tokens exactly instead of using substring matches", func() { @@ -155,11 +154,11 @@ var _ = Describe("parseTTML", func() { `) - list, err := parseTTML(content) + list, err := ParseTTML(content) Expect(err).ToNot(HaveOccurred()) Expect(list).To(HaveLen(1)) - Expect(list[0].Agents).To(Equal([]model.Agent{ + Expect(list[0].Agents).To(Equal([]Agent{ {ID: "main", Role: "main"}, {ID: "__nd_bg__|main", Role: "bg"}, })) @@ -188,10 +187,10 @@ var _ = Describe("parseTTML", func() { `) - list, err := parseTTML(content) + list, err := ParseTTML(content) Expect(err).ToNot(HaveOccurred()) Expect(list).To(HaveLen(1)) - Expect(list[0].Agents).To(Equal([]model.Agent{ + Expect(list[0].Agents).To(Equal([]Agent{ {ID: "v1", Role: "main", Name: "Chris Martin"}, {ID: "v2", Role: "voice", Name: "Jin"}, {ID: "v1000", Role: "group", Name: "All"}, @@ -223,10 +222,10 @@ var _ = Describe("parseTTML", func() { `) - list, err := parseTTML(content) + list, err := ParseTTML(content) Expect(err).ToNot(HaveOccurred()) Expect(list).To(HaveLen(1)) - Expect(list[0].Agents).To(Equal([]model.Agent{ + Expect(list[0].Agents).To(Equal([]Agent{ {ID: "lead", Role: "main", Name: "Lead"}, {ID: "__nd_bg__|lead", Role: "bg", Name: "Lead"}, {ID: "lead__bg", Role: "voice", Name: "Existing Background Id"}, @@ -257,10 +256,10 @@ var _ = Describe("parseTTML", func() { `) - list, err := parseTTML(content) + list, err := ParseTTML(content) Expect(err).ToNot(HaveOccurred()) Expect(list).To(HaveLen(1)) - Expect(list[0].Agents).To(Equal([]model.Agent{ + Expect(list[0].Agents).To(Equal([]Agent{ {ID: "guest", Role: "main", Name: "Guest Vocal"}, })) Expect(list[0].Line).To(HaveLen(1)) @@ -284,7 +283,7 @@ var _ = Describe("parseTTML", func() { `) - list, err := parseTTML(content) + list, err := ParseTTML(content) Expect(err).ToNot(HaveOccurred()) Expect(list).To(HaveLen(1)) Expect(list[0].Line).To(HaveLen(1)) @@ -294,8 +293,8 @@ var _ = Describe("parseTTML", func() { Expect(line.Value).To(Equal("go\ngo")) Expect(line.End).To(Equal(ptr(int64(45570)))) Expect(line.Cue).To(HaveLen(2)) - Expect(line.Cue[0]).To(Equal(model.Cue{Start: ptr(int64(43444)), End: ptr(int64(43716)), Value: "go", ByteStart: 0, ByteEnd: 1})) - Expect(line.Cue[1]).To(Equal(model.Cue{Start: ptr(int64(43716)), End: ptr(int64(43887)), Value: "go", ByteStart: 3, ByteEnd: 4})) + Expect(line.Cue[0]).To(Equal(Cue{Start: ptr(int64(43444)), End: ptr(int64(43716)), Value: "go", ByteStart: 0, ByteEnd: 1})) + Expect(line.Cue[1]).To(Equal(Cue{Start: ptr(int64(43716)), End: ptr(int64(43887)), Value: "go", ByteStart: 3, ByteEnd: 4})) }) }) @@ -310,7 +309,7 @@ var _ = Describe("parseTTML", func() { `) - list, err := parseTTML(content) + list, err := ParseTTML(content) Expect(err).ToNot(HaveOccurred()) Expect(list).To(HaveLen(1)) Expect(list[0].Lang).To(Equal("xxx")) @@ -350,7 +349,7 @@ var _ = Describe("parseTTML", func() { `) - list, err := parseTTML(content) + list, err := ParseTTML(content) Expect(err).ToNot(HaveOccurred()) Expect(list).To(HaveLen(3)) @@ -378,8 +377,8 @@ var _ = Describe("parseTTML", func() { Expect(pronunciation.Line[0].Value).To(Equal("konni")) Expect(pronunciation.Line[0].End).To(Equal(ptr(int64(2600)))) Expect(pronunciation.Line[0].Cue).To(HaveLen(2)) - Expect(pronunciation.Line[0].Cue[0]).To(Equal(model.Cue{Start: ptr(int64(2000)), End: ptr(int64(2300)), Value: "ko", ByteStart: 0, ByteEnd: 1})) - Expect(pronunciation.Line[0].Cue[1]).To(Equal(model.Cue{Start: ptr(int64(2300)), End: ptr(int64(2600)), Value: "nni", ByteStart: 2, ByteEnd: 4})) + Expect(pronunciation.Line[0].Cue[0]).To(Equal(Cue{Start: ptr(int64(2000)), End: ptr(int64(2300)), Value: "ko", ByteStart: 0, ByteEnd: 1})) + Expect(pronunciation.Line[0].Cue[1]).To(Equal(Cue{Start: ptr(int64(2300)), End: ptr(int64(2600)), Value: "nni", ByteStart: 2, ByteEnd: 4})) }) }) @@ -405,10 +404,10 @@ var _ = Describe("parseTTML", func() { `) - list, err := parseTTML(content) + list, err := ParseTTML(content) Expect(err).ToNot(HaveOccurred()) - var pronunciation *model.Lyrics + var pronunciation *Lyrics for i := range list { if list[i].Kind == "pronunciation" { pronunciation = &list[i] @@ -422,9 +421,9 @@ var _ = Describe("parseTTML", func() { Expect(line.Start).To(Equal(ptr(int64(2747)))) Expect(line.Value).To(Equal("I woke up")) Expect(line.Cue).To(HaveLen(3)) - Expect(line.Cue[0]).To(Equal(model.Cue{Start: ptr(int64(2747)), End: ptr(int64(3018)), Value: "I", ByteStart: 0, ByteEnd: 0})) - Expect(line.Cue[1]).To(Equal(model.Cue{Start: ptr(int64(3018)), End: ptr(int64(3179)), Value: "woke", ByteStart: 2, ByteEnd: 5})) - Expect(line.Cue[2]).To(Equal(model.Cue{Start: ptr(int64(3179)), End: ptr(int64(3582)), Value: "up", ByteStart: 7, ByteEnd: 8})) + Expect(line.Cue[0]).To(Equal(Cue{Start: ptr(int64(2747)), End: ptr(int64(3018)), Value: "I", ByteStart: 0, ByteEnd: 0})) + Expect(line.Cue[1]).To(Equal(Cue{Start: ptr(int64(3018)), End: ptr(int64(3179)), Value: "woke", ByteStart: 2, ByteEnd: 5})) + Expect(line.Cue[2]).To(Equal(Cue{Start: ptr(int64(3179)), End: ptr(int64(3582)), Value: "up", ByteStart: 7, ByteEnd: 8})) }) }) }) diff --git a/model/metadata/map_mediafile.go b/model/metadata/map_mediafile.go index 52258b938..4415d8ed6 100644 --- a/model/metadata/map_mediafile.go +++ b/model/metadata/map_mediafile.go @@ -8,7 +8,6 @@ import ( "strconv" "github.com/navidrome/navidrome/conf" - lyricssvc "github.com/navidrome/navidrome/core/lyrics" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/utils/str" @@ -144,7 +143,7 @@ func (md Metadata) mapLyrics() string { lang := raw.Key() text := raw.Value() - lyrics, err := lyricssvc.ParseEmbedded(lang, text) + lyrics, err := model.ParseEmbedded(lang, text) if err != nil { log.Warn("Unexpected failure occurred when parsing lyrics", "file", md.filePath, err) continue