diff --git a/adapters/gotaglib/gotaglib.go b/adapters/gotaglib/gotaglib.go index 7ea98a4421f..c4f8a509594 100644 --- a/adapters/gotaglib/gotaglib.go +++ b/adapters/gotaglib/gotaglib.go @@ -23,6 +23,7 @@ import ( "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/core/storage/local" "github.com/navidrome/navidrome/log" + "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/metadata" "go.senan.xyz/taglib" ) @@ -36,6 +37,29 @@ func (e extractor) Parse(files ...string) (map[string]metadata.Info, error) { for _, path := range files { props, err := e.extractMetadata(path) if err != nil { + if !conf.Server.Scanner.ImportOnReadError { + continue + } + log.Warn("gotaglib: Error reading metadata from file. Importing without metadata", "filePath", path, err) + f, openErr := e.fs.Open(path) + if openErr != nil { + continue + } + info, statErr := f.Stat() + _ = f.Close() + if statErr != nil { + continue + } + fileInfo, ok := info.(metadata.FileInfo) + if !ok { + continue + } + results[path] = metadata.Info{ + FileInfo: fileInfo, + Tags: model.RawTags{}, + AudioProperties: metadata.AudioProperties{}, + HasPicture: false, + } continue } results[path] = *props diff --git a/adapters/gotaglib/gotaglib_test.go b/adapters/gotaglib/gotaglib_test.go index 05924914d72..3568732ef76 100644 --- a/adapters/gotaglib/gotaglib_test.go +++ b/adapters/gotaglib/gotaglib_test.go @@ -7,6 +7,8 @@ import ( "github.com/navidrome/navidrome/tests" "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/conf" + "github.com/navidrome/navidrome/model/metadata" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -252,6 +254,46 @@ var _ = Describe("Extractor", func() { }) }) + Context("when metadata cannot be read", func() { + var invalidFile string + + BeforeEach(func() { + invalidFile = utils.TempFileName("invalid-audio-", ".xm") + + Expect(os.WriteFile(invalidFile, []byte("this is not a valid audio file"), 0644)).To(Succeed()) + + DeferCleanup(func() { + Expect(os.Remove(invalidFile)).To(Succeed()) + }) + + // Use root fs for absolute paths in temp directory + e = &extractor{fs: os.DirFS("/")} + }) + + It("skips the file when ImportOnReadError is disabled", func() { + conf.Server.Scanner.ImportOnReadError = false + + // Strip leading slash for DirFS rooted at "/" + mds, err := e.Parse(invalidFile[1:]) + Expect(err).ToNot(HaveOccurred()) + Expect(mds).ToNot(HaveKey(invalidFile[1:])) + }) + + It("imports the file with empty metadata when ImportOnReadError is enabled", func() { + conf.Server.Scanner.ImportOnReadError = true + + // Strip leading slash for DirFS rooted at "/" + mds, err := e.Parse(invalidFile[1:]) + Expect(err).ToNot(HaveOccurred()) + Expect(mds).To(HaveKey(invalidFile[1:])) + + m := mds[invalidFile[1:]] + Expect(m.Tags).To(BeEmpty()) + Expect(m.HasPicture).To(BeFalse()) + Expect(m.AudioProperties).To(Equal(metadata.AudioProperties{})) + }) + }) + }) Describe("Error Checking", func() { diff --git a/conf/configuration.go b/conf/configuration.go index 6fff1641a80..2fd97c4b226 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -163,6 +163,7 @@ type scannerOptions struct { GroupAlbumReleases bool // Deprecated: Use PID.Album instead FollowSymlinks bool // Whether to follow symlinks when scanning directories PurgeMissing string // Values: "never", "always", "full" + ImportOnReadError bool } type subsonicOptions struct { @@ -815,6 +816,7 @@ func setViperDefaults() { viper.SetDefault("scanner.groupalbumreleases", false) viper.SetDefault("scanner.followsymlinks", true) viper.SetDefault("scanner.purgemissing", consts.PurgeMissingNever) + viper.SetDefault("scanner.importonreaderror", false) viper.SetDefault("subsonic.appendsubtitle", true) viper.SetDefault("subsonic.appendalbumversion", true) viper.SetDefault("subsonic.artistparticipations", false)