From 0ec7a818fb0e0a881b3e9667cf72089b7651d730 Mon Sep 17 00:00:00 2001 From: Deluan Date: Fri, 19 Jun 2026 14:15:07 -0400 Subject: [PATCH] feat(plugins): content-sniff plugin lyrics for all formats Replace model.ToLyrics (LRC/plain only) with model.ParseEmbedded so plugin responses are content-sniffed for TTML, SRT, YAML, LRC, and plain text. ParseEmbedded returns a LyricList, so the loop now flattens multiple tracks per response entry. The test-lyrics WASM plugin gains a "ttml" format mode (configured via pdk.GetConfig) that returns a minimal TTML document; rebuilt with the standard Go wasip1 toolchain (GOOS=wasip1 GOARCH=wasm). A new Ginkgo test asserts Synced==true and the exact cue value, which the old plain-text path could not produce. GetLyrics doc comment updated to reflect content-sniffing; a later task will retarget it to ParseLyrics once that function is introduced. --- plugins/lyrics_adapter.go | 13 ++++++++----- plugins/lyrics_adapter_test.go | 19 +++++++++++++++++++ plugins/testdata/test-lyrics/main.go | 18 ++++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/plugins/lyrics_adapter.go b/plugins/lyrics_adapter.go index c66b027d7..08994220a 100644 --- a/plugins/lyrics_adapter.go +++ b/plugins/lyrics_adapter.go @@ -31,8 +31,9 @@ type LyricsPlugin struct { plugin *plugin } -// GetLyrics calls the plugin to fetch lyrics, then parses the raw text responses -// using model.ToLyrics. +// GetLyrics calls the plugin to fetch lyrics, then content-sniffs each response +// via model.ParseEmbedded (TTML/SRT/YAML/LRC/plain). A later task retargets this +// to ParseLyrics once that function is introduced. func (l *LyricsPlugin) GetLyrics(ctx context.Context, mf *model.MediaFile) (model.LyricList, error) { req := capabilities.GetLyricsRequest{ Track: mediaFileToTrackInfo(l.plugin, mf), @@ -50,13 +51,15 @@ func (l *LyricsPlugin) GetLyrics(ctx context.Context, mf *model.MediaFile) (mode if lang == "" { lang = "xxx" } - parsed, err := model.ToLyrics(lang, lt.Text) + parsed, err := model.ParseEmbedded(lang, lt.Text) if err != nil { log.Warn(ctx, "Error parsing plugin lyrics", "plugin", l.name, err) continue } - if parsed != nil && !parsed.IsEmpty() { - result = append(result, *parsed) + for _, lyric := range parsed { + if !lyric.IsEmpty() { + result = append(result, lyric) + } } } return result, nil diff --git a/plugins/lyrics_adapter_test.go b/plugins/lyrics_adapter_test.go index a1a6c1809..77b81099b 100644 --- a/plugins/lyrics_adapter_test.go +++ b/plugins/lyrics_adapter_test.go @@ -83,6 +83,25 @@ var _ = Describe("LyricsPlugin", Ordered, func() { _, err := p.GetLyrics(GinkgoT().Context(), track) Expect(err).To(HaveOccurred()) }) + + It("content-sniffs TTML returned by the plugin", func() { + manager, _ := createTestManagerWithPlugins(map[string]map[string]string{ + "test-lyrics": {"format": "ttml"}, + }, "test-lyrics"+PackageExtension) + + p, ok := manager.LoadLyricsProvider("test-lyrics") + Expect(ok).To(BeTrue()) + + track := &model.MediaFile{ID: "track-1", Title: "Test Song", Artist: "Test Artist"} + result, err := p.GetLyrics(GinkgoT().Context(), track) + Expect(err).ToNot(HaveOccurred()) + Expect(result).To(HaveLen(1)) + // Verify the result is parsed as TTML (synced, with the exact cue text), + // not mangled as plain text with raw XML markup. + Expect(result[0].Synced).To(BeTrue()) + Expect(result[0].Line).To(HaveLen(1)) + Expect(result[0].Line[0].Value).To(Equal("plugin ttml line")) + }) }) Describe("PluginNames", func() { diff --git a/plugins/testdata/test-lyrics/main.go b/plugins/testdata/test-lyrics/main.go index 0e485ceba..c87fdf358 100644 --- a/plugins/testdata/test-lyrics/main.go +++ b/plugins/testdata/test-lyrics/main.go @@ -21,6 +21,24 @@ func (t *testLyrics) GetLyrics(input lyrics.GetLyricsRequest) (lyrics.GetLyricsR return lyrics.GetLyricsResponse{}, fmt.Errorf("%s", errMsg) } + // Return a minimal TTML document to exercise content-sniffing for rich formats. + format, hasFormat := pdk.GetConfig("format") + if hasFormat && format == "ttml" { + const ttml = ` + + +
+

plugin ttml line

+
+ +
` + return lyrics.GetLyricsResponse{ + Lyrics: []lyrics.LyricsText{ + {Lang: "eng", Text: ttml}, + }, + }, nil + } + // Check if we should omit language (to test default language handling) noLang, hasNoLang := pdk.GetConfig("no_lang") lang := "eng"