mirror of
https://github.com/navidrome/navidrome.git
synced 2026-07-09 17:18:45 +00:00
fix(subsonic): emit agent-specific cueLine values (#5679)
Some checks are pending
Pipeline: Test, Lint, Build / Get version info (push) Waiting to run
Pipeline: Test, Lint, Build / Lint Go code (push) Waiting to run
Pipeline: Test, Lint, Build / Test Go code (push) Waiting to run
Pipeline: Test, Lint, Build / Test Go code (Windows) (push) Waiting to run
Pipeline: Test, Lint, Build / Test JS code (push) Waiting to run
Pipeline: Test, Lint, Build / Lint i18n files (push) Waiting to run
Pipeline: Test, Lint, Build / Check Docker configuration (push) Waiting to run
Pipeline: Test, Lint, Build / Build (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-1 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-2 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-3 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-4 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-5 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-6 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-7 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build Windows installers (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-8 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-9 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-10 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Push to GHCR (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Package/Release (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Blocked by required conditions
Some checks are pending
Pipeline: Test, Lint, Build / Get version info (push) Waiting to run
Pipeline: Test, Lint, Build / Lint Go code (push) Waiting to run
Pipeline: Test, Lint, Build / Test Go code (push) Waiting to run
Pipeline: Test, Lint, Build / Test Go code (Windows) (push) Waiting to run
Pipeline: Test, Lint, Build / Test JS code (push) Waiting to run
Pipeline: Test, Lint, Build / Lint i18n files (push) Waiting to run
Pipeline: Test, Lint, Build / Check Docker configuration (push) Waiting to run
Pipeline: Test, Lint, Build / Build (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-1 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-2 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-3 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-4 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-5 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-6 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-7 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build Windows installers (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-8 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-9 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-10 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Push to GHCR (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Package/Release (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Blocked by required conditions
* fix(subsonic): emit agent-specific cue line values * fix(subsonic): preserve cue line edge text
This commit is contained in:
parent
5a3ac80a8a
commit
9b862e8833
2 changed files with 304 additions and 6 deletions
|
|
@ -114,12 +114,17 @@ func buildCueLines(line model.Line, index int32, agents lyricAgents) []responses
|
||||||
|
|
||||||
cueLines := make([]responses.CueLine, 0, len(agentOrder))
|
cueLines := make([]responses.CueLine, 0, len(agentOrder))
|
||||||
for _, agentID := range agentOrder {
|
for _, agentID := range agentOrder {
|
||||||
|
value := line.Value
|
||||||
|
cues := cuesByAgent[agentID]
|
||||||
|
if len(agentOrder) > 1 {
|
||||||
|
value, cues = buildAgentCueLineValue(line.Value, cues, line.Cue, agentID)
|
||||||
|
}
|
||||||
cueLine := responses.CueLine{
|
cueLine := responses.CueLine{
|
||||||
Index: index,
|
Index: index,
|
||||||
Start: line.Start,
|
Start: line.Start,
|
||||||
End: line.End,
|
End: line.End,
|
||||||
Value: line.Value,
|
Value: value,
|
||||||
Cue: buildLyricCues(cuesByAgent[agentID], line.End),
|
Cue: buildLyricCues(cues, line.End),
|
||||||
}
|
}
|
||||||
if agentID != "" {
|
if agentID != "" {
|
||||||
cueLine.AgentID = agentID
|
cueLine.AgentID = agentID
|
||||||
|
|
@ -149,6 +154,85 @@ func (a lyricAgents) less(left, right string, origI, origJ int) bool {
|
||||||
return origI < origJ
|
return origI < origJ
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildAgentCueLineValue(lineValue string, cues, allCues []model.Cue, agentID string) (string, []model.Cue) {
|
||||||
|
if len(cues) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
remapped := slices.Clone(cues)
|
||||||
|
var value strings.Builder
|
||||||
|
leadingGap := cueLineGap(lineValue, 0, remapped[0].ByteStart, allCues, agentID)
|
||||||
|
if strings.TrimSpace(leadingGap) != "" {
|
||||||
|
value.WriteString(leadingGap)
|
||||||
|
}
|
||||||
|
|
||||||
|
previousEnd := -1
|
||||||
|
for i := range remapped {
|
||||||
|
originalStart := remapped[i].ByteStart
|
||||||
|
originalEnd := remapped[i].ByteEnd
|
||||||
|
if i > 0 {
|
||||||
|
value.WriteString(cueLineGap(lineValue, previousEnd+1, originalStart, allCues, agentID))
|
||||||
|
}
|
||||||
|
|
||||||
|
remapped[i].ByteStart = value.Len()
|
||||||
|
value.WriteString(remapped[i].Value)
|
||||||
|
remapped[i].ByteEnd = value.Len() - 1
|
||||||
|
previousEnd = originalEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
trailingGap := cueLineGap(lineValue, previousEnd+1, len(lineValue), allCues, agentID)
|
||||||
|
if strings.TrimSpace(trailingGap) != "" {
|
||||||
|
value.WriteString(trailingGap)
|
||||||
|
}
|
||||||
|
return value.String(), remapped
|
||||||
|
}
|
||||||
|
|
||||||
|
type byteRange struct {
|
||||||
|
start int
|
||||||
|
end int
|
||||||
|
}
|
||||||
|
|
||||||
|
func cueLineGap(source string, start, end int, allCues []model.Cue, agentID string) string {
|
||||||
|
start = max(start, 0)
|
||||||
|
end = min(end, len(source))
|
||||||
|
if start >= end {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
excluded := make([]byteRange, 0, 1)
|
||||||
|
for _, cue := range allCues {
|
||||||
|
if strings.TrimSpace(cue.AgentID) == agentID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cueStart := max(cue.ByteStart, start)
|
||||||
|
cueEnd := min(cue.ByteEnd+1, end)
|
||||||
|
if cueStart < cueEnd {
|
||||||
|
excluded = append(excluded, byteRange{start: cueStart, end: cueEnd})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(excluded) == 0 {
|
||||||
|
return source[start:end]
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(excluded, func(i, j int) bool {
|
||||||
|
return excluded[i].start < excluded[j].start
|
||||||
|
})
|
||||||
|
|
||||||
|
var gap strings.Builder
|
||||||
|
cursor := start
|
||||||
|
for _, r := range excluded {
|
||||||
|
if r.start > cursor {
|
||||||
|
gap.WriteString(source[cursor:r.start])
|
||||||
|
}
|
||||||
|
cursor = max(cursor, r.end)
|
||||||
|
}
|
||||||
|
if cursor < end {
|
||||||
|
gap.WriteString(source[cursor:end])
|
||||||
|
}
|
||||||
|
return gap.String()
|
||||||
|
}
|
||||||
|
|
||||||
func buildLyricCues(cues []model.Cue, lineEnd *int64) []responses.LyricCue {
|
func buildLyricCues(cues []model.Cue, lineEnd *int64) []responses.LyricCue {
|
||||||
if len(cues) == 0 {
|
if len(cues) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -431,7 +431,7 @@ var _ = Describe("GetLyricsBySongId", func() {
|
||||||
Index: 0,
|
Index: 0,
|
||||||
Start: &lineStart,
|
Start: &lineStart,
|
||||||
End: &lineEnd,
|
End: &lineEnd,
|
||||||
Value: "Hello echo",
|
Value: "Hello",
|
||||||
AgentID: "lead",
|
AgentID: "lead",
|
||||||
Cue: []responses.LyricCue{
|
Cue: []responses.LyricCue{
|
||||||
{
|
{
|
||||||
|
|
@ -447,14 +447,14 @@ var _ = Describe("GetLyricsBySongId", func() {
|
||||||
Index: 0,
|
Index: 0,
|
||||||
Start: &lineStart,
|
Start: &lineStart,
|
||||||
End: &lineEnd,
|
End: &lineEnd,
|
||||||
Value: "Hello echo",
|
Value: "echo",
|
||||||
AgentID: "__nd_bg__|lead",
|
AgentID: "__nd_bg__|lead",
|
||||||
Cue: []responses.LyricCue{
|
Cue: []responses.LyricCue{
|
||||||
{
|
{
|
||||||
Start: tokenStartB,
|
Start: tokenStartB,
|
||||||
End: &tokenEndB,
|
End: &tokenEndB,
|
||||||
ByteStart: 6,
|
ByteStart: 0,
|
||||||
ByteEnd: 9,
|
ByteEnd: 3,
|
||||||
Value: "echo",
|
Value: "echo",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -465,6 +465,220 @@ var _ = Describe("GetLyricsBySongId", func() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should preserve shared edge text when remapping agent cue lines", func() {
|
||||||
|
lineStart := int64(1000)
|
||||||
|
lineEnd := int64(2000)
|
||||||
|
cueStart := int64(1200)
|
||||||
|
cueEnd := int64(1800)
|
||||||
|
|
||||||
|
cueLines := buildCueLines(model.Line{
|
||||||
|
Start: &lineStart,
|
||||||
|
End: &lineEnd,
|
||||||
|
Value: "(Hello)",
|
||||||
|
Cue: []model.Cue{
|
||||||
|
{
|
||||||
|
Start: &cueStart,
|
||||||
|
End: &cueEnd,
|
||||||
|
Value: "Hello",
|
||||||
|
ByteStart: 1,
|
||||||
|
ByteEnd: 5,
|
||||||
|
AgentID: "lead",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Start: &cueStart,
|
||||||
|
End: &cueEnd,
|
||||||
|
Value: "Hello",
|
||||||
|
ByteStart: 1,
|
||||||
|
ByteEnd: 5,
|
||||||
|
AgentID: "__nd_bg__|lead",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, 0, newLyricAgents([]model.Agent{
|
||||||
|
{ID: "lead", Role: "main"},
|
||||||
|
{ID: "__nd_bg__|lead", Role: "bg"},
|
||||||
|
}))
|
||||||
|
|
||||||
|
Expect(cueLines).To(Equal([]responses.CueLine{
|
||||||
|
{
|
||||||
|
Index: 0,
|
||||||
|
Start: &lineStart,
|
||||||
|
End: &lineEnd,
|
||||||
|
Value: "(Hello)",
|
||||||
|
AgentID: "lead",
|
||||||
|
Cue: []responses.LyricCue{
|
||||||
|
{
|
||||||
|
Start: cueStart,
|
||||||
|
End: &cueEnd,
|
||||||
|
Value: "Hello",
|
||||||
|
ByteStart: 1,
|
||||||
|
ByteEnd: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Index: 0,
|
||||||
|
Start: &lineStart,
|
||||||
|
End: &lineEnd,
|
||||||
|
Value: "(Hello)",
|
||||||
|
AgentID: "__nd_bg__|lead",
|
||||||
|
Cue: []responses.LyricCue{
|
||||||
|
{
|
||||||
|
Start: cueStart,
|
||||||
|
End: &cueEnd,
|
||||||
|
Value: "Hello",
|
||||||
|
ByteStart: 1,
|
||||||
|
ByteEnd: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should remap cue offsets for interleaved agent cue lines", func() {
|
||||||
|
r := newGetRequest("id=1&enhanced=true")
|
||||||
|
|
||||||
|
lineStart := int64(82889)
|
||||||
|
lineEnd := int64(86859)
|
||||||
|
realStart := int64(85593)
|
||||||
|
realEnd := int64(85934)
|
||||||
|
slowStart := int64(85934)
|
||||||
|
slowEnd := int64(86751)
|
||||||
|
bgStartA := int64(83881)
|
||||||
|
bgEndA := int64(84243)
|
||||||
|
bgStartB := int64(86232)
|
||||||
|
bgEndB := int64(86859)
|
||||||
|
lyricsJSON, err := json.Marshal(model.LyricList{
|
||||||
|
{
|
||||||
|
Lang: "eng",
|
||||||
|
Agents: []model.Agent{{ID: "v2", Role: "main"}, {ID: "__nd_bg__|v2", Role: "bg"}},
|
||||||
|
Synced: true,
|
||||||
|
Line: []model.Line{
|
||||||
|
{
|
||||||
|
Start: &lineStart,
|
||||||
|
End: &lineEnd,
|
||||||
|
Value: "real slow (When you slide)",
|
||||||
|
Cue: []model.Cue{
|
||||||
|
{
|
||||||
|
Start: &realStart,
|
||||||
|
End: &realEnd,
|
||||||
|
Value: "real",
|
||||||
|
ByteStart: 0,
|
||||||
|
ByteEnd: 3,
|
||||||
|
AgentID: "v2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Start: &slowStart,
|
||||||
|
End: &slowEnd,
|
||||||
|
Value: "slow",
|
||||||
|
ByteStart: 5,
|
||||||
|
ByteEnd: 8,
|
||||||
|
AgentID: "v2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Start: &bgStartA,
|
||||||
|
End: &bgEndA,
|
||||||
|
Value: "(When you",
|
||||||
|
ByteStart: 10,
|
||||||
|
ByteEnd: 18,
|
||||||
|
AgentID: "__nd_bg__|v2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Start: &bgStartB,
|
||||||
|
End: &bgEndB,
|
||||||
|
Value: "slide)",
|
||||||
|
ByteStart: 20,
|
||||||
|
ByteEnd: 25,
|
||||||
|
AgentID: "__nd_bg__|v2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
mockRepo.SetData(model.MediaFiles{
|
||||||
|
{
|
||||||
|
ID: "1",
|
||||||
|
Artist: "Rick Astley",
|
||||||
|
Title: "Never Gonna Give You Up",
|
||||||
|
Lyrics: string(lyricsJSON),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
response, err := router.GetLyricsBySongId(r)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
compareResponses(response.LyricsList, responses.LyricsList{
|
||||||
|
StructuredLyrics: responses.StructuredLyrics{
|
||||||
|
{
|
||||||
|
DisplayArtist: "Rick Astley",
|
||||||
|
DisplayTitle: "Never Gonna Give You Up",
|
||||||
|
Kind: "main",
|
||||||
|
Lang: "eng",
|
||||||
|
Synced: true,
|
||||||
|
Agents: []responses.Agent{
|
||||||
|
{ID: "v2", Role: "main"},
|
||||||
|
{ID: "__nd_bg__|v2", Role: "bg"},
|
||||||
|
},
|
||||||
|
Line: []responses.Line{
|
||||||
|
{
|
||||||
|
Start: &lineStart,
|
||||||
|
Value: "real slow (When you slide)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CueLine: []responses.CueLine{
|
||||||
|
{
|
||||||
|
Index: 0,
|
||||||
|
Start: &lineStart,
|
||||||
|
End: &lineEnd,
|
||||||
|
Value: "real slow",
|
||||||
|
AgentID: "v2",
|
||||||
|
Cue: []responses.LyricCue{
|
||||||
|
{
|
||||||
|
Start: realStart,
|
||||||
|
End: &realEnd,
|
||||||
|
ByteStart: 0,
|
||||||
|
ByteEnd: 3,
|
||||||
|
Value: "real",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Start: slowStart,
|
||||||
|
End: &slowEnd,
|
||||||
|
ByteStart: 5,
|
||||||
|
ByteEnd: 8,
|
||||||
|
Value: "slow",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Index: 0,
|
||||||
|
Start: &lineStart,
|
||||||
|
End: &lineEnd,
|
||||||
|
Value: "(When you slide)",
|
||||||
|
AgentID: "__nd_bg__|v2",
|
||||||
|
Cue: []responses.LyricCue{
|
||||||
|
{
|
||||||
|
Start: bgStartA,
|
||||||
|
End: &bgEndA,
|
||||||
|
ByteStart: 0,
|
||||||
|
ByteEnd: 8,
|
||||||
|
Value: "(When you",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Start: bgStartB,
|
||||||
|
End: &bgEndB,
|
||||||
|
ByteStart: 10,
|
||||||
|
ByteEnd: 15,
|
||||||
|
Value: "slide)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
It("should keep enhanced line-level lyrics when no cue data is available", func() {
|
It("should keep enhanced line-level lyrics when no cue data is available", func() {
|
||||||
r := newGetRequest("id=1&enhanced=true")
|
r := newGetRequest("id=1&enhanced=true")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue