Browse Source

local scanner artwork cleanup (#2778)

* move plex artwork removal to its own repository

* clean up old local movie artwork

* clien up old music video/artist artwork

* clean up old remote stream artwork

* clean up old song artwork

* clean up old show artwork; properly update season artwork
pull/2779/head
Jason Dove 7 months ago committed by GitHub
parent
commit
a2d023ee69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 2
      ErsatzTV.Core/Interfaces/Repositories/IMetadataRepository.cs
  3. 1
      ErsatzTV.Core/Interfaces/Repositories/IOtherVideoRepository.cs
  4. 8
      ErsatzTV.Core/Interfaces/Repositories/IPlexMetadataRepository.cs
  5. 108
      ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs
  6. 11
      ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs
  7. 9
      ErsatzTV.Infrastructure/Data/Repositories/OtherVideoRepository.cs
  8. 19
      ErsatzTV.Infrastructure/Data/Repositories/PlexMetadataRepository.cs
  9. 26
      ErsatzTV.Infrastructure/Data/Repositories/RemoteStreamRepository.cs
  10. 34
      ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs
  11. 17
      ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs
  12. 11
      ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs
  13. 27
      ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs
  14. 6
      ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs
  15. 11
      ErsatzTV.Scanner/Core/Metadata/RemoteStreamFolderScanner.cs
  16. 23
      ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs
  17. 52
      ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs
  18. 9
      ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs
  19. 9
      ErsatzTV.Scanner/Core/Plex/PlexOtherVideoLibraryScanner.cs
  20. 9
      ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs
  21. 1
      ErsatzTV.Scanner/Program.cs
  22. 1
      ErsatzTV/Startup.cs

2
CHANGELOG.md

@ -25,7 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -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

2
ErsatzTV.Core/Interfaces/Repositories/IMetadataRepository.cs

@ -16,7 +16,7 @@ public interface IMetadataRepository @@ -16,7 +16,7 @@ public interface IMetadataRepository
Task<bool> UpdateStatistics(MediaItem mediaItem, MediaVersion incoming, bool updateVersion = true);
Task<Unit> UpdateArtworkPath(Artwork artwork);
Task<Unit> AddArtwork(Domain.Metadata metadata, Artwork artwork);
Task<Unit> RemoveArtwork(Domain.Metadata metadata, ArtworkKind artworkKind);
Task<bool> RemoveArtworkWithKind(Domain.Metadata metadata, ArtworkKind artworkKind);
Task<bool> CloneArtwork(
Domain.Metadata metadata,

1
ErsatzTV.Core/Interfaces/Repositories/IOtherVideoRepository.cs

@ -19,5 +19,4 @@ public interface IOtherVideoRepository @@ -19,5 +19,4 @@ public interface IOtherVideoRepository
Task<bool> AddActor(OtherVideoMetadata metadata, Actor actor);
Task<bool> AddDirector(OtherVideoMetadata metadata, Director director);
Task<bool> AddWriter(OtherVideoMetadata metadata, Writer writer);
Task<bool> RemoveArtwork(OtherVideoMetadata metadata, ArtworkKind artworkKind);
}

8
ErsatzTV.Core/Interfaces/Repositories/IPlexMetadataRepository.cs

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Core.Interfaces.Repositories;
public interface IPlexMetadataRepository
{
Task<Unit> RemoveArtwork(Domain.Metadata metadata, ArtworkKind artworkKind);
}

108
ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs

@ -44,10 +44,11 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -44,10 +44,11 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
{
MetadataKind = MetadataKind.Fallback,
Title = fileName ?? showFolder,
Genres = new List<Genre>(),
Tags = new List<Tag>(),
Studios = new List<Studio>(),
Actors = new List<Actor>()
Genres = [],
Tags = [],
Studios = [],
Actors = [],
Artwork = []
};
return GetTelevisionShowMetadata(fileName, metadata);
}
@ -56,7 +57,11 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -56,7 +57,11 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
{
string fileName = Path.GetFileName(artistFolder);
return new ArtistMetadata
{ MetadataKind = MetadataKind.Fallback, Title = fileName ?? artistFolder };
{
MetadataKind = MetadataKind.Fallback,
Title = fileName ?? artistFolder,
Artwork = []
};
}
public List<EpisodeMetadata> GetFallbackMetadata(Episode episode)
@ -69,18 +74,18 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -69,18 +74,18 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
Title = Path.GetFileNameWithoutExtension(path) ?? path,
DateAdded = DateTime.UtcNow,
EpisodeNumber = 0,
Actors = new List<Actor>(),
Artwork = new List<Artwork>(),
Directors = new List<Director>(),
Genres = new List<Genre>(),
Guids = new List<MetadataGuid>(),
Studios = new List<Studio>(),
Tags = new List<Tag>(),
Writers = new List<Writer>()
Actors = [],
Artwork = [],
Directors = [],
Genres = [],
Guids = [],
Studios = [],
Tags = [],
Writers = []
};
return fileName != null
? GetEpisodeMetadata(fileName, baseMetadata)
: new List<EpisodeMetadata> { baseMetadata };
: [baseMetadata];
}
public MovieMetadata GetFallbackMetadata(Movie movie)
@ -91,13 +96,14 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -91,13 +96,14 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
{
MetadataKind = MetadataKind.Fallback,
Title = Path.GetFileNameWithoutExtension(path) ?? path,
Genres = new List<Genre>(),
Tags = new List<Tag>(),
Studios = new List<Studio>(),
Actors = new List<Actor>(),
Directors = new List<Director>(),
Writers = new List<Writer>(),
Guids = new List<MetadataGuid>()
Artwork = [],
Genres = [],
Tags = [],
Studios = [],
Actors = [],
Directors = [],
Writers = [],
Guids = []
};
return fileName != null ? GetMovieMetadata(fileName, metadata) : metadata;
@ -110,7 +116,8 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -110,7 +116,8 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
var metadata = new MusicVideoMetadata
{
MetadataKind = MetadataKind.Fallback,
Title = fileName ?? path
Title = fileName ?? path,
Artwork = []
};
return GetMusicVideoMetadata(fileName, metadata);
@ -125,13 +132,14 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -125,13 +132,14 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
MetadataKind = MetadataKind.Fallback,
Title = fileName ?? path,
OtherVideo = otherVideo,
Genres = new List<Genre>(),
Tags = new List<Tag>(),
Studios = new List<Studio>(),
Actors = new List<Actor>(),
Directors = new List<Director>(),
Writers = new List<Writer>(),
Guids = new List<MetadataGuid>()
Artwork = [],
Genres = [],
Tags = [],
Studios = [],
Actors = [],
Directors = [],
Writers = [],
Guids = []
};
return GetOtherVideoMetadata(path, metadata);
@ -145,7 +153,8 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -145,7 +153,8 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
{
MetadataKind = MetadataKind.Fallback,
Title = fileName ?? path,
Song = song
Song = song,
Artwork = []
};
return GetSongMetadata(path, metadata);
@ -160,6 +169,7 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -160,6 +169,7 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
MetadataKind = MetadataKind.Fallback,
Title = fileName ?? path,
Image = image,
Artwork = [],
Genres = [],
Tags = [],
Studios = [],
@ -179,6 +189,7 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -179,6 +189,7 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
MetadataKind = MetadataKind.Fallback,
Title = fileName ?? path,
RemoteStream = remoteStream,
Artwork = [],
Genres = [],
Tags = [],
Studios = [],
@ -232,14 +243,14 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -232,14 +243,14 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
DateAdded = baseMetadata.DateAdded,
DateUpdated = baseMetadata.DateAdded,
Title = baseMetadata.Title,
Actors = new List<Actor>(),
Artwork = new List<Artwork>(),
Directors = new List<Director>(),
Genres = new List<Genre>(),
Guids = new List<MetadataGuid>(),
Studios = new List<Studio>(),
Tags = new List<Tag>(),
Writers = new List<Writer>()
Actors = [],
Artwork = [],
Directors = [],
Genres = [],
Guids = [],
Studios = [],
Tags = [],
Writers = []
};
result.Add(metadata);
@ -290,6 +301,7 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -290,6 +301,7 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
? match.Groups[2].Value.Trim()
: Path.GetFileNameWithoutExtension(fileName);
metadata.Artists = [];
metadata.Artwork = [];
metadata.Genres = [];
metadata.Tags = [];
metadata.Studios = [];
@ -326,11 +338,11 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -326,11 +338,11 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
.Map(t => new Tag { Name = t })
.ToList();
metadata.Artwork = new List<Artwork>();
metadata.Actors = new List<Actor>();
metadata.Genres = new List<Genre>();
metadata.Actors = [];
metadata.Artwork = [];
metadata.Genres = [];
metadata.Tags = tags;
metadata.Studios = new List<Studio>();
metadata.Studios = [];
metadata.DateUpdated = DateTime.UtcNow;
metadata.OriginalTitle = Path.GetRelativePath(libraryPath, path);
@ -364,8 +376,8 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -364,8 +376,8 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
.Map(t => new Tag { Name = t })
.ToList();
metadata.Artwork = [];
metadata.Actors = [];
metadata.Artwork = [];
metadata.Genres = [];
metadata.Tags = tags;
metadata.Studios = [];
@ -402,8 +414,8 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -402,8 +414,8 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
.Map(t => new Tag { Name = t })
.ToList();
metadata.Artwork = [];
metadata.Actors = [];
metadata.Artwork = [];
metadata.Genres = [];
metadata.Tags = tags;
metadata.Studios = [];
@ -440,11 +452,11 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat @@ -440,11 +452,11 @@ public partial class FallbackMetadataProvider(IClient client) : IFallbackMetadat
.Map(t => new Tag { Name = t })
.ToList();
metadata.Artwork = new List<Artwork>();
metadata.Actors = new List<Actor>();
metadata.Genres = new List<Genre>();
metadata.Actors = [];
metadata.Artwork = [];
metadata.Genres = [];
metadata.Tags = tags;
metadata.Studios = new List<Studio>();
metadata.Studios = [];
metadata.DateUpdated = DateTime.UtcNow;
metadata.OriginalTitle = Path.GetRelativePath(libraryPath, path);

11
ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs

@ -309,14 +309,13 @@ public class MetadataRepository(IDbContextFactory<TvContext> dbContextFactory) : @@ -309,14 +309,13 @@ public class MetadataRepository(IDbContextFactory<TvContext> dbContextFactory) :
};
}
public async Task<Unit> RemoveArtwork(Core.Domain.Metadata metadata, ArtworkKind artworkKind)
public async Task<bool> RemoveArtworkWithKind(Core.Domain.Metadata metadata, ArtworkKind artworkKind)
{
// this is only used by plex, so only needs to support plex media kinds (movie, show, season, episode, other video)
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
return await dbContext.Connection.ExecuteAsync(
@"DELETE FROM Artwork WHERE ArtworkKind = @ArtworkKind AND (MovieMetadataId = @Id
OR ShowMetadataId = @Id OR SeasonMetadataId = @Id OR EpisodeMetadataId = @Id OR OtherVideoMetadataId = @Id)",
new { ArtworkKind = artworkKind, metadata.Id }).ToUnit();
var ids = metadata.Artwork.Where(a => a.ArtworkKind == artworkKind).Select(a => a.Id).ToHashSet();
return await dbContext.Artwork
.Where(a => ids.Contains(a.Id))
.ExecuteDeleteAsync() > 0;
}
public async Task<bool> CloneArtwork(

9
ErsatzTV.Infrastructure/Data/Repositories/OtherVideoRepository.cs

@ -175,15 +175,6 @@ public class OtherVideoRepository : IOtherVideoRepository @@ -175,15 +175,6 @@ public class OtherVideoRepository : IOtherVideoRepository
new { writer.Name, MetadataId = metadata.Id }).Map(result => result > 0);
}
public async Task<bool> RemoveArtwork(OtherVideoMetadata metadata, ArtworkKind artworkKind)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
var ids = metadata.Artwork.Where(a => a.ArtworkKind == artworkKind).Select(a => a.Id).ToHashSet();
return await dbContext.Artwork
.Where(a => ids.Contains(a.Id))
.ExecuteDeleteAsync() > 0;
}
private async Task<Either<BaseError, MediaItemScanResult<OtherVideo>>> AddOtherVideo(
TvContext dbContext,
int libraryPathId,

19
ErsatzTV.Infrastructure/Data/Repositories/PlexMetadataRepository.cs

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
using Dapper;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Infrastructure.Data.Repositories;
public class PlexMetadataRepository(IDbContextFactory<TvContext> dbContextFactory) : IPlexMetadataRepository
{
public async Task<Unit> RemoveArtwork(Core.Domain.Metadata metadata, ArtworkKind artworkKind)
{
// this is only used by plex, so only needs to support plex media kinds (movie, show, season, episode, other video)
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
return await dbContext.Connection.ExecuteAsync(
@"DELETE FROM Artwork WHERE ArtworkKind = @ArtworkKind AND (MovieMetadataId = @Id
OR ShowMetadataId = @Id OR SeasonMetadataId = @Id OR EpisodeMetadataId = @Id OR OtherVideoMetadataId = @Id)",
new { ArtworkKind = artworkKind, metadata.Id }).ToUnit();
}
}

26
ErsatzTV.Infrastructure/Data/Repositories/RemoteStreamRepository.cs

@ -25,25 +25,27 @@ public class RemoteStreamRepository( @@ -25,25 +25,27 @@ public class RemoteStreamRepository(
Option<RemoteStream> maybeExisting = await dbContext.RemoteStreams
.AsNoTracking()
.Include(i => i.RemoteStreamMetadata)
.ThenInclude(ovm => ovm.Genres)
.ThenInclude(rsm => rsm.Genres)
.Include(i => i.RemoteStreamMetadata)
.ThenInclude(ovm => ovm.Tags)
.ThenInclude(rsm => rsm.Tags)
.Include(i => i.RemoteStreamMetadata)
.ThenInclude(ovm => ovm.Studios)
.ThenInclude(rsm => rsm.Studios)
.Include(i => i.RemoteStreamMetadata)
.ThenInclude(ovm => ovm.Guids)
.ThenInclude(rsm => rsm.Guids)
.Include(i => i.RemoteStreamMetadata)
.ThenInclude(ovm => ovm.Actors)
.ThenInclude(rsm => rsm.Actors)
.Include(i => i.RemoteStreamMetadata)
.ThenInclude(ovm => ovm.Actors)
.ThenInclude(rsm => rsm.Actors)
.ThenInclude(a => a.Artwork)
.Include(ov => ov.LibraryPath)
.Include(i => i.RemoteStreamMetadata)
.ThenInclude(rsm => rsm.Artwork)
.Include(rs => rs.LibraryPath)
.ThenInclude(lp => lp.Library)
.Include(ov => ov.MediaVersions)
.ThenInclude(ov => ov.MediaFiles)
.Include(ov => ov.MediaVersions)
.ThenInclude(ov => ov.Streams)
.Include(ov => ov.TraktListItems)
.Include(rs => rs.MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(rs => rs.MediaVersions)
.ThenInclude(mv => mv.Streams)
.Include(rs => rs.TraktListItems)
.ThenInclude(tli => tli.TraktList)
.OrderBy(i => i.MediaVersions.First().MediaFiles.First().Path)
.SingleOrDefaultAsync(i => i.MediaVersions.First().MediaFiles.First().Path == path, cancellationToken);

34
ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs

@ -271,17 +271,18 @@ public class TelevisionRepository : ITelevisionRepository @@ -271,17 +271,18 @@ public class TelevisionRepository : ITelevisionRepository
try
{
metadata.DateAdded = DateTime.UtcNow;
metadata.Genres ??= new List<Genre>();
metadata.Tags ??= new List<Tag>();
metadata.Studios ??= new List<Studio>();
metadata.Actors ??= new List<Actor>();
metadata.Guids ??= new List<MetadataGuid>();
metadata.Genres ??= [];
metadata.Tags ??= [];
metadata.Studios ??= [];
metadata.Actors ??= [];
metadata.Guids ??= [];
metadata.Artwork ??= [];
var show = new Show
{
LibraryPathId = libraryPathId,
ShowMetadata = new List<ShowMetadata> { metadata },
Seasons = new List<Season>(),
TraktListItems = new List<TraktListItem>()
ShowMetadata = [metadata],
Seasons = [],
TraktListItems = []
};
await dbContext.Shows.AddAsync(show);
@ -670,17 +671,18 @@ public class TelevisionRepository : ITelevisionRepository @@ -670,17 +671,18 @@ public class TelevisionRepository : ITelevisionRepository
LibraryPathId = libraryPathId,
ShowId = show.Id,
SeasonNumber = seasonNumber,
Episodes = new List<Episode>(),
SeasonMetadata = new List<SeasonMetadata>
{
new()
Episodes = [],
SeasonMetadata =
[
new SeasonMetadata
{
DateAdded = DateTime.UtcNow,
Guids = new List<MetadataGuid>(),
Tags = new List<Tag>()
Guids = [],
Tags = [],
Artwork = []
}
},
TraktListItems = new List<TraktListItem>()
],
TraktListItems = []
};
await dbContext.Seasons.AddAsync(season);
await dbContext.SaveChangesAsync();

17
ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs

@ -335,7 +335,8 @@ public class LocalMetadataProvider : ILocalMetadataProvider @@ -335,7 +335,8 @@ public class LocalMetadataProvider : ILocalMetadataProvider
Genres = nfo.Genres.Map(g => new Genre { Name = g }).ToList(),
Tags = nfo.Tags.Map(t => new Tag { Name = t }).ToList(),
Studios = nfo.Studios.Map(s => new Studio { Name = s }).ToList(),
Directors = nfo.Directors.Map(s => new Director { Name = s }).ToList()
Directors = nfo.Directors.Map(s => new Director { Name = s }).ToList(),
Artwork = []
};
}
@ -562,7 +563,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider @@ -562,7 +563,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider
{
var updated = false;
episode.EpisodeMetadata ??= new List<EpisodeMetadata>();
episode.EpisodeMetadata ??= [];
var toUpdate = episode.EpisodeMetadata
.Where(em => episodeMetadata.Any(em2 => em2.EpisodeNumber == em.EpisodeNumber))
@ -949,7 +950,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider @@ -949,7 +950,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider
? SortTitle.GetSortTitle(metadata.Title)
: metadata.SortTitle;
metadata.ArtistId = artist.Id;
artist.ArtistMetadata = new List<ArtistMetadata> { metadata };
artist.ArtistMetadata = [metadata];
return await _metadataRepository.Add(metadata);
}
@ -1316,7 +1317,8 @@ public class LocalMetadataProvider : ILocalMetadataProvider @@ -1316,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 = []
};
}
@ -1355,7 +1357,8 @@ public class LocalMetadataProvider : ILocalMetadataProvider @@ -1355,7 +1357,8 @@ public class LocalMetadataProvider : ILocalMetadataProvider
Biography = nfo.Biography,
Genres = nfo.Genres.Map(g => new Genre { Name = g }).ToList(),
Styles = nfo.Styles.Map(s => new Style { Name = s }).ToList(),
Moods = nfo.Moods.Map(m => new Mood { Name = m }).ToList()
Moods = nfo.Moods.Map(m => new Mood { Name = m }).ToList(),
Artwork = []
};
}
@ -1407,8 +1410,8 @@ public class LocalMetadataProvider : ILocalMetadataProvider @@ -1407,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<Studio>(),
Artwork = new List<Artwork>()
Studios = [],
Artwork = []
};
result.Add(metadata);

11
ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs

@ -27,6 +27,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner @@ -27,6 +27,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
private readonly IFileSystem _fileSystem;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly IMetadataRepository _metadataRepository;
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
private readonly ILogger<MovieFolderScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
@ -67,6 +68,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner @@ -67,6 +68,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
_localSubtitlesProvider = localSubtitlesProvider;
_localChaptersProvider = localChaptersProvider;
_localMetadataProvider = localMetadataProvider;
_metadataRepository = metadataRepository;
_libraryRepository = libraryRepository;
_mediaItemRepository = mediaItemRepository;
_client = client;
@ -312,13 +314,20 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner @@ -312,13 +314,20 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
try
{
Movie movie = result.Item;
foreach (MovieMetadata metadata in movie.MovieMetadata.HeadOrNone())
{
Option<string> maybeArtwork = LocateArtwork(movie, artworkKind);
foreach (string posterFile in maybeArtwork)
{
MovieMetadata metadata = movie.MovieMetadata.Head();
await RefreshArtwork(posterFile, metadata, artworkKind, None, None, cancellationToken);
}
if (maybeArtwork.IsNone && metadata.Artwork.Any(a => a.ArtworkKind == artworkKind))
{
await _metadataRepository.RemoveArtworkWithKind(metadata, artworkKind);
}
}
return result;
}
catch (Exception ex)

27
ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs

@ -23,6 +23,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan @@ -23,6 +23,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
private readonly IClient _client;
private readonly ILibraryRepository _libraryRepository;
private readonly ILocalChaptersProvider _localChaptersProvider;
private readonly IMetadataRepository _metadataRepository;
private readonly IScannerProxy _scannerProxy;
private readonly IFileSystem _fileSystem;
private readonly ILocalFileSystem _localFileSystem;
@ -66,6 +67,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan @@ -66,6 +67,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
_localMetadataProvider = localMetadataProvider;
_localSubtitlesProvider = localSubtitlesProvider;
_localChaptersProvider = localChaptersProvider;
_metadataRepository = metadataRepository;
_artistRepository = artistRepository;
_musicVideoRepository = musicVideoRepository;
_libraryRepository = libraryRepository;
@ -279,11 +281,19 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan @@ -279,11 +281,19 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
try
{
Artist artist = result.Item;
await LocateArtworkForArtist(artistFolder, artworkKind).IfSomeAsync(async artworkFile =>
foreach (ArtistMetadata metadata in artist.ArtistMetadata.HeadOrNone())
{
Option<string> maybeArtwork = LocateArtworkForArtist(artistFolder, artworkKind);
foreach (string artworkFile in maybeArtwork)
{
ArtistMetadata metadata = artist.ArtistMetadata.Head();
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;
}
@ -425,7 +435,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan @@ -425,7 +435,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
{
if (!Optional(musicVideo.MusicVideoMetadata).Flatten().Any())
{
musicVideo.MusicVideoMetadata ??= new List<MusicVideoMetadata>();
musicVideo.MusicVideoMetadata ??= [];
string path = musicVideo.MediaVersions.Head().MediaFiles.Head().Path;
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Fallback Metadata", path);
@ -497,13 +507,20 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan @@ -497,13 +507,20 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
{
MusicVideo musicVideo = result.Item;
foreach (MusicVideoMetadata metadata in musicVideo.MusicVideoMetadata.HeadOrNone())
{
Option<string> maybeThumbnail = LocateThumbnail(musicVideo);
foreach (string thumbnailFile in maybeThumbnail)
{
MusicVideoMetadata metadata = musicVideo.MusicVideoMetadata.Head();
await RefreshArtwork(thumbnailFile, metadata, ArtworkKind.Thumbnail, None, None, cancellationToken);
}
if (maybeThumbnail.IsNone && metadata.Artwork.Any(a => a.ArtworkKind is ArtworkKind.Thumbnail))
{
await _metadataRepository.RemoveArtworkWithKind(metadata, ArtworkKind.Thumbnail);
}
}
return result;
}
catch (Exception ex)

6
ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs

@ -22,6 +22,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan @@ -22,6 +22,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
private readonly IClient _client;
private readonly ILibraryRepository _libraryRepository;
private readonly ILocalChaptersProvider _localChaptersProvider;
private readonly IMetadataRepository _metadataRepository;
private readonly IScannerProxy _scannerProxy;
private readonly IFileSystem _fileSystem;
private readonly ILocalFileSystem _localFileSystem;
@ -64,6 +65,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan @@ -64,6 +65,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
_localMetadataProvider = localMetadataProvider;
_localSubtitlesProvider = localSubtitlesProvider;
_localChaptersProvider = localChaptersProvider;
_metadataRepository = metadataRepository;
_otherVideoRepository = otherVideoRepository;
_libraryRepository = libraryRepository;
_mediaItemRepository = mediaItemRepository;
@ -359,7 +361,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan @@ -359,7 +361,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
{
OtherVideo otherVideo = result.Item;
foreach (var metadata in otherVideo.OtherVideoMetadata.HeadOrNone())
foreach (OtherVideoMetadata metadata in otherVideo.OtherVideoMetadata.HeadOrNone())
{
Option<string> maybeThumbnail = LocateThumbnail(otherVideo);
foreach (string thumbnailFile in maybeThumbnail)
@ -369,7 +371,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan @@ -369,7 +371,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
if (maybeThumbnail.IsNone && metadata.Artwork.Any(a => a.ArtworkKind is ArtworkKind.Thumbnail))
{
await _otherVideoRepository.RemoveArtwork(metadata, ArtworkKind.Thumbnail);
await _metadataRepository.RemoveArtworkWithKind(metadata, ArtworkKind.Thumbnail);
}
}

11
ErsatzTV.Scanner/Core/Metadata/RemoteStreamFolderScanner.cs

@ -28,6 +28,7 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder @@ -28,6 +28,7 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder
private readonly IFileSystem _fileSystem;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly IMetadataRepository _metadataRepository;
private readonly ILogger<RemoteStreamFolderScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly IRemoteStreamRepository _remoteStreamRepository;
@ -61,6 +62,7 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder @@ -61,6 +62,7 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder
_fileSystem = fileSystem;
_localFileSystem = localFileSystem;
_localMetadataProvider = localMetadataProvider;
_metadataRepository = metadataRepository;
_remoteStreamRepository = remoteStreamRepository;
_libraryRepository = libraryRepository;
_mediaItemRepository = mediaItemRepository;
@ -386,13 +388,20 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder @@ -386,13 +388,20 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder
{
RemoteStream remoteStream = result.Item;
foreach (RemoteStreamMetadata metadata in remoteStream.RemoteStreamMetadata.HeadOrNone())
{
Option<string> maybeThumbnail = LocateThumbnail(remoteStream);
foreach (string thumbnailFile in maybeThumbnail)
{
RemoteStreamMetadata metadata = remoteStream.RemoteStreamMetadata.Head();
await RefreshArtwork(thumbnailFile, metadata, ArtworkKind.Thumbnail, None, None, cancellationToken);
}
if (maybeThumbnail.IsNone && metadata.Artwork.Any(a => a.ArtworkKind is ArtworkKind.Thumbnail))
{
await _metadataRepository.RemoveArtworkWithKind(metadata, ArtworkKind.Thumbnail);
}
}
return result;
}
catch (Exception ex)

23
ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs

@ -25,6 +25,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner @@ -25,6 +25,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
private readonly IFileSystem _fileSystem;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly IMetadataRepository _metadataRepository;
private readonly ILogger<SongFolderScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly ISongRepository _songRepository;
@ -58,6 +59,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner @@ -58,6 +59,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
_fileSystem = fileSystem;
_localFileSystem = localFileSystem;
_localMetadataProvider = localMetadataProvider;
_metadataRepository = metadataRepository;
_songRepository = songRepository;
_libraryRepository = libraryRepository;
_mediaItemRepository = mediaItemRepository;
@ -263,7 +265,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner @@ -263,7 +265,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
if (shouldUpdate)
{
song.SongMetadata ??= new List<SongMetadata>();
song.SongMetadata ??= [];
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Metadata", path);
if (await _localMetadataProvider.RefreshTagMetadata(song))
@ -302,16 +304,20 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner @@ -302,16 +304,20 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
}
Song song = result.Item;
foreach (SongMetadata metadata in song.SongMetadata.HeadOrNone())
{
Option<string> maybeThumbnail = LocateThumbnail(song);
if (maybeThumbnail.IsNone)
if (maybeThumbnail.IsNone && !await ExtractEmbeddedArtwork(song, ffmpegPath, cancellationToken))
{
await ExtractEmbeddedArtwork(song, ffmpegPath, cancellationToken);
if (metadata.Artwork.Any(a => a.ArtworkKind is ArtworkKind.Thumbnail))
{
await _metadataRepository.RemoveArtworkWithKind(metadata, ArtworkKind.Thumbnail);
}
}
foreach (string thumbnailFile in maybeThumbnail)
{
SongMetadata metadata = song.SongMetadata.Head();
await RefreshArtwork(
thumbnailFile,
metadata,
@ -320,6 +326,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner @@ -320,6 +326,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
None,
cancellationToken);
}
}
return result;
}
@ -345,12 +352,12 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner @@ -345,12 +352,12 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
}).Flatten();
}
private async Task ExtractEmbeddedArtwork(Song song, string ffmpegPath, CancellationToken cancellationToken)
private async Task<bool> ExtractEmbeddedArtwork(Song song, string ffmpegPath, CancellationToken cancellationToken)
{
Option<MediaStream> maybeArtworkStream = Optional(song.GetHeadVersion().Streams.Find(ms => ms.AttachedPic));
foreach (MediaStream artworkStream in maybeArtworkStream)
{
await RefreshArtwork(
return await RefreshArtwork(
song.GetHeadVersion().MediaFiles.Head().Path,
song.SongMetadata.Head(),
ArtworkKind.Thumbnail,
@ -358,5 +365,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner @@ -358,5 +365,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
artworkStream.Index,
cancellationToken);
}
return false;
}
}

52
ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs

@ -248,17 +248,15 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -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 @@ -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<BaseError, Unit> scanResult = await ScanEpisodes(
libraryPath,
knownFolder,
@ -410,7 +414,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -410,7 +414,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
private async Task<Either<BaseError, Season>> EnsureMetadataExists(Season season)
{
season.SeasonMetadata ??= new List<SeasonMetadata>();
season.SeasonMetadata ??= [];
if (season.SeasonMetadata.Count == 0)
{
@ -419,8 +423,9 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -419,8 +423,9 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
SeasonId = season.Id,
Season = season,
DateAdded = DateTime.UtcNow,
Guids = new List<MetadataGuid>(),
Tags = new List<Tag>()
Guids = [],
Tags = [],
Artwork = []
};
season.SeasonMetadata.Add(metadata);
@ -492,13 +497,20 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -492,13 +497,20 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
try
{
Show show = result.Item;
foreach (ShowMetadata metadata in show.ShowMetadata.HeadOrNone())
{
Option<string> maybeArtwork = LocateArtworkForShow(showFolder, artworkKind);
foreach (string artworkFile in maybeArtwork)
{
ShowMetadata metadata = show.ShowMetadata.Head();
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;
}
catch (Exception ex)
@ -514,14 +526,21 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -514,14 +526,21 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
CancellationToken cancellationToken)
{
try
{
foreach (SeasonMetadata metadata in season.SeasonMetadata.HeadOrNone())
{
Option<string> maybePoster = LocatePoster(season, seasonFolder);
foreach (string posterFile in maybePoster)
{
SeasonMetadata metadata = season.SeasonMetadata.Head();
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;
}
catch (Exception ex)
@ -534,11 +553,11 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -534,11 +553,11 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
private async Task<Either<BaseError, Episode>> UpdateThumbnail(Episode episode, CancellationToken cancellationToken)
{
try
{
foreach (EpisodeMetadata metadata in episode.EpisodeMetadata)
{
Option<string> maybeThumbnail = LocateThumbnail(episode);
foreach (string thumbnailFile in maybeThumbnail)
{
foreach (EpisodeMetadata metadata in episode.EpisodeMetadata)
{
await RefreshArtwork(
thumbnailFile,
@ -548,6 +567,11 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -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;

9
ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs

@ -22,6 +22,7 @@ public class PlexMovieLibraryScanner : @@ -22,6 +22,7 @@ public class PlexMovieLibraryScanner :
private readonly IMetadataRepository _metadataRepository;
private readonly IMovieRepository _movieRepository;
private readonly IPlexMovieRepository _plexMovieRepository;
private readonly IPlexMetadataRepository _plexMetadataRepository;
private readonly IPlexPathReplacementService _plexPathReplacementService;
private readonly IPlexServerApiClient _plexServerApiClient;
@ -32,6 +33,7 @@ public class PlexMovieLibraryScanner : @@ -32,6 +33,7 @@ public class PlexMovieLibraryScanner :
IMetadataRepository metadataRepository,
IMediaSourceRepository mediaSourceRepository,
IPlexMovieRepository plexMovieRepository,
IPlexMetadataRepository plexMetadataRepository,
IPlexPathReplacementService plexPathReplacementService,
IFileSystem fileSystem,
ILocalChaptersProvider localChaptersProvider,
@ -48,6 +50,7 @@ public class PlexMovieLibraryScanner : @@ -48,6 +50,7 @@ public class PlexMovieLibraryScanner :
_metadataRepository = metadataRepository;
_mediaSourceRepository = mediaSourceRepository;
_plexMovieRepository = plexMovieRepository;
_plexMetadataRepository = plexMetadataRepository;
_plexPathReplacementService = plexPathReplacementService;
_logger = logger;
}
@ -383,9 +386,9 @@ public class PlexMovieLibraryScanner : @@ -383,9 +386,9 @@ public class PlexMovieLibraryScanner :
if (maybeIncomingArtwork.IsNone)
{
existingMetadata.Artwork ??= new List<Artwork>();
existingMetadata.Artwork ??= [];
existingMetadata.Artwork.RemoveAll(a => a.ArtworkKind == artworkKind);
await _metadataRepository.RemoveArtwork(existingMetadata, artworkKind);
await _plexMetadataRepository.RemoveArtwork(existingMetadata, artworkKind);
}
foreach (Artwork incomingArtwork in maybeIncomingArtwork)
@ -397,7 +400,7 @@ public class PlexMovieLibraryScanner : @@ -397,7 +400,7 @@ public class PlexMovieLibraryScanner :
if (maybeExistingArtwork.IsNone)
{
existingMetadata.Artwork ??= new List<Artwork>();
existingMetadata.Artwork ??= [];
existingMetadata.Artwork.Add(incomingArtwork);
await _metadataRepository.AddArtwork(existingMetadata, incomingArtwork);
}

9
ErsatzTV.Scanner/Core/Plex/PlexOtherVideoLibraryScanner.cs

@ -22,6 +22,7 @@ public class PlexOtherVideoLibraryScanner : @@ -22,6 +22,7 @@ public class PlexOtherVideoLibraryScanner :
private readonly IMetadataRepository _metadataRepository;
private readonly IOtherVideoRepository _otherVideoRepository;
private readonly IPlexOtherVideoRepository _plexOtherVideoRepository;
private readonly IPlexMetadataRepository _plexMetadataRepository;
private readonly IPlexPathReplacementService _plexPathReplacementService;
private readonly IPlexServerApiClient _plexServerApiClient;
@ -32,6 +33,7 @@ public class PlexOtherVideoLibraryScanner : @@ -32,6 +33,7 @@ public class PlexOtherVideoLibraryScanner :
IMetadataRepository metadataRepository,
IMediaSourceRepository mediaSourceRepository,
IPlexOtherVideoRepository plexOtherVideoRepository,
IPlexMetadataRepository plexMetadataRepository,
IPlexPathReplacementService plexPathReplacementService,
IFileSystem fileSystem,
ILocalChaptersProvider localChaptersProvider,
@ -48,6 +50,7 @@ public class PlexOtherVideoLibraryScanner : @@ -48,6 +50,7 @@ public class PlexOtherVideoLibraryScanner :
_metadataRepository = metadataRepository;
_mediaSourceRepository = mediaSourceRepository;
_plexOtherVideoRepository = plexOtherVideoRepository;
_plexMetadataRepository = plexMetadataRepository;
_plexPathReplacementService = plexPathReplacementService;
_logger = logger;
}
@ -388,9 +391,9 @@ public class PlexOtherVideoLibraryScanner : @@ -388,9 +391,9 @@ public class PlexOtherVideoLibraryScanner :
if (maybeIncomingArtwork.IsNone)
{
existingMetadata.Artwork ??= new List<Artwork>();
existingMetadata.Artwork ??= [];
existingMetadata.Artwork.RemoveAll(a => a.ArtworkKind == artworkKind);
await _metadataRepository.RemoveArtwork(existingMetadata, artworkKind);
await _plexMetadataRepository.RemoveArtwork(existingMetadata, artworkKind);
}
foreach (Artwork incomingArtwork in maybeIncomingArtwork)
@ -402,7 +405,7 @@ public class PlexOtherVideoLibraryScanner : @@ -402,7 +405,7 @@ public class PlexOtherVideoLibraryScanner :
if (maybeExistingArtwork.IsNone)
{
existingMetadata.Artwork ??= new List<Artwork>();
existingMetadata.Artwork ??= [];
existingMetadata.Artwork.Add(incomingArtwork);
await _metadataRepository.AddArtwork(existingMetadata, incomingArtwork);
}

9
ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs

@ -27,6 +27,7 @@ public partial class PlexTelevisionLibraryScanner : @@ -27,6 +27,7 @@ public partial class PlexTelevisionLibraryScanner :
private readonly IPlexPathReplacementService _plexPathReplacementService;
private readonly IPlexServerApiClient _plexServerApiClient;
private readonly IPlexTelevisionRepository _plexTelevisionRepository;
private readonly IPlexMetadataRepository _plexMetadataRepository;
private readonly ITelevisionRepository _televisionRepository;
public PlexTelevisionLibraryScanner(
@ -37,6 +38,7 @@ public partial class PlexTelevisionLibraryScanner : @@ -37,6 +38,7 @@ public partial class PlexTelevisionLibraryScanner :
IMediaSourceRepository mediaSourceRepository,
IPlexPathReplacementService plexPathReplacementService,
IPlexTelevisionRepository plexTelevisionRepository,
IPlexMetadataRepository plexMetadataRepository,
IFileSystem fileSystem,
ILocalChaptersProvider localChaptersProvider,
ILogger<PlexTelevisionLibraryScanner> logger)
@ -53,6 +55,7 @@ public partial class PlexTelevisionLibraryScanner : @@ -53,6 +55,7 @@ public partial class PlexTelevisionLibraryScanner :
_mediaSourceRepository = mediaSourceRepository;
_plexPathReplacementService = plexPathReplacementService;
_plexTelevisionRepository = plexTelevisionRepository;
_plexMetadataRepository = plexMetadataRepository;
_logger = logger;
}
@ -719,9 +722,9 @@ public partial class PlexTelevisionLibraryScanner : @@ -719,9 +722,9 @@ public partial class PlexTelevisionLibraryScanner :
if (maybeIncomingArtwork.IsNone)
{
existingMetadata.Artwork ??= new List<Artwork>();
existingMetadata.Artwork ??= [];
existingMetadata.Artwork.RemoveAll(a => a.ArtworkKind == artworkKind);
await _metadataRepository.RemoveArtwork(existingMetadata, artworkKind);
await _plexMetadataRepository.RemoveArtwork(existingMetadata, artworkKind);
}
foreach (Artwork incomingArtwork in maybeIncomingArtwork)
@ -733,7 +736,7 @@ public partial class PlexTelevisionLibraryScanner : @@ -733,7 +736,7 @@ public partial class PlexTelevisionLibraryScanner :
if (maybeExistingArtwork.IsNone)
{
existingMetadata.Artwork ??= new List<Artwork>();
existingMetadata.Artwork ??= [];
existingMetadata.Artwork.Add(incomingArtwork);
await _metadataRepository.AddArtwork(existingMetadata, incomingArtwork);
}

1
ErsatzTV.Scanner/Program.cs

@ -224,6 +224,7 @@ public class Program @@ -224,6 +224,7 @@ public class Program
services.AddScoped<IPlexMovieRepository, PlexMovieRepository>();
services.AddScoped<IPlexOtherVideoRepository, PlexOtherVideoRepository>();
services.AddScoped<IPlexTelevisionRepository, PlexTelevisionRepository>();
services.AddScoped<IPlexMetadataRepository, PlexMetadataRepository>();
services.AddScoped<IPlexPathReplacementService, PlexPathReplacementService>();
services.AddScoped<PlexEtag>();

1
ErsatzTV/Startup.cs

@ -812,6 +812,7 @@ public class Startup @@ -812,6 +812,7 @@ public class Startup
services.AddScoped<IPlexMovieRepository, PlexMovieRepository>();
services.AddScoped<IPlexTelevisionRepository, PlexTelevisionRepository>();
services.AddScoped<IPlexCollectionRepository, PlexCollectionRepository>();
services.AddScoped<IPlexMetadataRepository, PlexMetadataRepository>();
services.AddScoped<IJellyfinApiClient, JellyfinApiClient>();
services.AddScoped<IJellyfinPathReplacementService, JellyfinPathReplacementService>();
services.AddScoped<IJellyfinTelevisionRepository, JellyfinTelevisionRepository>();

Loading…
Cancel
Save