mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
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.
This commit is contained in:
parent
85134673a2
commit
917f16a96d
@ -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
|
||||
|
||||
@ -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 {
|
||||
@ -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"},
|
||||
}))
|
||||
@ -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,
|
||||
5
model/lyrics_test_helpers_test.go
Normal file
5
model/lyrics_test_helpers_test.go
Normal file
@ -0,0 +1,5 @@
|
||||
package model
|
||||
|
||||
func ptr[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
@ -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(`<?xml$1encoding="UTF-8"$2?>`))
|
||||
|
||||
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 {
|
||||
@ -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(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
@ -22,7 +21,7 @@ var _ = Describe("parseTTML", func() {
|
||||
</body>
|
||||
</tt>`)
|
||||
|
||||
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() {
|
||||
</body>
|
||||
</tt>`)
|
||||
|
||||
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() {
|
||||
</body>
|
||||
</tt>`)
|
||||
|
||||
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() {
|
||||
</body>
|
||||
</tt>`)
|
||||
|
||||
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() {
|
||||
</body>
|
||||
</tt>`)
|
||||
|
||||
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() {
|
||||
</body>
|
||||
</tt>`)
|
||||
|
||||
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() {
|
||||
</body>
|
||||
</tt>`)
|
||||
|
||||
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() {
|
||||
</body>
|
||||
</tt>`)
|
||||
|
||||
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() {
|
||||
</body>
|
||||
</tt>`)
|
||||
|
||||
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() {
|
||||
</body>
|
||||
</tt>`)
|
||||
|
||||
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() {
|
||||
</body>
|
||||
</tt>`)
|
||||
|
||||
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() {
|
||||
</body>
|
||||
</tt>`)
|
||||
|
||||
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() {
|
||||
</body>
|
||||
</tt>`)
|
||||
|
||||
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}))
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user