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))
|
||||
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{
|
||||
Index: index,
|
||||
Start: line.Start,
|
||||
End: line.End,
|
||||
Value: line.Value,
|
||||
Cue: buildLyricCues(cuesByAgent[agentID], line.End),
|
||||
Value: value,
|
||||
Cue: buildLyricCues(cues, line.End),
|
||||
}
|
||||
if agentID != "" {
|
||||
cueLine.AgentID = agentID
|
||||
|
|
@ -149,6 +154,85 @@ func (a lyricAgents) less(left, right string, origI, origJ int) bool {
|
|||
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 {
|
||||
if len(cues) == 0 {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -431,7 +431,7 @@ var _ = Describe("GetLyricsBySongId", func() {
|
|||
Index: 0,
|
||||
Start: &lineStart,
|
||||
End: &lineEnd,
|
||||
Value: "Hello echo",
|
||||
Value: "Hello",
|
||||
AgentID: "lead",
|
||||
Cue: []responses.LyricCue{
|
||||
{
|
||||
|
|
@ -447,14 +447,14 @@ var _ = Describe("GetLyricsBySongId", func() {
|
|||
Index: 0,
|
||||
Start: &lineStart,
|
||||
End: &lineEnd,
|
||||
Value: "Hello echo",
|
||||
Value: "echo",
|
||||
AgentID: "__nd_bg__|lead",
|
||||
Cue: []responses.LyricCue{
|
||||
{
|
||||
Start: tokenStartB,
|
||||
End: &tokenEndB,
|
||||
ByteStart: 6,
|
||||
ByteEnd: 9,
|
||||
ByteStart: 0,
|
||||
ByteEnd: 3,
|
||||
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() {
|
||||
r := newGetRequest("id=1&enhanced=true")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue