mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-10 11:27:11 +02:00
94 lines
2.5 KiB
Go
94 lines
2.5 KiB
Go
package plugins
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/navidrome/navidrome/log"
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/navidrome/navidrome/plugins/capabilities"
|
|
)
|
|
|
|
const CapabilityLyrics Capability = "Lyrics"
|
|
|
|
const (
|
|
FuncLyricsGetLyrics = "nd_lyrics_get_lyrics"
|
|
)
|
|
|
|
// maxConcurrentLyricsCalls caps in-flight lyrics calls per plugin: clients prefetch
|
|
// lyrics for whole queues, and the resulting burst can rate-limit upstream providers.
|
|
const maxConcurrentLyricsCalls = 2
|
|
|
|
func init() {
|
|
registerCapability(
|
|
CapabilityLyrics,
|
|
FuncLyricsGetLyrics,
|
|
)
|
|
}
|
|
|
|
func newLyricsPlugin(p *plugin) *LyricsPlugin {
|
|
displayName := p.name
|
|
if p.manifest != nil && strings.TrimSpace(p.manifest.Name) != "" {
|
|
displayName = strings.TrimSpace(p.manifest.Name)
|
|
}
|
|
return &LyricsPlugin{name: p.name, displayName: displayName, plugin: p}
|
|
}
|
|
|
|
// LyricsPlugin adapts a WASM plugin with the Lyrics capability.
|
|
type LyricsPlugin struct {
|
|
name string
|
|
displayName string
|
|
plugin *plugin
|
|
}
|
|
|
|
// GetLyrics calls the plugin to fetch lyrics, then content-sniffs each response
|
|
// via model.ParseLyrics (TTML/SRT/YAML/LRC/plain).
|
|
func (l *LyricsPlugin) GetLyrics(ctx context.Context, mf *model.MediaFile) (model.LyricList, error) {
|
|
select {
|
|
case l.plugin.lyricsSem <- struct{}{}:
|
|
defer func() { <-l.plugin.lyricsSem }()
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
}
|
|
req := capabilities.GetLyricsRequest{
|
|
Track: mediaFileToTrackInfo(l.plugin, mf),
|
|
}
|
|
resp, err := callPluginFunction[capabilities.GetLyricsRequest, capabilities.GetLyricsResponse](
|
|
ctx, l.plugin, FuncLyricsGetLyrics, req,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// The lyric text comes from the plugin, not the media file's own tags, so
|
|
// attribute logs to both the plugin and the track it was fetched for.
|
|
ctx = log.NewContext(ctx, "plugin", l.name, "file", mf.Path)
|
|
source := &model.LyricsSource{
|
|
Type: model.LyricsSourcePlugin,
|
|
Name: l.displayName,
|
|
}
|
|
if resp.Source != nil {
|
|
source.Provider = strings.TrimSpace(resp.Source.Provider)
|
|
source.Format = strings.ToLower(strings.TrimSpace(resp.Source.Format))
|
|
}
|
|
|
|
var result model.LyricList
|
|
for _, lt := range resp.Lyrics {
|
|
lang := lt.Lang
|
|
if lang == "" {
|
|
lang = "xxx"
|
|
}
|
|
parsed, err := model.ParseLyrics(ctx, "", lang, []byte(lt.Text))
|
|
if err != nil {
|
|
log.Warn(ctx, "Error parsing plugin lyrics", err)
|
|
continue
|
|
}
|
|
for _, lyric := range parsed {
|
|
if !lyric.IsEmpty() {
|
|
lyric.Source = source
|
|
result = append(result, lyric)
|
|
}
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|