diff --git a/ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs b/ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs index 8f6b7b06f..4fc9ef03f 100644 --- a/ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs +++ b/ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs @@ -1,4 +1,5 @@ using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Emby; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; @@ -17,6 +18,7 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner private readonly ILocalStatisticsProvider _localStatisticsProvider; private readonly ILogger _logger; private readonly IMediaSourceRepository _mediaSourceRepository; + private readonly IMetadataRepository _metadataRepository; private readonly IMediator _mediator; private readonly IMovieRepository _movieRepository; private readonly IEmbyPathReplacementService _pathReplacementService; @@ -31,6 +33,7 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner ISearchRepository searchRepository, IEmbyPathReplacementService pathReplacementService, IMediaSourceRepository mediaSourceRepository, + IMetadataRepository metadataRepository, ILocalFileSystem localFileSystem, ILocalStatisticsProvider localStatisticsProvider, ILogger logger) @@ -42,6 +45,7 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner _searchRepository = searchRepository; _pathReplacementService = pathReplacementService; _mediaSourceRepository = mediaSourceRepository; + _metadataRepository = metadataRepository; _localFileSystem = localFileSystem; _localStatisticsProvider = localStatisticsProvider; _logger = logger; @@ -179,6 +183,11 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner incomingMovie, localPath); + if (refreshResult.Map(t => t).IfLeft(false)) + { + refreshResult = await UpdateSubtitles(incomingMovie); + } + await refreshResult.Match( async _ => { @@ -229,4 +238,45 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner _searchIndex.Commit(); return Unit.Default; } + + private async Task> UpdateSubtitles(EmbyMovie movie) + { + try + { + foreach (MovieMetadata metadata in movie.MovieMetadata) + { + MediaVersion version = movie.GetHeadVersion(); + var subtitleStreams = version.Streams + .Filter(s => s.MediaStreamKind == MediaStreamKind.Subtitle) + .ToList(); + + var subtitles = new List(); + + foreach (MediaStream stream in subtitleStreams) + { + var subtitle = new Subtitle + { + Codec = stream.Codec, + Default = stream.Default, + Forced = stream.Forced, + Language = stream.Language, + StreamIndex = stream.Index, + SubtitleKind = SubtitleKind.Embedded, + DateAdded = DateTime.UtcNow, + DateUpdated = DateTime.UtcNow + }; + + subtitles.Add(subtitle); + } + + return await _metadataRepository.UpdateSubtitles(metadata, subtitles); + } + } + catch (Exception ex) + { + return BaseError.New(ex.ToString()); + } + + return false; + } } diff --git a/ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs b/ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs index 9dac61ae8..5e20ed06e 100644 --- a/ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs +++ b/ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs @@ -1,4 +1,5 @@ using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Emby; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; @@ -19,6 +20,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMediator _mediator; private readonly IEmbyPathReplacementService _pathReplacementService; + private readonly IMetadataRepository _metadataRepository; private readonly ISearchIndex _searchIndex; private readonly ISearchRepository _searchRepository; private readonly IEmbyTelevisionRepository _televisionRepository; @@ -30,6 +32,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner ISearchIndex searchIndex, ISearchRepository searchRepository, IEmbyPathReplacementService pathReplacementService, + IMetadataRepository metadataRepository, ILocalFileSystem localFileSystem, ILocalStatisticsProvider localStatisticsProvider, IMediator mediator, @@ -41,6 +44,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner _searchIndex = searchIndex; _searchRepository = searchRepository; _pathReplacementService = pathReplacementService; + _metadataRepository = metadataRepository; _localFileSystem = localFileSystem; _localStatisticsProvider = localStatisticsProvider; _mediator = mediator; @@ -425,6 +429,11 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner ffprobePath, incomingEpisode, localPath); + + if (refreshResult.Map(t => t).IfLeft(false)) + { + refreshResult = await UpdateSubtitles(incomingEpisode); + } refreshResult.Match( _ => { }, @@ -436,4 +445,45 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner } } } + + private async Task> UpdateSubtitles(EmbyEpisode episode) + { + try + { + foreach (EpisodeMetadata metadata in episode.EpisodeMetadata) + { + MediaVersion version = episode.GetHeadVersion(); + var subtitleStreams = version.Streams + .Filter(s => s.MediaStreamKind == MediaStreamKind.Subtitle) + .ToList(); + + var subtitles = new List(); + + foreach (MediaStream stream in subtitleStreams) + { + var subtitle = new Subtitle + { + Codec = stream.Codec, + Default = stream.Default, + Forced = stream.Forced, + Language = stream.Language, + StreamIndex = stream.Index, + SubtitleKind = SubtitleKind.Embedded, + DateAdded = DateTime.UtcNow, + DateUpdated = DateTime.UtcNow + }; + + subtitles.Add(subtitle); + } + + return await _metadataRepository.UpdateSubtitles(metadata, subtitles); + } + } + catch (Exception ex) + { + return BaseError.New(ex.ToString()); + } + + return false; + } } diff --git a/ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs b/ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs index abccdb1d0..975a23f8e 100644 --- a/ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs +++ b/ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs @@ -1,4 +1,5 @@ using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Jellyfin; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; @@ -17,6 +18,7 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner private readonly ILocalStatisticsProvider _localStatisticsProvider; private readonly ILogger _logger; private readonly IMediaSourceRepository _mediaSourceRepository; + private readonly IMetadataRepository _metadataRepository; private readonly IMediator _mediator; private readonly IMovieRepository _movieRepository; private readonly IJellyfinPathReplacementService _pathReplacementService; @@ -31,6 +33,7 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner ISearchRepository searchRepository, IJellyfinPathReplacementService pathReplacementService, IMediaSourceRepository mediaSourceRepository, + IMetadataRepository metadataRepository, ILocalFileSystem localFileSystem, ILocalStatisticsProvider localStatisticsProvider, ILogger logger) @@ -42,6 +45,7 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner _searchRepository = searchRepository; _pathReplacementService = pathReplacementService; _mediaSourceRepository = mediaSourceRepository; + _metadataRepository = metadataRepository; _localFileSystem = localFileSystem; _localStatisticsProvider = localStatisticsProvider; _logger = logger; @@ -178,6 +182,11 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner ffprobePath, incomingMovie, localPath); + + if (refreshResult.Map(t => t).IfLeft(false)) + { + refreshResult = await UpdateSubtitles(incomingMovie); + } await refreshResult.Match( async _ => @@ -229,4 +238,45 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner _searchIndex.Commit(); return Unit.Default; } + private async Task> UpdateSubtitles(JellyfinMovie movie) + { + try + { + foreach (MovieMetadata metadata in movie.MovieMetadata) + { + MediaVersion version = movie.GetHeadVersion(); + var subtitleStreams = version.Streams + .Filter(s => s.MediaStreamKind == MediaStreamKind.Subtitle) + .ToList(); + + var subtitles = new List(); + + foreach (MediaStream stream in subtitleStreams) + { + var subtitle = new Subtitle + { + Codec = stream.Codec, + Default = stream.Default, + Forced = stream.Forced, + Language = stream.Language, + StreamIndex = stream.Index, + SubtitleKind = SubtitleKind.Embedded, + DateAdded = DateTime.UtcNow, + DateUpdated = DateTime.UtcNow + }; + + subtitles.Add(subtitle); + } + + return await _metadataRepository.UpdateSubtitles(metadata, subtitles); + } + } + catch (Exception ex) + { + return BaseError.New(ex.ToString()); + } + + return false; + } + } diff --git a/ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs b/ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs index bd340fd50..a81c7dec8 100644 --- a/ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs +++ b/ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs @@ -1,4 +1,5 @@ using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Jellyfin; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; @@ -19,6 +20,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMediator _mediator; private readonly IJellyfinPathReplacementService _pathReplacementService; + private readonly IMetadataRepository _metadataRepository; private readonly ISearchIndex _searchIndex; private readonly ISearchRepository _searchRepository; private readonly IJellyfinTelevisionRepository _televisionRepository; @@ -30,6 +32,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne ISearchIndex searchIndex, ISearchRepository searchRepository, IJellyfinPathReplacementService pathReplacementService, + IMetadataRepository metadataRepository, ILocalFileSystem localFileSystem, ILocalStatisticsProvider localStatisticsProvider, IMediator mediator, @@ -41,6 +44,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne _searchIndex = searchIndex; _searchRepository = searchRepository; _pathReplacementService = pathReplacementService; + _metadataRepository = metadataRepository; _localFileSystem = localFileSystem; _localStatisticsProvider = localStatisticsProvider; _mediator = mediator; @@ -427,6 +431,11 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne ffprobePath, incomingEpisode, localPath); + + if (refreshResult.Map(t => t).IfLeft(false)) + { + refreshResult = await UpdateSubtitles(incomingEpisode); + } refreshResult.Match( _ => { }, @@ -438,4 +447,45 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne } } } + + private async Task> UpdateSubtitles(JellyfinEpisode episode) + { + try + { + foreach (EpisodeMetadata metadata in episode.EpisodeMetadata) + { + MediaVersion version = episode.GetHeadVersion(); + var subtitleStreams = version.Streams + .Filter(s => s.MediaStreamKind == MediaStreamKind.Subtitle) + .ToList(); + + var subtitles = new List(); + + foreach (MediaStream stream in subtitleStreams) + { + var subtitle = new Subtitle + { + Codec = stream.Codec, + Default = stream.Default, + Forced = stream.Forced, + Language = stream.Language, + StreamIndex = stream.Index, + SubtitleKind = SubtitleKind.Embedded, + DateAdded = DateTime.UtcNow, + DateUpdated = DateTime.UtcNow + }; + + subtitles.Add(subtitle); + } + + return await _metadataRepository.UpdateSubtitles(metadata, subtitles); + } + } + catch (Exception ex) + { + return BaseError.New(ex.ToString()); + } + + return false; + } } diff --git a/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs b/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs index 7bec1afad..4fd6dc7b8 100644 --- a/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs +++ b/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs @@ -1,4 +1,5 @@ using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Repositories; @@ -97,6 +98,7 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan .BindT( existing => UpdateStatistics(pathReplacements, existing, incoming, ffmpegPath, ffprobePath)) .BindT(existing => UpdateMetadata(existing, incoming, library, connection, token)) + .BindT(UpdateSubtitles) .BindT(existing => UpdateArtwork(existing, incoming)); await maybeMovie.Match( @@ -406,6 +408,50 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan return result; } + private async Task>> UpdateSubtitles( + MediaItemScanResult result) + { + try + { + Movie movie = result.Item; + + foreach (MovieMetadata metadata in movie.MovieMetadata) + { + MediaVersion version = movie.GetHeadVersion(); + var subtitleStreams = version.Streams + .Filter(s => s.MediaStreamKind == MediaStreamKind.Subtitle) + .ToList(); + + var subtitles = new List(); + + foreach (MediaStream stream in subtitleStreams) + { + var subtitle = new Subtitle + { + Codec = stream.Codec, + Default = stream.Default, + Forced = stream.Forced, + Language = stream.Language, + StreamIndex = stream.Index, + SubtitleKind = SubtitleKind.Embedded, + DateAdded = DateTime.UtcNow, + DateUpdated = DateTime.UtcNow + }; + + subtitles.Add(subtitle); + } + + await _metadataRepository.UpdateSubtitles(metadata, subtitles); + } + + return result; + } + catch (Exception ex) + { + return BaseError.New(ex.ToString()); + } + } + private async Task>> UpdateArtwork( MediaItemScanResult result, PlexMovie incoming) diff --git a/ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs b/ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs index 61a02cc7e..545852a8e 100644 --- a/ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs +++ b/ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs @@ -1,4 +1,5 @@ using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Repositories; @@ -444,6 +445,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL token, ffmpegPath, ffprobePath)) + .BindT(UpdateSubtitles) .BindT(existing => UpdateArtwork(existing, incoming)); await maybeEpisode.Match( @@ -615,6 +617,47 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL return Right(existing); } + + private async Task> UpdateSubtitles(PlexEpisode episode) + { + try + { + foreach (EpisodeMetadata metadata in episode.EpisodeMetadata) + { + MediaVersion version = episode.GetHeadVersion(); + var subtitleStreams = version.Streams + .Filter(s => s.MediaStreamKind == MediaStreamKind.Subtitle) + .ToList(); + + var subtitles = new List(); + + foreach (MediaStream stream in subtitleStreams) + { + var subtitle = new Subtitle + { + Codec = stream.Codec, + Default = stream.Default, + Forced = stream.Forced, + Language = stream.Language, + StreamIndex = stream.Index, + SubtitleKind = SubtitleKind.Embedded, + DateAdded = DateTime.UtcNow, + DateUpdated = DateTime.UtcNow + }; + + subtitles.Add(subtitle); + } + + await _metadataRepository.UpdateSubtitles(metadata, subtitles); + } + + return episode; + } + catch (Exception ex) + { + return BaseError.New(ex.ToString()); + } + } private async Task> UpdateArtwork(PlexEpisode existing, PlexEpisode incoming) {