sync episode tags and genres (#1155)

* sync episode tags and genres

* update dependencies

* property update local episode genres and tags

* fix test
This commit is contained in:
Jason Dove
2023-02-12 09:53:20 -06:00
committed by GitHub
parent 0eb36f0ce1
commit 13e21bbcce
40 changed files with 218 additions and 42 deletions
+4
View File
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- Add button to copy/clone schedule from schedules table
- Synchronize episode tags and genres from Jellyfin, Emby and Local show libraries
- Add `Deep Scan` button to Jellyfin and Emby libraries
- This is now required to update some metadata for existing libraries, when targeted updates are not possible
- For example, if you already have tags and genres on your episodes in Jellyfin or Emby, you will need to deep scan each library to update that metadata on existing items in ErsatzTV
### Fixed
- Fix many QSV pipeline bugs
@@ -65,6 +65,11 @@ public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler<ISynchron
arguments.Add("--force");
}
if (request.DeepScan)
{
arguments.Add("--deep");
}
return await base.PerformScan(scanner, arguments, cancellationToken);
}
@@ -2,19 +2,20 @@
namespace ErsatzTV.Application.Emby;
public interface ISynchronizeEmbyLibraryById : IRequest<Either<BaseError, string>>,
IEmbyBackgroundServiceRequest
public interface ISynchronizeEmbyLibraryById : IRequest<Either<BaseError, string>>, IEmbyBackgroundServiceRequest
{
int EmbyLibraryId { get; }
bool ForceScan { get; }
bool DeepScan { get; }
}
public record SynchronizeEmbyLibraryByIdIfNeeded(int EmbyLibraryId) : ISynchronizeEmbyLibraryById
{
public bool ForceScan => false;
public bool DeepScan => false;
}
public record ForceSynchronizeEmbyLibraryById(int EmbyLibraryId) : ISynchronizeEmbyLibraryById
public record ForceSynchronizeEmbyLibraryById(int EmbyLibraryId, bool DeepScan) : ISynchronizeEmbyLibraryById
{
public bool ForceScan => true;
}
@@ -64,6 +64,11 @@ public class CallJellyfinLibraryScannerHandler : CallLibraryScannerHandler<ISync
{
arguments.Add("--force");
}
if (request.DeepScan)
{
arguments.Add("--deep");
}
return await base.PerformScan(scanner, arguments, cancellationToken);
}
@@ -7,14 +7,17 @@ public interface ISynchronizeJellyfinLibraryById : IRequest<Either<BaseError, st
{
int JellyfinLibraryId { get; }
bool ForceScan { get; }
bool DeepScan { get; }
}
public record SynchronizeJellyfinLibraryByIdIfNeeded(int JellyfinLibraryId) : ISynchronizeJellyfinLibraryById
{
public bool ForceScan => false;
public bool DeepScan => false;
}
public record ForceSynchronizeJellyfinLibraryById(int JellyfinLibraryId) : ISynchronizeJellyfinLibraryById
public record ForceSynchronizeJellyfinLibraryById
(int JellyfinLibraryId, bool DeepScan) : ISynchronizeJellyfinLibraryById
{
public bool ForceScan => true;
}
@@ -9,7 +9,7 @@
<ItemGroup>
<PackageReference Include="Bugsnag" Version="3.1.0" />
<PackageReference Include="CliWrap" Version="3.6.0" />
<PackageReference Include="FluentAssertions" Version="6.9.0" />
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="LanguageExt.Core" Version="4.4.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
@@ -59,6 +59,8 @@ public class FakeTelevisionRepository : ITelevisionRepository
public Task<List<int>> DeleteEmptyShows(LibraryPath libraryPath) => throw new NotSupportedException();
public Task<bool> AddGenre(ShowMetadata metadata, Genre genre) => throw new NotSupportedException();
public Task<bool> AddGenre(EpisodeMetadata metadata, Genre genre) => throw new NotSupportedException();
public Task<bool> AddTag(Domain.Metadata metadata, Tag tag) => throw new NotSupportedException();
public Task<bool> AddStudio(ShowMetadata metadata, Studio studio) => throw new NotSupportedException();
@@ -10,5 +10,6 @@ public interface IEmbyMovieLibraryScanner
EmbyLibrary library,
string ffmpegPath,
string ffprobePath,
bool deepScan,
CancellationToken cancellationToken);
}
@@ -10,5 +10,6 @@ public interface IEmbyTelevisionLibraryScanner
EmbyLibrary library,
string ffmpegPath,
string ffprobePath,
bool deepScan,
CancellationToken cancellationToken);
}
@@ -10,5 +10,6 @@ public interface IJellyfinMovieLibraryScanner
JellyfinLibrary library,
string ffmpegPath,
string ffprobePath,
bool deepScan,
CancellationToken cancellationToken);
}
@@ -10,5 +10,6 @@ public interface IJellyfinTelevisionLibraryScanner
JellyfinLibrary library,
string ffmpegPath,
string ffprobePath,
bool deepScan,
CancellationToken cancellationToken);
}
@@ -14,7 +14,7 @@ public interface IMediaServerTelevisionRepository<in TLibrary, TShow, TSeason, T
Task<List<TEtag>> GetExistingEpisodes(TLibrary library, TSeason season);
Task<Either<BaseError, MediaItemScanResult<TShow>>> GetOrAdd(TLibrary library, TShow item);
Task<Either<BaseError, MediaItemScanResult<TSeason>>> GetOrAdd(TLibrary library, TSeason item);
Task<Either<BaseError, MediaItemScanResult<TEpisode>>> GetOrAdd(TLibrary library, TEpisode item);
Task<Either<BaseError, MediaItemScanResult<TEpisode>>> GetOrAdd(TLibrary library, TEpisode item, bool deepScan);
Task<Unit> SetEtag(TShow show, string etag);
Task<Unit> SetEtag(TSeason season, string etag);
Task<Unit> SetEtag(TEpisode episode, string etag);
@@ -30,6 +30,7 @@ public interface ITelevisionRepository
Task<Unit> DeleteEmptySeasons(LibraryPath libraryPath);
Task<List<int>> DeleteEmptyShows(LibraryPath libraryPath);
Task<bool> AddGenre(ShowMetadata metadata, Genre genre);
Task<bool> AddGenre(EpisodeMetadata metadata, Genre genre);
Task<bool> AddTag(Domain.Metadata metadata, Tag tag);
Task<bool> AddStudio(ShowMetadata metadata, Studio studio);
Task<bool> AddActor(ShowMetadata metadata, Actor actor);
@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.9.0" />
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="Moq" Version="4.18.4" />
@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.9.0" />
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="NUnit" Version="3.13.3" />
@@ -124,7 +124,8 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository
public async Task<Either<BaseError, MediaItemScanResult<EmbyEpisode>>> GetOrAdd(
EmbyLibrary library,
EmbyEpisode item)
EmbyEpisode item,
bool deepScan)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
Option<EmbyEpisode> maybeExisting = await dbContext.EmbyEpisodes
@@ -158,7 +159,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository
foreach (EmbyEpisode embyEpisode in maybeExisting)
{
var result = new MediaItemScanResult<EmbyEpisode>(embyEpisode) { IsAdded = false };
if (embyEpisode.Etag != item.Etag)
if (embyEpisode.Etag != item.Etag || deepScan)
{
await UpdateEpisode(dbContext, embyEpisode, item);
result.IsUpdated = true;
@@ -657,6 +658,36 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository
metadata.Guids.Add(guid);
}
// genres
foreach (Genre genre in metadata.Genres
.Filter(g => incomingMetadata.Genres.All(g2 => g2.Name != g.Name))
.ToList())
{
metadata.Genres.Remove(genre);
}
foreach (Genre genre in incomingMetadata.Genres
.Filter(g => metadata.Genres.All(g2 => g2.Name != g.Name))
.ToList())
{
metadata.Genres.Add(genre);
}
// tags
foreach (Tag tag in metadata.Tags
.Filter(g => incomingMetadata.Tags.All(g2 => g2.Name != g.Name))
.ToList())
{
metadata.Tags.Remove(tag);
}
foreach (Tag tag in incomingMetadata.Tags
.Filter(g => metadata.Tags.All(g2 => g2.Name != g.Name))
.ToList())
{
metadata.Tags.Add(tag);
}
var paths = incomingMetadata.Artwork.Map(a => a.Path).ToList();
foreach (Artwork artworkToRemove in metadata.Artwork
.Filter(a => !paths.Contains(a.Path))
@@ -128,7 +128,8 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
public async Task<Either<BaseError, MediaItemScanResult<JellyfinEpisode>>> GetOrAdd(
JellyfinLibrary library,
JellyfinEpisode item)
JellyfinEpisode item,
bool deepScan)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
Option<JellyfinEpisode> maybeExisting = await dbContext.JellyfinEpisodes
@@ -162,7 +163,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
foreach (JellyfinEpisode jellyfinEpisode in maybeExisting)
{
var result = new MediaItemScanResult<JellyfinEpisode>(jellyfinEpisode) { IsAdded = false };
if (jellyfinEpisode.Etag != item.Etag)
if (jellyfinEpisode.Etag != item.Etag || deepScan)
{
await UpdateEpisode(dbContext, jellyfinEpisode, item);
result.IsUpdated = true;
@@ -660,6 +661,36 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
{
metadata.Guids.Add(guid);
}
// genres
foreach (Genre genre in metadata.Genres
.Filter(g => incomingMetadata.Genres.All(g2 => g2.Name != g.Name))
.ToList())
{
metadata.Genres.Remove(genre);
}
foreach (Genre genre in incomingMetadata.Genres
.Filter(g => metadata.Genres.All(g2 => g2.Name != g.Name))
.ToList())
{
metadata.Genres.Add(genre);
}
// tags
foreach (Tag tag in metadata.Tags
.Filter(g => incomingMetadata.Tags.All(g2 => g2.Name != g.Name))
.ToList())
{
metadata.Tags.Remove(tag);
}
foreach (Tag tag in incomingMetadata.Tags
.Filter(g => metadata.Tags.All(g2 => g2.Name != g.Name))
.ToList())
{
metadata.Tags.Add(tag);
}
var paths = incomingMetadata.Artwork.Map(a => a.Path).ToList();
foreach (Artwork artworkToRemove in metadata.Artwork
@@ -183,7 +183,8 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
public async Task<Either<BaseError, MediaItemScanResult<PlexEpisode>>> GetOrAdd(
PlexLibrary library,
PlexEpisode item)
PlexEpisode item,
bool deepScan)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
Option<PlexEpisode> maybeExisting = await dbContext.PlexEpisodes
@@ -219,6 +220,8 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
foreach (PlexEpisode plexEpisode in maybeExisting)
{
var result = new MediaItemScanResult<PlexEpisode>(plexEpisode) { IsAdded = false };
// deepScan isn't needed here since we create our own plex etags
if (plexEpisode.Etag != item.Etag)
{
await UpdateEpisodePath(dbContext, plexEpisode, item);
@@ -539,6 +539,13 @@ public class TelevisionRepository : ITelevisionRepository
new { genre.Name, MetadataId = metadata.Id }).Map(result => result > 0);
}
public async Task<bool> AddGenre(EpisodeMetadata metadata, Genre genre)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Connection.ExecuteAsync(
"INSERT INTO Genre (Name, EpisodeMetadataId) VALUES (@Name, @MetadataId)",
new { genre.Name, MetadataId = metadata.Id }).Map(result => result > 0); }
public async Task<bool> AddTag(Metadata metadata, Tag tag)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@@ -692,8 +692,8 @@ public class EmbyApiClient : IEmbyApiClient
Plot = item.Overview,
Year = item.ProductionYear,
DateAdded = dateAdded,
Genres = new List<Genre>(),
Tags = new List<Tag>(),
Genres = Optional(item.Genres).Flatten().Map(g => new Genre { Name = g }).ToList(),
Tags = Optional(item.TagItems).Flatten().Map(t => new Tag { Name = t.Name }).ToList(),
Studios = new List<Studio>(),
Actors = new List<Actor>(),
Artwork = new List<Artwork>(),
+1 -1
View File
@@ -89,7 +89,7 @@ public interface IEmbyApi
string seasonId,
[Query]
string fields =
"Path,DateCreated,Etag,Overview,ProductionYear,PremiereDate,MediaSources,LocationType,ProviderIds,People",
"Path,Genres,Tags,DateCreated,Etag,Overview,ProductionYear,PremiereDate,MediaSources,LocationType,ProviderIds,People",
[Query]
int startIndex = 0,
[Query]
@@ -111,7 +111,7 @@ public interface IJellyfinApi
[Query]
string parentId,
[Query]
string fields = "Path,DateCreated,Etag,Overview,ProviderIds,People",
string fields = "Path,Genres,Tags,DateCreated,Etag,Overview,ProviderIds,People",
[Query]
string includeItemTypes = "Episode",
[Query]
@@ -775,8 +775,8 @@ public class JellyfinApiClient : IJellyfinApiClient
Plot = item.Overview,
Year = item.ProductionYear,
DateAdded = dateAdded,
Genres = new List<Genre>(),
Tags = new List<Tag>(),
Genres = Optional(item.Genres).Flatten().Map(g => new Genre { Name = g }).ToList(),
Tags = Optional(item.Tags).Flatten().Map(t => new Tag { Name = t }).ToList(),
Studios = new List<Studio>(),
Actors = new List<Actor>(),
Artwork = new List<Artwork>(),
@@ -289,6 +289,58 @@ public class EpisodeNfoReaderTests
.Should().Be(1);
}
}
[Test]
public async Task Genres()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
<genre>Genre 1</genre>
</episodedetails>
<episodedetails>
<genre>Genre 2</genre>
<genre>Genre 3</genre>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.Should().BeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.Should().Be(2);
list.Count(nfo => nfo.Genres is ["Genre 1"]).Should().Be(1);
list.Count(nfo => nfo.Genres is ["Genre 2", "Genre 3"]).Should().Be(1);
}
}
[Test]
public async Task Tags()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
<tag>Tag 1</tag>
</episodedetails>
<episodedetails>
<tag>Tag 2</tag>
<tag>Tag 3</tag>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.Should().BeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.Should().Be(2);
list.Count(nfo => nfo.Tags is ["Tag 1"]).Should().Be(1);
list.Count(nfo => nfo.Tags is ["Tag 2", "Tag 3"]).Should().Be(1);
}
}
[Test]
public async Task FullSample_Should_Return_Nfo()
@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.9.0" />
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="LanguageExt.Core" Version="4.4.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="Moq" Version="4.18.4" />
@@ -2,4 +2,5 @@
namespace ErsatzTV.Scanner.Application.Emby;
public record SynchronizeEmbyLibraryById(int EmbyLibraryId, bool ForceScan) : IRequest<Either<BaseError, string>>;
public record SynchronizeEmbyLibraryById
(int EmbyLibraryId, bool ForceScan, bool DeepScan) : IRequest<Either<BaseError, string>>;
@@ -70,6 +70,7 @@ public class SynchronizeEmbyLibraryByIdHandler : IRequestHandler<SynchronizeEmby
parameters.Library,
parameters.FFmpegPath,
parameters.FFprobePath,
parameters.DeepScan,
cancellationToken),
LibraryMediaKind.Shows =>
await _embyTelevisionLibraryScanner.ScanLibrary(
@@ -78,6 +79,7 @@ public class SynchronizeEmbyLibraryByIdHandler : IRequestHandler<SynchronizeEmby
parameters.Library,
parameters.FFmpegPath,
parameters.FFprobePath,
parameters.DeepScan,
cancellationToken),
_ => Unit.Default
};
@@ -139,7 +141,8 @@ public class SynchronizeEmbyLibraryByIdHandler : IRequestHandler<SynchronizeEmby
request.ForceScan,
libraryRefreshInterval,
ffmpegPath,
ffprobePath
ffprobePath,
request.DeepScan
));
private Task<Validation<BaseError, ConnectionParameters>> ValidateConnection(
@@ -203,7 +206,8 @@ public class SynchronizeEmbyLibraryByIdHandler : IRequestHandler<SynchronizeEmby
bool ForceScan,
int LibraryRefreshInterval,
string FFmpegPath,
string FFprobePath);
string FFprobePath,
bool DeepScan);
private record ConnectionParameters(EmbyConnection ActiveConnection)
{
@@ -3,4 +3,4 @@
namespace ErsatzTV.Scanner.Application.Jellyfin;
public record SynchronizeJellyfinLibraryById
(int JellyfinLibraryId, bool ForceScan) : IRequest<Either<BaseError, string>>;
(int JellyfinLibraryId, bool ForceScan, bool DeepScan) : IRequest<Either<BaseError, string>>;
@@ -82,6 +82,7 @@ public class
parameters.Library,
parameters.FFmpegPath,
parameters.FFprobePath,
parameters.DeepScan,
cancellationToken),
LibraryMediaKind.Shows =>
await _jellyfinTelevisionLibraryScanner.ScanLibrary(
@@ -90,6 +91,7 @@ public class
parameters.Library,
parameters.FFmpegPath,
parameters.FFprobePath,
parameters.DeepScan,
cancellationToken),
_ => Unit.Default
};
@@ -151,7 +153,8 @@ public class
request.ForceScan,
libraryRefreshInterval,
ffmpegPath,
ffprobePath
ffprobePath,
request.DeepScan
));
private Task<Validation<BaseError, ConnectionParameters>> ValidateConnection(
@@ -215,7 +218,8 @@ public class
bool ForceScan,
int LibraryRefreshInterval,
string FFmpegPath,
string FFprobePath);
string FFprobePath,
bool DeepScan);
private record ConnectionParameters(JellyfinConnection ActiveConnection)
{
@@ -50,6 +50,7 @@ public class EmbyMovieLibraryScanner :
EmbyLibrary library,
string ffmpegPath,
string ffprobePath,
bool deepScan,
CancellationToken cancellationToken)
{
List<EmbyPathReplacement> pathReplacements =
@@ -70,7 +71,7 @@ public class EmbyMovieLibraryScanner :
GetLocalPath,
ffmpegPath,
ffprobePath,
false,
deepScan,
cancellationToken);
}
@@ -50,6 +50,7 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner<
EmbyLibrary library,
string ffmpegPath,
string ffprobePath,
bool deepScan,
CancellationToken cancellationToken)
{
List<EmbyPathReplacement> pathReplacements =
@@ -70,7 +71,7 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner<
GetLocalPath,
ffmpegPath,
ffprobePath,
false,
deepScan,
cancellationToken);
}
@@ -50,6 +50,7 @@ public class JellyfinMovieLibraryScanner :
JellyfinLibrary library,
string ffmpegPath,
string ffprobePath,
bool deepScan,
CancellationToken cancellationToken)
{
List<JellyfinPathReplacement> pathReplacements =
@@ -70,7 +71,7 @@ public class JellyfinMovieLibraryScanner :
GetLocalPath,
ffmpegPath,
ffprobePath,
false,
deepScan,
cancellationToken);
}
@@ -51,6 +51,7 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan
JellyfinLibrary library,
string ffmpegPath,
string ffprobePath,
bool deepScan,
CancellationToken cancellationToken)
{
List<JellyfinPathReplacement> pathReplacements =
@@ -71,7 +72,7 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan
GetLocalPath,
ffmpegPath,
ffprobePath,
false,
deepScan,
cancellationToken);
}
@@ -443,8 +443,8 @@ public class LocalMetadataProvider : ILocalMetadataProvider
updated = await UpdateMetadataCollections(
existing,
metadata,
(_, _) => Task.FromResult(false),
(_, _) => Task.FromResult(false),
_televisionRepository.AddGenre,
_televisionRepository.AddTag,
(_, _) => Task.FromResult(false),
_televisionRepository.AddActor) || updated;
@@ -1120,8 +1120,8 @@ public class LocalMetadataProvider : ILocalMetadataProvider
.ToList(),
Directors = nfo.Directors.Map(d => new Director { Name = d }).ToList(),
Writers = nfo.Writers.Map(w => new Writer { Name = w }).ToList(),
Genres = new List<Genre>(),
Tags = new List<Tag>(),
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>()
};
@@ -437,7 +437,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
incoming.SeasonId = season.Id;
Either<BaseError, MediaItemScanResult<TEpisode>> maybeEpisode = await televisionRepository
.GetOrAdd(library, incoming)
.GetOrAdd(library, incoming, deepScan)
.MapT(
result =>
{
@@ -4,6 +4,8 @@ public class EpisodeNfo
{
public EpisodeNfo()
{
Genres = new List<string>();
Tags = new List<string>();
Actors = new List<ActorNfo>();
Writers = new List<string>();
Directors = new List<string>();
@@ -17,6 +19,8 @@ public class EpisodeNfo
public string? ContentRating { get; set; }
public Option<DateTime> Aired { get; set; }
public string? Plot { get; set; }
public List<string> Genres { get; }
public List<string> Tags { get; }
public List<ActorNfo> Actors { get; }
public List<string> Writers { get; }
public List<string> Directors { get; }
@@ -89,6 +89,12 @@ public class EpisodeNfoReader : NfoReader<EpisodeNfo>, IEpisodeNfoReader
case "plot":
await ReadStringContent(reader, nfo, (episode, plot) => episode.Plot = plot);
break;
case "genre":
await ReadStringContent(reader, nfo, (episode, genre) => episode.Genres.Add(genre));
break;
case "tag":
await ReadStringContent(reader, nfo, (episode, tag) => episode.Tags.Add(tag));
break;
case "actor":
ReadActor(reader, nfo, (episode, actor) => episode.Actors.Add(actor));
break;
+6 -2
View File
@@ -72,10 +72,12 @@ public class Worker : BackgroundService
var scanEmbyCommand = new Command("scan-emby", "Scan an Emby library");
scanEmbyCommand.AddArgument(libraryIdArgument);
scanEmbyCommand.AddOption(forceOption);
scanEmbyCommand.AddOption(deepOption);
var scanJellyfinCommand = new Command("scan-jellyfin", "Scan a Jellyfin library");
scanJellyfinCommand.AddArgument(libraryIdArgument);
scanJellyfinCommand.AddOption(forceOption);
scanJellyfinCommand.AddOption(deepOption);
scanLocalCommand.SetHandler(
async context =>
@@ -122,12 +124,13 @@ public class Worker : BackgroundService
bool force = context.ParseResult.GetValueForOption(forceOption);
SetProcessPriority(force);
bool deep = context.ParseResult.GetValueForOption(deepOption);
int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument);
using IServiceScope scope = _serviceScopeFactory.CreateScope();
IMediator mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
var scan = new SynchronizeEmbyLibraryById(libraryId, force);
var scan = new SynchronizeEmbyLibraryById(libraryId, force, deep);
await mediator.Send(scan, context.GetCancellationToken());
}
});
@@ -140,12 +143,13 @@ public class Worker : BackgroundService
bool force = context.ParseResult.GetValueForOption(forceOption);
SetProcessPriority(force);
bool deep = context.ParseResult.GetValueForOption(deepOption);
int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument);
using IServiceScope scope = _serviceScopeFactory.CreateScope();
IMediator mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
var scan = new SynchronizeJellyfinLibraryById(libraryId, force);
var scan = new SynchronizeJellyfinLibraryById(libraryId, force, deep);
await mediator.Send(scan, context.GetCancellationToken());
}
});
+2 -2
View File
@@ -31,10 +31,10 @@ Global
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Release|Any CPU.Build.0 = Release|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug No Sync|Any CPU.ActiveCfg = Debug No Sync|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug No Sync|Any CPU.Build.0 = Debug No Sync|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug No Sync|Any CPU.ActiveCfg = Debug|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug No Sync|Any CPU.Build.0 = Debug|Any CPU
{C56FC23D-B863-401E-8E7C-E92BC307AFC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C56FC23D-B863-401E-8E7C-E92BC307AFC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C56FC23D-B863-401E-8E7C-E92BC307AFC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+3 -3
View File
@@ -55,7 +55,7 @@
}
else
{
if (context is PlexLibraryViewModel)
if (context is PlexLibraryViewModel or EmbyLibraryViewModel or JellyfinLibraryViewModel)
{
<MudTooltip Text="Deep Scan Library">
<MudIconButton Icon="@Icons.Material.Filled.FindReplace"
@@ -120,11 +120,11 @@
break;
case JellyfinLibraryViewModel:
await _jellyfinWorkerChannel.WriteAsync(new SynchronizeJellyfinLibraries(library.MediaSourceId), _cts.Token);
await _jellyfinWorkerChannel.WriteAsync(new ForceSynchronizeJellyfinLibraryById(library.Id), _cts.Token);
await _jellyfinWorkerChannel.WriteAsync(new ForceSynchronizeJellyfinLibraryById(library.Id, deepScan), _cts.Token);
break;
case EmbyLibraryViewModel:
await _embyWorkerChannel.WriteAsync(new SynchronizeEmbyLibraries(library.MediaSourceId), _cts.Token);
await _embyWorkerChannel.WriteAsync(new ForceSynchronizeEmbyLibraryById(library.Id), _cts.Token);
await _embyWorkerChannel.WriteAsync(new ForceSynchronizeEmbyLibraryById(library.Id, deepScan), _cts.Token);
break;
}