mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
refactor(lyrics): share cue end-time normalization across layers
buildLyricCues (subsonic response) reimplemented the same fill-from-next / fallback / all-or-none end-time logic that normalizeCueLine (model) already ran at parse time. They operate on different groupings -- the response builder normalizes per-agent cue subsets, the model the whole line -- so they cannot be collapsed, but the algorithm itself was duplicated. Extract model.NormalizeCueEnds([]Cue, fallbackEnd) as the single implementation (now also clamping ends within the model line normalization), and have buildLyricCues delegate to it and only map to the response type.
This commit is contained in:
parent
a42b27cfce
commit
7796faa1b2
@ -467,39 +467,56 @@ func normalizeCueLine(line Line, fallbackEnd *int64) Line {
|
||||
if len(line.Cue) == 0 {
|
||||
return line
|
||||
}
|
||||
|
||||
for i := range line.Cue {
|
||||
if line.Cue[i].End != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if i+1 < len(line.Cue) && line.Cue[i+1].Start != nil {
|
||||
v := *line.Cue[i+1].Start
|
||||
line.Cue[i].End = &v
|
||||
continue
|
||||
}
|
||||
|
||||
if fallbackEnd != nil {
|
||||
v := *fallbackEnd
|
||||
line.Cue[i].End = &v
|
||||
}
|
||||
}
|
||||
|
||||
for i := range line.Cue {
|
||||
if line.Cue[i].End == nil {
|
||||
line.Cue = clearCueEnds(line.Cue)
|
||||
return NormalizeLineTiming(line)
|
||||
}
|
||||
}
|
||||
|
||||
line.Cue = NormalizeCueEnds(line.Cue, fallbackEnd)
|
||||
return NormalizeLineTiming(line)
|
||||
}
|
||||
|
||||
func clearCueEnds(cues []Cue) []Cue {
|
||||
normalized := make([]Cue, len(cues))
|
||||
copy(normalized, cues)
|
||||
for i := range normalized {
|
||||
normalized[i].End = nil
|
||||
// NormalizeCueEnds resolves missing cue end times within a single ordered cue
|
||||
// group: each end is filled from the next cue's start, then from fallbackEnd,
|
||||
// and is clamped so it never precedes the cue's own start nor overruns the next
|
||||
// cue. End times are all-or-none — if any cue still lacks an end afterwards, all
|
||||
// ends in the group are cleared. The input slice is never mutated.
|
||||
func NormalizeCueEnds(cues []Cue, fallbackEnd *int64) []Cue {
|
||||
if len(cues) == 0 {
|
||||
return cues
|
||||
}
|
||||
return normalized
|
||||
|
||||
out := slices.Clone(cues)
|
||||
for i := range out {
|
||||
end := out[i].End
|
||||
if end == nil {
|
||||
if i+1 < len(out) && out[i+1].Start != nil {
|
||||
end = out[i+1].Start
|
||||
} else {
|
||||
end = fallbackEnd
|
||||
}
|
||||
}
|
||||
if end != nil && i+1 < len(out) && out[i+1].Start != nil && *end > *out[i+1].Start {
|
||||
end = out[i+1].Start
|
||||
}
|
||||
if end != nil && out[i].Start != nil && *end < *out[i].Start {
|
||||
end = out[i].Start
|
||||
}
|
||||
out[i].End = ptrOrNil(end)
|
||||
}
|
||||
|
||||
for i := range out {
|
||||
if out[i].End == nil {
|
||||
for j := range out {
|
||||
out[j].End = nil
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ptrOrNil returns a fresh copy of *v so callers never alias another cue's
|
||||
// Start/End pointer into the result.
|
||||
func ptrOrNil(v *int64) *int64 {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
c := *v
|
||||
return &c
|
||||
}
|
||||
|
||||
@ -606,20 +606,18 @@ func buildLyricCues(cues []model.Cue, lineEnd *int64) []responses.LyricCue {
|
||||
return nil
|
||||
}
|
||||
|
||||
hasAnyEnd := false
|
||||
for i := range cues {
|
||||
if cues[i].End != nil {
|
||||
hasAnyEnd = true
|
||||
break
|
||||
}
|
||||
// Only resolve end times when at least one cue carries one; otherwise the
|
||||
// group is start-only and must stay that way.
|
||||
hasAnyEnd := slices.ContainsFunc(cues, func(c model.Cue) bool { return c.End != nil })
|
||||
if hasAnyEnd {
|
||||
cues = model.NormalizeCueEnds(cues, lineEnd)
|
||||
}
|
||||
|
||||
normalized := make([]responses.LyricCue, 0, len(cues))
|
||||
out := make([]responses.LyricCue, 0, len(cues))
|
||||
for i := range cues {
|
||||
if cues[i].Start == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
cue := responses.LyricCue{
|
||||
Start: *cues[i].Start,
|
||||
Value: cues[i].Value,
|
||||
@ -627,41 +625,11 @@ func buildLyricCues(cues []model.Cue, lineEnd *int64) []responses.LyricCue {
|
||||
ByteEnd: cues[i].ByteEnd,
|
||||
}
|
||||
if hasAnyEnd {
|
||||
end := cues[i].End
|
||||
if end == nil {
|
||||
if i+1 < len(cues) && cues[i+1].Start != nil {
|
||||
v := *cues[i+1].Start
|
||||
end = &v
|
||||
} else if lineEnd != nil {
|
||||
v := *lineEnd
|
||||
end = &v
|
||||
}
|
||||
}
|
||||
if end != nil && i+1 < len(cues) && cues[i+1].Start != nil && *end > *cues[i+1].Start {
|
||||
v := *cues[i+1].Start
|
||||
end = &v
|
||||
}
|
||||
if end != nil && *end < cue.Start {
|
||||
v := cue.Start
|
||||
end = &v
|
||||
}
|
||||
cue.End = end
|
||||
cue.End = cues[i].End
|
||||
}
|
||||
normalized = append(normalized, cue)
|
||||
out = append(out, cue)
|
||||
}
|
||||
|
||||
if hasAnyEnd {
|
||||
for i := range normalized {
|
||||
if normalized[i].End == nil {
|
||||
for j := range normalized {
|
||||
normalized[j].End = nil
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return normalized
|
||||
return out
|
||||
}
|
||||
|
||||
func buildLyricsList(mf *model.MediaFile, lyricsList model.LyricList, enhanced bool) *responses.LyricsList {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user