From ed79a8897bc16602ebadb4e3a253d87f1599642f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deluan=20Quint=C3=A3o?= Date: Mon, 9 Feb 2026 16:16:28 -0500 Subject: [PATCH] fix(scanner): pass filename hint to gotaglib's OpenStream for format detection (#5012) * fix: split reflex -R flags to preserve directory exclusion optimization Combining the _test.go exclusion pattern (which uses $) into the same -R regex as the directory prefixes (^ui, ^data, ^db/migrations) disabled reflex's ExcludePrefix optimization. Reflex disables prefix-based directory skipping when the regex AST contains $, \z, or \b operators, causing it to traverse into ui/node_modules and hit "too many open files". Splitting into two separate -R flags fixes this: the directory prefix regex remains $-free so ExcludePrefix works, while the _test.go pattern gets its own flag where the $ anchor doesn't affect directory skipping. * fix(gotaglib): pass filename hint to OpenStream for format detection OpenStream relies on content-sniffing when no filename is provided, which fails for some files (e.g. OPUS). Pass the filename via the new WithFilename option so TagLib can use the file extension as a hint. Also adds an OPUS test fixture and test entry. Relates to https://github.com/navidrome/navidrome/issues/4604#issuecomment-3868569113, #4998, #5010 --- adapters/gotaglib/gotaglib.go | 7 ++++++- adapters/gotaglib/gotaglib_test.go | 3 +++ go.mod | 2 +- go.sum | 4 ++-- tests/fixtures/test.opus | Bin 0 -> 14236 bytes 5 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/test.opus diff --git a/adapters/gotaglib/gotaglib.go b/adapters/gotaglib/gotaglib.go index 8d166d9a..1e966036 100644 --- a/adapters/gotaglib/gotaglib.go +++ b/adapters/gotaglib/gotaglib.go @@ -119,7 +119,12 @@ func (e extractor) openFile(filePath string) (f *taglib.File, closeFunc func(), file.Close() return nil, nil, errors.New("file is not seekable") } - f, err = taglib.OpenStream(rs, taglib.WithReadStyle(taglib.ReadStyleFast)) + // WithFilename provides a format detection hint via the file extension, + // since OpenStream alone relies on content-sniffing which fails for some files. + f, err = taglib.OpenStream(rs, + taglib.WithReadStyle(taglib.ReadStyleFast), + taglib.WithFilename(filePath), + ) if err != nil { file.Close() return nil, nil, err diff --git a/adapters/gotaglib/gotaglib_test.go b/adapters/gotaglib/gotaglib_test.go index 529a8110..8fdf5b40 100644 --- a/adapters/gotaglib/gotaglib_test.go +++ b/adapters/gotaglib/gotaglib_test.go @@ -173,6 +173,9 @@ var _ = Describe("Extractor", func() { Entry("correctly parses m4a (aac) gain tags (uppercase)", "test.m4a", "1.04s", 2, 44100, 16, "0.37", "0.48", "0.37", "0.48", false, true), Entry("correctly parses ogg (vorbis) tags", "test.ogg", "1.04s", 2, 8000, 0, "+7.64 dB", "0.11772506", "+7.64 dB", "0.11772506", false, true), + // ffmpeg -f lavfi -i "sine=frequency=1100:duration=1" -c:a libopus test.opus (tags added via mutagen) + Entry("correctly parses opus tags (#4998)", "test.opus", "1s", 1, 48000, 0, "+5.12 dB", "0.11345678", "+5.12 dB", "0.11345678", false, true), + // ffmpeg -f lavfi -i "sine=frequency=900:duration=1" test.wma // Weird note: for the tag parsing to work, the lyrics are actually stored in the reverse order Entry("correctly parses wma/asf tags", "test.wma", "1.02s", 1, 44100, 16, "3.27 dB", "0.132914", "3.27 dB", "0.132914", false, true), diff --git a/go.mod b/go.mod index 3ec22556..76fa243c 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ replace ( github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 => github.com/deluan/tag v0.0.0-20241002021117-dfe5e6ea396d // Fork to implement raw tags support - go.senan.xyz/taglib => github.com/deluan/go-taglib v0.0.0-20260119020817-8753c7531798 + go.senan.xyz/taglib => github.com/deluan/go-taglib v0.0.0-20260209170351-c057626454d0 ) require ( diff --git a/go.sum b/go.sum index 7c67105a..a91ce3ba 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= -github.com/deluan/go-taglib v0.0.0-20260119020817-8753c7531798 h1:q4fvcIK/LxElpyQILCejG6WPYjVb2F/4P93+k017ANk= -github.com/deluan/go-taglib v0.0.0-20260119020817-8753c7531798/go.mod h1:sKDN0U4qXDlq6LFK+aOAkDH4Me5nDV1V/A4B+B69xBA= +github.com/deluan/go-taglib v0.0.0-20260209170351-c057626454d0 h1:R8fMzz++cqdQ3DVjzrmAKmZFr2PT8vT8pQEfRzxms00= +github.com/deluan/go-taglib v0.0.0-20260209170351-c057626454d0/go.mod h1:sKDN0U4qXDlq6LFK+aOAkDH4Me5nDV1V/A4B+B69xBA= github.com/deluan/rest v0.0.0-20211102003136-6260bc399cbf h1:tb246l2Zmpt/GpF9EcHCKTtwzrd0HGfEmoODFA/qnk4= github.com/deluan/rest v0.0.0-20211102003136-6260bc399cbf/go.mod h1:tSgDythFsl0QgS/PFWfIZqcJKnkADWneY80jaVRlqK8= github.com/deluan/sanitize v0.0.0-20241120162836-fdfd8fdfaa55 h1:wSCnggTs2f2ji6nFwQmfwgINcmSMj0xF0oHnoyRSPe4= diff --git a/tests/fixtures/test.opus b/tests/fixtures/test.opus new file mode 100644 index 0000000000000000000000000000000000000000..5052c0e6edde9389cb62c50a22a24466b4c78766 GIT binary patch literal 14236 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 Qz4v)^{lOoup5}G@8~Rbun*aa+ literal 0 HcmV?d00001