diff --git a/internal/service/scanner.go b/internal/service/scanner.go index f2d2dbe..cca087b 100644 --- a/internal/service/scanner.go +++ b/internal/service/scanner.go @@ -43,9 +43,14 @@ var videoExtensions = map[string]struct{}{ ".3gp": {}, ".mpg": {}, ".mpeg": {}, + ".iso": {}, ".strm": {}, } +func mediaExtensionSupportsProbe(ext string) bool { + return ext != ".strm" && ext != ".iso" +} + // ScannerService walks libraries on disk and upserts model.Media rows. type ScannerService struct { cfg *config.Config diff --git a/internal/service/scanner_cloud_ingest.go b/internal/service/scanner_cloud_ingest.go index 29f0191..3acba06 100644 --- a/internal/service/scanner_cloud_ingest.go +++ b/internal/service/scanner_cloud_ingest.go @@ -76,7 +76,7 @@ func (s *ScannerService) ingestCloudFile(ctx context.Context, lib *model.Library isNewMedia = !exists needsTrackProbe = !exists || cloudTrackMetadataMissing(existing) if exists && existing.LibraryID == lib.ID && existing.SizeBytes == size && existing.STRMURL == expectedSTRMURL && !cloudMetadataNeedsRefresh(existing, localMeta) && !cloudDerivedMetadataNeedsRefresh(existing, m) { - if needsTrackProbe && ext != ".strm" { + if needsTrackProbe && mediaExtensionSupportsProbe(ext) { s.queueCloudMediaProbeWithBudget(typ, ref, path, probeBudget) } res.Skipped++ @@ -90,7 +90,7 @@ func (s *ScannerService) ingestCloudFile(ctx context.Context, lib *model.Library } if isNewMedia && writeBatch != nil { var after func() - if needsTrackProbe && ext != ".strm" { + if needsTrackProbe && mediaExtensionSupportsProbe(ext) { after = func() { s.queueCloudMediaProbeWithBudget(typ, ref, path, probeBudget) } @@ -103,7 +103,7 @@ func (s *ScannerService) ingestCloudFile(ctx context.Context, lib *model.Library s.log.Warn("upsert cloud media failed", zap.String("path", path), zap.Error(err)) return } - if needsTrackProbe && ext != ".strm" { + if needsTrackProbe && mediaExtensionSupportsProbe(ext) { s.queueCloudMediaProbeWithBudget(typ, ref, path, probeBudget) } if isNewMedia { diff --git a/internal/service/scanner_incremental_test.go b/internal/service/scanner_incremental_test.go index 37a1a91..4b8b578 100644 --- a/internal/service/scanner_incremental_test.go +++ b/internal/service/scanner_incremental_test.go @@ -63,6 +63,34 @@ func TestIngestPathAddsSingleFile(t *testing.T) { } } +func TestScanLibraryImportsISOImage(t *testing.T) { + sc, repos := newScannerTestEnv(t) + root := t.TempDir() + lib := model.Library{Name: "Movies", Path: root, Type: "movie", Enabled: true} + if err := repos.Library.Create(t.Context(), &lib); err != nil { + t.Fatal(err) + } + isoPath := filepath.Join(root, "Movie.Name.2026.iso") + if err := os.WriteFile(isoPath, []byte("iso image"), 0o644); err != nil { + t.Fatal(err) + } + + res, err := sc.ScanLibrary(t.Context(), lib.ID) + if err != nil { + t.Fatalf("scan: %v", err) + } + if res.Added != 1 || res.ErrorCount != 0 { + t.Fatalf("scan result = %#v, want one ISO image added without errors", res) + } + var media model.Media + if err := repos.DB.First(&media).Error; err != nil { + t.Fatal(err) + } + if media.Path != isoPath || media.Container != "iso" { + t.Fatalf("ISO media = %#v, want path %q and container iso", media, isoPath) + } +} + func TestScanLibraryReturnsNotFoundForMissingLibrary(t *testing.T) { sc, _ := newScannerTestEnv(t) diff --git a/internal/service/scanner_local_ingest.go b/internal/service/scanner_local_ingest.go index 1b2510a..faa0d1d 100644 --- a/internal/service/scanner_local_ingest.go +++ b/internal/service/scanner_local_ingest.go @@ -169,7 +169,7 @@ func (s *ScannerService) buildLocalScanMedia(in localScanMediaInput) *model.Medi } func (s *ScannerService) localProbeAfter(path, ext string) func() { - if ext == ".strm" || s.probe == nil { + if !mediaExtensionSupportsProbe(ext) || s.probe == nil { return nil } return func() { diff --git a/internal/service/strm_tree_import_test.go b/internal/service/strm_tree_import_test.go index 5125ae7..baf4f07 100644 --- a/internal/service/strm_tree_import_test.go +++ b/internal/service/strm_tree_import_test.go @@ -93,8 +93,8 @@ func TestGenerateSTRMFromTreeSupportsCommonVideoExtensions(t *testing.T) { if err != nil { t.Fatal(err) } - if res.Generated != 5 || len(res.Errors) != 0 { - t.Fatalf("result = %#v, want five common video sources generated and iso ignored", res) + if res.Generated != 6 || len(res.Errors) != 0 { + t.Fatalf("result = %#v, want six common video sources generated", res) } for _, name := range []string{ "BluRay.Stream.2026", @@ -102,15 +102,13 @@ func TestGenerateSTRMFromTreeSupportsCommonVideoExtensions(t *testing.T) { "DVD.Feature.2026", "Legacy.Video.2026", "Web.Legacy.2026", + "Disc.Image.2026", } { got := readSTRM(t, filepath.Join(outDir, "Movies", name+".strm")) if !strings.Contains(got, "/api/cloud/play/openlist?") { t.Fatalf("%s strm url = %q, want cloud play url", name, got) } } - if _, err := os.Stat(filepath.Join(outDir, "Movies", "Disc.Image.2026.strm")); !os.IsNotExist(err) { - t.Fatalf("iso source should stay ignored by tree generator, stat err=%v", err) - } } func TestGenerateSTRMFromTreeReportsIgnoredFileLikeRows(t *testing.T) { @@ -138,8 +136,8 @@ func TestGenerateSTRMFromTreeReportsIgnoredFileLikeRows(t *testing.T) { if err != nil { t.Fatal(err) } - if res.Generated != 2 || res.Ignored != 6 || len(res.IgnoredItems) != 6 { - t.Fatalf("result = %#v, want two generated videos and six ignored sidecars", res) + if res.Generated != 3 || res.Ignored != 5 || len(res.IgnoredItems) != 5 { + t.Fatalf("result = %#v, want three generated videos and five ignored sidecars", res) } for _, item := range res.IgnoredItems { if item == "Show.Name.2026" || item == "电视剧/Show.Name.2026" { diff --git a/internal/service/strm_tree_normalize.go b/internal/service/strm_tree_normalize.go index 97af2cb..2702ae8 100644 --- a/internal/service/strm_tree_normalize.go +++ b/internal/service/strm_tree_normalize.go @@ -16,7 +16,6 @@ var strmTreeKnownNonVideoExtensions = map[string]struct{}{ ".bmp": {}, ".gif": {}, ".idx": {}, - ".iso": {}, ".jpeg": {}, ".jpg": {}, ".nfo": {},