From 5f3b392168d84efd8f1e947c0b824386beea60d1 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 10 May 2026 11:51:43 -0400 Subject: [PATCH] fix(scanner): wire all metadata fields in updateMediaItem and skip image dupes updateMediaItem (used by force rescan) was missing 22 fields including Language, Genre, PageCount, CopyrightYear, GoodreadsID, and all 14 new columns from the SQL query fix. Now wires all 37 UpdateMediaItemParams. Also adds hasSiblingBookFile() early exit in processMediaFile: if a file is an image (jpg/png/webp/etc) and its directory contains an actual book file (epub/pdf/cbz/etc), skip importing the image as a standalone media item. This prevents cover images and interior art from appearing as duplicate library entries. --- internal/services/media_scanner.go | 52 ++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/internal/services/media_scanner.go b/internal/services/media_scanner.go index 17e5354..dd8cf73 100644 --- a/internal/services/media_scanner.go +++ b/internal/services/media_scanner.go @@ -535,6 +535,26 @@ func (s *MediaScanner) extractFolderStructureMetadata(path, rootFolder string) * return metadata } +var bookExtensions = map[string]bool{ + ".epub": true, ".pdf": true, ".mobi": true, ".azw": true, ".azw3": true, + ".fb2": true, ".txt": true, ".rtf": true, ".doc": true, ".docx": true, + ".lit": true, ".pdb": true, ".djvu": true, + ".cbz": true, ".cbr": true, ".cb7": true, ".cbt": true, +} + +func hasSiblingBookFile(dir string) bool { + entries, err := os.ReadDir(dir) + if err != nil { + return false + } + for _, entry := range entries { + if !entry.IsDir() && bookExtensions[strings.ToLower(filepath.Ext(entry.Name()))] { + return true + } + } + return false +} + func (s *MediaScanner) processMediaFile(ctx context.Context, path string) (bool, error) { fmt.Printf("Processing media file: %s\n", path) @@ -545,9 +565,10 @@ func (s *MediaScanner) processMediaFile(ctx context.Context, path string) (bool, return false, fmt.Errorf("failed to get file info: %v", err) } - fmt.Printf("File info for %s: size=%d\n", path, info.Size()) + if isImageFile(path) && hasSiblingBookFile(filepath.Dir(path)) { + return false, nil + } - // Get file modification time for created_at fileModTime := info.ModTime() // Find library for this file's folder @@ -2338,7 +2359,11 @@ func (t *tarFileAdapter) Open() (io.ReadCloser, error) { // isImageFile checks if a file is an image based on extension func isImageFile(filename string) bool { ext := strings.ToLower(filepath.Ext(filename)) - return ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif" || ext == ".webp" + switch ext { + case ".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".avif", ".tiff", ".tif": + return true + } + return false } // countArchiveImages counts image files in a comic archive @@ -2426,6 +2451,10 @@ func (s *MediaScanner) updateMediaItem(ctx context.Context, mediaItemID pgtype.U tagsSearch := utils.NormalizeTagsSearch(metadata.Tags) // Call the database update - only update fields available in MediaMetadata + var alternateInfoBytes []byte + if metadata.AlternateInfo != "" { + alternateInfoBytes = []byte(metadata.AlternateInfo) + } _, err = s.db.UpdateMediaItem(ctx, database.UpdateMediaItemParams{ ID: mediaItemID, Title: metadata.Title, @@ -2442,6 +2471,23 @@ func (s *MediaScanner) updateMediaItem(ctx context.Context, mediaItemID pgtype.U Publisher: pgtype.Text{String: metadata.Publisher, Valid: metadata.Publisher != ""}, Contributors: metadata.Contributors, ContributorsSearch: contributorsSearch, + Language: pgtype.Text{String: metadata.Language, Valid: metadata.Language != ""}, + Genre: pgtype.Text{String: metadata.Genre, Valid: metadata.Genre != ""}, + PageCount: pgtype.Int4{Int32: metadata.PageCount, Valid: metadata.PageCount > 0}, + MangaType: pgtype.Text{String: metadata.MangaType, Valid: metadata.MangaType != ""}, + ReadingDirection: pgtype.Text{String: metadata.ReadingDirection, Valid: metadata.ReadingDirection != ""}, + SeriesCount: pgtype.Int4{Int32: metadata.SeriesCount, Valid: metadata.SeriesCount > 0}, + Volume: pgtype.Int4{Int32: metadata.Volume, Valid: metadata.Volume > 0}, + Imprint: pgtype.Text{String: metadata.Imprint, Valid: metadata.Imprint != ""}, + AgeRating: pgtype.Text{String: metadata.AgeRating, Valid: metadata.AgeRating != ""}, + WebUrl: pgtype.Text{String: metadata.WebURL, Valid: metadata.WebURL != ""}, + MetadataNotes: pgtype.Text{String: metadata.MetadataNotes, Valid: metadata.MetadataNotes != ""}, + CommunityRating: pgtype.Float8{Float64: metadata.CommunityRating, Valid: metadata.CommunityRating > 0}, + StoryArc: pgtype.Text{String: metadata.StoryArc, Valid: metadata.StoryArc != ""}, + IsBlackAndWhite: pgtype.Bool{Bool: metadata.IsBlackAndWhite, Valid: metadata.IsBlackAndWhite}, + AlternateInfo: alternateInfoBytes, + ScanInformation: pgtype.Text{String: metadata.ScanInformation, Valid: metadata.ScanInformation != ""}, + Summary: pgtype.Text{String: metadata.Summary, Valid: metadata.Summary != ""}, }) return err }