fix(lyrics): honor caller language when Lyricsfile YAML omits it

parseLyricsfile discarded the caller's language argument, so a Lyricsfile YAML
parsed from an embedded tag or plugin response with no metadata.language was
labeled "xxx" even when ParseLyrics was given a language. The SRT and TTML
parsers already use the caller language as their default; fall back to it here
too, preferring the document's own metadata.language when present.

Also reword a misleading TTML comment: isTTMLDocument still runs an XML decode
(it stops at the first element), so the skip avoids the full TTML parse, not the
XML decoder entirely.
This commit is contained in:
Deluan 2026-06-19 17:58:05 -04:00
commit e8a404ac76
3 changed files with 28 additions and 3 deletions

View file

@ -20,7 +20,7 @@ import (
// overlapping lines are attributed to synthetic voice agents via lowest-free
// voice ID assignment so the OpenSubsonic v2 enhanced response can split
// parallel vocals.
func parseLyricsfile(_ string, contents []byte) (LyricList, error) {
func parseLyricsfile(lang string, contents []byte) (LyricList, error) {
var doc lyricsfileDocument
dec := yaml.NewDecoder(bytes.NewReader(contents))
dec.KnownFields(false)
@ -32,10 +32,16 @@ func parseLyricsfile(_ string, contents []byte) (LyricList, error) {
return nil, nil
}
// Fall back to the caller's language when the document omits its own, matching
// the SRT/TTML parsers; normalizeLyricLang yields "xxx" only if both are empty.
docLang := doc.Metadata.Language
if strings.TrimSpace(docLang) == "" {
docLang = lang
}
lyrics := Lyrics{
DisplayArtist: str.SanitizeText(doc.Metadata.Artist),
DisplayTitle: str.SanitizeText(doc.Metadata.Title),
Lang: normalizeLyricLang(doc.Metadata.Language),
Lang: normalizeLyricLang(docLang),
Kind: LyricKindMain,
}
if doc.Metadata.OffsetMs != 0 {

View file

@ -66,6 +66,24 @@ lines:
Expect(l.Line[1].Cue).To(BeNil())
})
DescribeTable("resolves the lyric language",
func(metaLanguage, callerLang, want string) {
input := "version: '1.0'\nmetadata:\n title: 'T'\n"
if metaLanguage != "" {
input += " language: '" + metaLanguage + "'\n"
}
input += "lines:\n - text: \"line\"\n start_ms: 0\n"
lyrics, err := parseLyricsfile(callerLang, []byte(input))
Expect(err).ToNot(HaveOccurred())
Expect(lyrics).To(HaveLen(1))
Expect(lyrics[0].Lang).To(Equal(want))
},
Entry("prefers the document's own language", "eng", "deu", "eng"),
Entry("falls back to the caller language when metadata omits it", "", "deu", "deu"),
Entry("uses xxx when neither is provided", "", "", "xxx"),
)
It("parses plain-only Lyricsfile lyrics as unsynced lines", func() {
input := `version: '1.0'
metadata:

View file

@ -118,7 +118,8 @@ func isTTMLDocument(contents []byte) bool {
func parseTTMLWithDefaultLang(defaultLang string, contents []byte) (LyricList, error) {
contents = xmlEncodingRegex.ReplaceAll(contents, []byte(`<?xml$1encoding="UTF-8"$2?>`))
// Skip non-TTML content so sniffing doesn't run the XML decoder on plain text.
// Skip non-TTML content so sniffing doesn't run the full TTML parse on plain
// text — isTTMLDocument does a cheap decode that stops at the first element.
// Checked after the encoding fixup so UTF-16-declared documents are recognized.
if !isTTMLDocument(contents) {
return nil, nil