From 0dd9f8399f097a9b0eea89979cea67cb70f22589 Mon Sep 17 00:00:00 2001 From: ShukeBta <272197458+ShukeBta@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:33:31 +0800 Subject: [PATCH] Support metadata scraping for ISO disc images --- internal/service/scanner_cloud_ingest.go | 1 + internal/service/scanner_incremental_test.go | 32 ++++++++++++++++ internal/service/scanner_iso.go | 40 ++++++++++++++++++++ internal/service/scanner_local_ingest.go | 1 + 4 files changed, 74 insertions(+) create mode 100644 internal/service/scanner_iso.go diff --git a/internal/service/scanner_cloud_ingest.go b/internal/service/scanner_cloud_ingest.go index 3acba06..34bb7b2 100644 --- a/internal/service/scanner_cloud_ingest.go +++ b/internal/service/scanner_cloud_ingest.go @@ -20,6 +20,7 @@ func (s *ScannerService) ingestCloudFile(ctx context.Context, lib *model.Library if title == "" { title = ref } + title, year = preferISOParentScrapeIdentity(path, lib.Path, title, year) parsedSeason, parsedEpisode := ParseEpisode(path) if librarySupportsSeasons(lib) || parsedSeason > 0 || parsedEpisode > 0 { if seriesTitle, seriesYear := cloudSeriesTitleFromMediaPath(path); seriesTitle != "" { diff --git a/internal/service/scanner_incremental_test.go b/internal/service/scanner_incremental_test.go index 4b8b578..3b50a89 100644 --- a/internal/service/scanner_incremental_test.go +++ b/internal/service/scanner_incremental_test.go @@ -91,6 +91,38 @@ func TestScanLibraryImportsISOImage(t *testing.T) { } } +func TestScanLibraryUsesISOParentFolderForScrapeIdentity(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) + } + movieDir := filepath.Join(root, "Dune.Part.Two.2024") + if err := os.MkdirAll(movieDir, 0o755); err != nil { + t.Fatal(err) + } + isoPath := filepath.Join(movieDir, "BDMV.iso") + if err := os.WriteFile(isoPath, []byte("iso image"), 0o644); err != nil { + t.Fatal(err) + } + + if _, err := sc.ScanLibrary(t.Context(), lib.ID); err != nil { + t.Fatal(err) + } + var media model.Media + if err := repos.DB.First(&media).Error; err != nil { + t.Fatal(err) + } + if media.Title != "dune part two" || media.Year != 2024 || media.ScrapeStatus != "pending" { + t.Fatalf("ISO scrape identity = title=%q year=%d status=%q", media.Title, media.Year, media.ScrapeStatus) + } + candidates := scrapeQueryCandidates(&media, &lib) + if len(candidates) == 0 || candidates[0] != "dune part two" { + t.Fatalf("ISO scrape candidates = %#v", candidates) + } +} + func TestScanLibraryReturnsNotFoundForMissingLibrary(t *testing.T) { sc, _ := newScannerTestEnv(t) diff --git a/internal/service/scanner_iso.go b/internal/service/scanner_iso.go new file mode 100644 index 0000000..b88fdf9 --- /dev/null +++ b/internal/service/scanner_iso.go @@ -0,0 +1,40 @@ +package service + +import ( + "path/filepath" + "strings" +) + +func preferISOParentScrapeIdentity(mediaPath, libraryRoot, title string, year int) (string, int) { + if !strings.EqualFold(filepath.Ext(pathBaseSlash(mediaPath)), ".iso") || !genericISOImageTitle(title) { + return title, year + } + parent := pathBaseSlash(parentSlashPath(mediaPath)) + if parent == "" || isGenericMediaCategoryFolder(parent) || isTechnicalMediaFolder(parent) { + return title, year + } + // CleanQuery expects a filename and would otherwise treat a trailing + // ".2024" folder suffix as a file extension. + parentTitle, parentYear := CleanQuery(parent + ".mkv") + if parentTitle == "" { + parentTitle = mediaFolderTitle(mediaPath, libraryRoot) + } + if parentTitle != "" { + title = parentTitle + } + if parentYear > 0 { + year = parentYear + } + return title, year +} + +func genericISOImageTitle(title string) bool { + key := strings.ToLower(strings.TrimSpace(title)) + key = strings.NewReplacer(" ", "", "_", "", "-", "", ".", "").Replace(key) + switch key { + case "bdmv", "bluray", "bluraydisc", "disc", "disk", "dvd", "movie", "video", "videots", "image", "iso": + return true + default: + return false + } +} diff --git a/internal/service/scanner_local_ingest.go b/internal/service/scanner_local_ingest.go index faa0d1d..d649d66 100644 --- a/internal/service/scanner_local_ingest.go +++ b/internal/service/scanner_local_ingest.go @@ -140,6 +140,7 @@ func (s *ScannerService) buildLocalScanMedia(in localScanMediaInput) *model.Medi if title == "" { title = strings.TrimSuffix(filepath.Base(in.path), in.ext) } + title, year = preferISOParentScrapeIdentity(in.path, in.lib.Path, title, year) media := &model.Media{ LibraryID: in.lib.ID,