Browse Source

sync subtitles from media server scanners (#744)

pull/746/head
Jason Dove 4 years ago committed by GitHub
parent
commit
d755d0ae29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 50
      ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs
  2. 50
      ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs
  3. 50
      ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs
  4. 50
      ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs
  5. 46
      ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs
  6. 43
      ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs

50
ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs

@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Emby; using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
@ -17,6 +18,7 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner
private readonly ILocalStatisticsProvider _localStatisticsProvider; private readonly ILocalStatisticsProvider _localStatisticsProvider;
private readonly ILogger<EmbyMovieLibraryScanner> _logger; private readonly ILogger<EmbyMovieLibraryScanner> _logger;
private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IMetadataRepository _metadataRepository;
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly IMovieRepository _movieRepository; private readonly IMovieRepository _movieRepository;
private readonly IEmbyPathReplacementService _pathReplacementService; private readonly IEmbyPathReplacementService _pathReplacementService;
@ -31,6 +33,7 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner
ISearchRepository searchRepository, ISearchRepository searchRepository,
IEmbyPathReplacementService pathReplacementService, IEmbyPathReplacementService pathReplacementService,
IMediaSourceRepository mediaSourceRepository, IMediaSourceRepository mediaSourceRepository,
IMetadataRepository metadataRepository,
ILocalFileSystem localFileSystem, ILocalFileSystem localFileSystem,
ILocalStatisticsProvider localStatisticsProvider, ILocalStatisticsProvider localStatisticsProvider,
ILogger<EmbyMovieLibraryScanner> logger) ILogger<EmbyMovieLibraryScanner> logger)
@ -42,6 +45,7 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner
_searchRepository = searchRepository; _searchRepository = searchRepository;
_pathReplacementService = pathReplacementService; _pathReplacementService = pathReplacementService;
_mediaSourceRepository = mediaSourceRepository; _mediaSourceRepository = mediaSourceRepository;
_metadataRepository = metadataRepository;
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;
_localStatisticsProvider = localStatisticsProvider; _localStatisticsProvider = localStatisticsProvider;
_logger = logger; _logger = logger;
@ -179,6 +183,11 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner
incomingMovie, incomingMovie,
localPath); localPath);
if (refreshResult.Map(t => t).IfLeft(false))
{
refreshResult = await UpdateSubtitles(incomingMovie);
}
await refreshResult.Match( await refreshResult.Match(
async _ => async _ =>
{ {
@ -229,4 +238,45 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner
_searchIndex.Commit(); _searchIndex.Commit();
return Unit.Default; return Unit.Default;
} }
private async Task<Either<BaseError, bool>> 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<Subtitle>();
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;
}
} }

50
ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs

@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Emby; using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
@ -19,6 +20,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly IEmbyPathReplacementService _pathReplacementService; private readonly IEmbyPathReplacementService _pathReplacementService;
private readonly IMetadataRepository _metadataRepository;
private readonly ISearchIndex _searchIndex; private readonly ISearchIndex _searchIndex;
private readonly ISearchRepository _searchRepository; private readonly ISearchRepository _searchRepository;
private readonly IEmbyTelevisionRepository _televisionRepository; private readonly IEmbyTelevisionRepository _televisionRepository;
@ -30,6 +32,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
ISearchIndex searchIndex, ISearchIndex searchIndex,
ISearchRepository searchRepository, ISearchRepository searchRepository,
IEmbyPathReplacementService pathReplacementService, IEmbyPathReplacementService pathReplacementService,
IMetadataRepository metadataRepository,
ILocalFileSystem localFileSystem, ILocalFileSystem localFileSystem,
ILocalStatisticsProvider localStatisticsProvider, ILocalStatisticsProvider localStatisticsProvider,
IMediator mediator, IMediator mediator,
@ -41,6 +44,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
_searchIndex = searchIndex; _searchIndex = searchIndex;
_searchRepository = searchRepository; _searchRepository = searchRepository;
_pathReplacementService = pathReplacementService; _pathReplacementService = pathReplacementService;
_metadataRepository = metadataRepository;
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;
_localStatisticsProvider = localStatisticsProvider; _localStatisticsProvider = localStatisticsProvider;
_mediator = mediator; _mediator = mediator;
@ -425,6 +429,11 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
ffprobePath, ffprobePath,
incomingEpisode, incomingEpisode,
localPath); localPath);
if (refreshResult.Map(t => t).IfLeft(false))
{
refreshResult = await UpdateSubtitles(incomingEpisode);
}
refreshResult.Match( refreshResult.Match(
_ => { }, _ => { },
@ -436,4 +445,45 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
} }
} }
} }
private async Task<Either<BaseError, bool>> 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<Subtitle>();
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;
}
} }

50
ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs

@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Jellyfin; using ErsatzTV.Core.Interfaces.Jellyfin;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
@ -17,6 +18,7 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner
private readonly ILocalStatisticsProvider _localStatisticsProvider; private readonly ILocalStatisticsProvider _localStatisticsProvider;
private readonly ILogger<JellyfinMovieLibraryScanner> _logger; private readonly ILogger<JellyfinMovieLibraryScanner> _logger;
private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IMetadataRepository _metadataRepository;
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly IMovieRepository _movieRepository; private readonly IMovieRepository _movieRepository;
private readonly IJellyfinPathReplacementService _pathReplacementService; private readonly IJellyfinPathReplacementService _pathReplacementService;
@ -31,6 +33,7 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner
ISearchRepository searchRepository, ISearchRepository searchRepository,
IJellyfinPathReplacementService pathReplacementService, IJellyfinPathReplacementService pathReplacementService,
IMediaSourceRepository mediaSourceRepository, IMediaSourceRepository mediaSourceRepository,
IMetadataRepository metadataRepository,
ILocalFileSystem localFileSystem, ILocalFileSystem localFileSystem,
ILocalStatisticsProvider localStatisticsProvider, ILocalStatisticsProvider localStatisticsProvider,
ILogger<JellyfinMovieLibraryScanner> logger) ILogger<JellyfinMovieLibraryScanner> logger)
@ -42,6 +45,7 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner
_searchRepository = searchRepository; _searchRepository = searchRepository;
_pathReplacementService = pathReplacementService; _pathReplacementService = pathReplacementService;
_mediaSourceRepository = mediaSourceRepository; _mediaSourceRepository = mediaSourceRepository;
_metadataRepository = metadataRepository;
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;
_localStatisticsProvider = localStatisticsProvider; _localStatisticsProvider = localStatisticsProvider;
_logger = logger; _logger = logger;
@ -178,6 +182,11 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner
ffprobePath, ffprobePath,
incomingMovie, incomingMovie,
localPath); localPath);
if (refreshResult.Map(t => t).IfLeft(false))
{
refreshResult = await UpdateSubtitles(incomingMovie);
}
await refreshResult.Match( await refreshResult.Match(
async _ => async _ =>
@ -229,4 +238,45 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner
_searchIndex.Commit(); _searchIndex.Commit();
return Unit.Default; return Unit.Default;
} }
private async Task<Either<BaseError, bool>> 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<Subtitle>();
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;
}
} }

50
ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs

@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Jellyfin; using ErsatzTV.Core.Interfaces.Jellyfin;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
@ -19,6 +20,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne
private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly IJellyfinPathReplacementService _pathReplacementService; private readonly IJellyfinPathReplacementService _pathReplacementService;
private readonly IMetadataRepository _metadataRepository;
private readonly ISearchIndex _searchIndex; private readonly ISearchIndex _searchIndex;
private readonly ISearchRepository _searchRepository; private readonly ISearchRepository _searchRepository;
private readonly IJellyfinTelevisionRepository _televisionRepository; private readonly IJellyfinTelevisionRepository _televisionRepository;
@ -30,6 +32,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne
ISearchIndex searchIndex, ISearchIndex searchIndex,
ISearchRepository searchRepository, ISearchRepository searchRepository,
IJellyfinPathReplacementService pathReplacementService, IJellyfinPathReplacementService pathReplacementService,
IMetadataRepository metadataRepository,
ILocalFileSystem localFileSystem, ILocalFileSystem localFileSystem,
ILocalStatisticsProvider localStatisticsProvider, ILocalStatisticsProvider localStatisticsProvider,
IMediator mediator, IMediator mediator,
@ -41,6 +44,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne
_searchIndex = searchIndex; _searchIndex = searchIndex;
_searchRepository = searchRepository; _searchRepository = searchRepository;
_pathReplacementService = pathReplacementService; _pathReplacementService = pathReplacementService;
_metadataRepository = metadataRepository;
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;
_localStatisticsProvider = localStatisticsProvider; _localStatisticsProvider = localStatisticsProvider;
_mediator = mediator; _mediator = mediator;
@ -427,6 +431,11 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne
ffprobePath, ffprobePath,
incomingEpisode, incomingEpisode,
localPath); localPath);
if (refreshResult.Map(t => t).IfLeft(false))
{
refreshResult = await UpdateSubtitles(incomingEpisode);
}
refreshResult.Match( refreshResult.Match(
_ => { }, _ => { },
@ -438,4 +447,45 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne
} }
} }
} }
private async Task<Either<BaseError, bool>> 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<Subtitle>();
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;
}
} }

46
ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs

@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
@ -97,6 +98,7 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan
.BindT( .BindT(
existing => UpdateStatistics(pathReplacements, existing, incoming, ffmpegPath, ffprobePath)) existing => UpdateStatistics(pathReplacements, existing, incoming, ffmpegPath, ffprobePath))
.BindT(existing => UpdateMetadata(existing, incoming, library, connection, token)) .BindT(existing => UpdateMetadata(existing, incoming, library, connection, token))
.BindT(UpdateSubtitles)
.BindT(existing => UpdateArtwork(existing, incoming)); .BindT(existing => UpdateArtwork(existing, incoming));
await maybeMovie.Match( await maybeMovie.Match(
@ -406,6 +408,50 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan
return result; return result;
} }
private async Task<Either<BaseError, MediaItemScanResult<PlexMovie>>> UpdateSubtitles(
MediaItemScanResult<PlexMovie> 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<Subtitle>();
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<Either<BaseError, MediaItemScanResult<PlexMovie>>> UpdateArtwork( private async Task<Either<BaseError, MediaItemScanResult<PlexMovie>>> UpdateArtwork(
MediaItemScanResult<PlexMovie> result, MediaItemScanResult<PlexMovie> result,
PlexMovie incoming) PlexMovie incoming)

43
ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs

@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
@ -444,6 +445,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL
token, token,
ffmpegPath, ffmpegPath,
ffprobePath)) ffprobePath))
.BindT(UpdateSubtitles)
.BindT(existing => UpdateArtwork(existing, incoming)); .BindT(existing => UpdateArtwork(existing, incoming));
await maybeEpisode.Match( await maybeEpisode.Match(
@ -615,6 +617,47 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL
return Right<BaseError, PlexEpisode>(existing); return Right<BaseError, PlexEpisode>(existing);
} }
private async Task<Either<BaseError, PlexEpisode>> 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<Subtitle>();
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<Either<BaseError, PlexEpisode>> UpdateArtwork(PlexEpisode existing, PlexEpisode incoming) private async Task<Either<BaseError, PlexEpisode>> UpdateArtwork(PlexEpisode existing, PlexEpisode incoming)
{ {

Loading…
Cancel
Save