mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-14 21:37:09 +02:00
Apply code-review feedback to remove avoidable allocations in the lyrics parsers. isTTMLDocument now takes []byte directly, so parseTTMLWithDefaultLang no longer copies its buffer into a string before the TTML probe. parseSRTBlock splits its block with strings.Split instead of converting to []byte and back per line. ParseLyrics hoists strings.ToLower(suffix) out of the format loop. No behavior change; the dropped len(scanner)==0 SRT guard was dead (strings.Split never returns an empty slice, and the existing len(lines)==0 check still covers empty input). Signed-off-by: Deluan <deluan@navidrome.org>
73 lines
2.3 KiB
Go
73 lines
2.3 KiB
Go
package model
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/navidrome/navidrome/log"
|
|
)
|
|
|
|
// lyricParser returns an empty list (not an error) when the input is not its
|
|
// format, so parsers can be tried in order. lang is the default for formats that
|
|
// do not carry their own.
|
|
type lyricParser func(lang string, contents []byte) (LyricList, error)
|
|
|
|
// lyricFormats is the structured formats in content-sniff probe order; each
|
|
// row's suffixes drive sidecar dispatch. LRC/plain is the unlisted fallback floor.
|
|
var lyricFormats = []struct {
|
|
suffixes []string
|
|
parse lyricParser
|
|
}{
|
|
{[]string{".ttml"}, parseTTMLWithDefaultLang},
|
|
{[]string{".srt"}, parseSRTWithLanguage},
|
|
{[]string{".yaml", ".yml"}, parseLyricsfile},
|
|
}
|
|
|
|
// ParseLyrics is the single entry point for parsing lyrics. A known suffix routes
|
|
// to that format's parser; an empty or "auto" suffix content-sniffs. Either way,
|
|
// a structured parser that does not match falls back to the LRC/plain-text floor.
|
|
func ParseLyrics(suffix, lang string, contents []byte) (LyricList, error) {
|
|
contents = stripBOM(contents)
|
|
suffix = strings.ToLower(suffix)
|
|
sniff := suffix == "" || suffix == "auto"
|
|
|
|
// Sniffing tries every format in order; a known suffix selects just its own.
|
|
// Unmatched suffixes leave no candidates, so parseFirstMatch falls to plain.
|
|
candidates := make([]lyricParser, 0, len(lyricFormats))
|
|
for _, f := range lyricFormats {
|
|
if sniff || slices.Contains(f.suffixes, suffix) {
|
|
candidates = append(candidates, f.parse)
|
|
}
|
|
}
|
|
return parseFirstMatch(lang, contents, candidates...)
|
|
}
|
|
|
|
func parseFirstMatch(lang string, contents []byte, candidates ...lyricParser) (LyricList, error) {
|
|
for _, parse := range candidates {
|
|
list, err := parse(lang, contents)
|
|
if err == nil && len(list) > 0 {
|
|
return list, nil
|
|
}
|
|
if err != nil {
|
|
log.Warn("Error parsing lyrics, falling back to plain text", "error", err)
|
|
}
|
|
}
|
|
return plainLRC(lang, contents)
|
|
}
|
|
|
|
func plainLRC(lang string, contents []byte) (LyricList, error) {
|
|
lyric, err := parseLRC(lang, string(contents))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing lyrics: %w", err)
|
|
}
|
|
if lyric == nil || lyric.IsEmpty() {
|
|
return nil, nil
|
|
}
|
|
return LyricList{*lyric}, nil
|
|
}
|
|
|
|
func stripBOM(contents []byte) []byte {
|
|
return bytes.TrimPrefix(contents, []byte("\ufeff"))
|
|
}
|