From 1837fb7c564d27799fe67a0aa5fd74b7ddc3d04a Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:18:43 -0600 Subject: [PATCH] clean up old show artwork; properly update season artwork --- CHANGELOG.md | 2 +- .../Data/Repositories/TelevisionRepository.cs | 34 +++++----- .../Core/Metadata/LocalMetadataProvider.cs | 9 +-- .../Core/Metadata/TelevisionFolderScanner.cs | 66 +++++++++++++------ 4 files changed, 69 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d31dd524..8e9dd75c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fix 500 errors when serving XMLTV due to concurrent file reads and writes - Fix playback of AC3 audio when targeting stereo output and input layout changes mid-stream - Use other video artwork in XMLTV template -- Remove artwork from other videos when artwork has been removed from disk +- Properly update (add or remove) artwork for all local media libraries when files have changed ## [26.1.1] - 2026-01-08 ### Fixed diff --git a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs index 1f5686b1c..b788851bc 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs @@ -271,17 +271,18 @@ public class TelevisionRepository : ITelevisionRepository try { metadata.DateAdded = DateTime.UtcNow; - metadata.Genres ??= new List(); - metadata.Tags ??= new List(); - metadata.Studios ??= new List(); - metadata.Actors ??= new List(); - metadata.Guids ??= new List(); + metadata.Genres ??= []; + metadata.Tags ??= []; + metadata.Studios ??= []; + metadata.Actors ??= []; + metadata.Guids ??= []; + metadata.Artwork ??= []; var show = new Show { LibraryPathId = libraryPathId, - ShowMetadata = new List { metadata }, - Seasons = new List(), - TraktListItems = new List() + ShowMetadata = [metadata], + Seasons = [], + TraktListItems = [] }; await dbContext.Shows.AddAsync(show); @@ -670,17 +671,18 @@ public class TelevisionRepository : ITelevisionRepository LibraryPathId = libraryPathId, ShowId = show.Id, SeasonNumber = seasonNumber, - Episodes = new List(), - SeasonMetadata = new List - { - new() + Episodes = [], + SeasonMetadata = + [ + new SeasonMetadata { DateAdded = DateTime.UtcNow, - Guids = new List(), - Tags = new List() + Guids = [], + Tags = [], + Artwork = [] } - }, - TraktListItems = new List() + ], + TraktListItems = [] }; await dbContext.Seasons.AddAsync(season); await dbContext.SaveChangesAsync(); diff --git a/ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs b/ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs index 34e46a7b1..8d5d2add0 100644 --- a/ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs +++ b/ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs @@ -563,7 +563,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider { var updated = false; - episode.EpisodeMetadata ??= new List(); + episode.EpisodeMetadata ??= []; var toUpdate = episode.EpisodeMetadata .Where(em => episodeMetadata.Any(em2 => em2.EpisodeNumber == em.EpisodeNumber)) @@ -1317,7 +1317,8 @@ public class LocalMetadataProvider : ILocalMetadataProvider Actors = Actors(nfo.Actors, dateAdded, dateUpdated), Guids = nfo.UniqueIds .Map(id => new MetadataGuid { Guid = $"{id.Type}://{id.Guid}" }) - .ToList() + .ToList(), + Artwork = [] }; } @@ -1409,8 +1410,8 @@ public class LocalMetadataProvider : ILocalMetadataProvider Writers = nfo.Writers.Map(w => new Writer { Name = w }).ToList(), Genres = nfo.Genres.Map(g => new Genre { Name = g }).ToList(), Tags = nfo.Tags.Map(t => new Tag { Name = t }).ToList(), - Studios = new List(), - Artwork = new List() + Studios = [], + Artwork = [] }; result.Add(metadata); diff --git a/ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs index 7bf9e73df..2ed9e5932 100644 --- a/ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs @@ -248,17 +248,15 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan maybeParentFolder, seasonFolder); - // skip folder if etag matches - if (knownFolder.Etag == etag) + // cache etag match for later checking + // we still need to scan the season folder in case season artwork has changed + bool etagMatch = knownFolder.Etag == etag; + if (etagMatch) { if (allTrashedItems.Any(f => f.StartsWith(seasonFolder, StringComparison.OrdinalIgnoreCase))) { _logger.LogDebug("Previously trashed items are now present in folder {Folder}", seasonFolder); - } - else - { - // etag matches and no trashed items are now present, continue to next folder - continue; + etagMatch = false; } } @@ -280,6 +278,12 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan foreach (Season season in maybeSeason.RightToSeq()) { + // skip scanning episodes when season folder etag matches + if (etagMatch) + { + continue; + } + Either scanResult = await ScanEpisodes( libraryPath, knownFolder, @@ -410,7 +414,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan private async Task> EnsureMetadataExists(Season season) { - season.SeasonMetadata ??= new List(); + season.SeasonMetadata ??= []; if (season.SeasonMetadata.Count == 0) { @@ -419,8 +423,9 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan SeasonId = season.Id, Season = season, DateAdded = DateTime.UtcNow, - Guids = new List(), - Tags = new List() + Guids = [], + Tags = [], + Artwork = [] }; season.SeasonMetadata.Add(metadata); @@ -492,11 +497,18 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan try { Show show = result.Item; - Option maybeArtwork = LocateArtworkForShow(showFolder, artworkKind); - foreach (string artworkFile in maybeArtwork) + foreach (ShowMetadata metadata in show.ShowMetadata.HeadOrNone()) { - ShowMetadata metadata = show.ShowMetadata.Head(); - await RefreshArtwork(artworkFile, metadata, artworkKind, None, None, cancellationToken); + Option maybeArtwork = LocateArtworkForShow(showFolder, artworkKind); + foreach (string artworkFile in maybeArtwork) + { + await RefreshArtwork(artworkFile, metadata, artworkKind, None, None, cancellationToken); + } + + if (maybeArtwork.IsNone && metadata.Artwork.Any(a => a.ArtworkKind == artworkKind)) + { + await _metadataRepository.RemoveArtworkWithKind(metadata, artworkKind); + } } return result; @@ -515,11 +527,18 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan { try { - Option maybePoster = LocatePoster(season, seasonFolder); - foreach (string posterFile in maybePoster) + foreach (SeasonMetadata metadata in season.SeasonMetadata.HeadOrNone()) { - SeasonMetadata metadata = season.SeasonMetadata.Head(); - await RefreshArtwork(posterFile, metadata, ArtworkKind.Poster, None, None, cancellationToken); + Option maybePoster = LocatePoster(season, seasonFolder); + foreach (string posterFile in maybePoster) + { + await RefreshArtwork(posterFile, metadata, ArtworkKind.Poster, None, None, cancellationToken); + } + + if (maybePoster.IsNone && metadata.Artwork.Any(a => a.ArtworkKind is ArtworkKind.Poster)) + { + await _metadataRepository.RemoveArtworkWithKind(metadata, ArtworkKind.Poster); + } } return season; @@ -535,10 +554,10 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan { try { - Option maybeThumbnail = LocateThumbnail(episode); - foreach (string thumbnailFile in maybeThumbnail) + foreach (EpisodeMetadata metadata in episode.EpisodeMetadata) { - foreach (EpisodeMetadata metadata in episode.EpisodeMetadata) + Option maybeThumbnail = LocateThumbnail(episode); + foreach (string thumbnailFile in maybeThumbnail) { await RefreshArtwork( thumbnailFile, @@ -548,6 +567,11 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan None, cancellationToken); } + + if (maybeThumbnail.IsNone && metadata.Artwork.Any(a => a.ArtworkKind is ArtworkKind.Thumbnail)) + { + await _metadataRepository.RemoveArtworkWithKind(metadata, ArtworkKind.Thumbnail); + } } return episode;