From 9c02a6738bda750c0148ea3c482a7e6a02012488 Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Wed, 29 Jun 2022 15:01:49 -0500 Subject: [PATCH] fix missing trashed episodes (#881) * fix episodes missing from trash * cleanup --- .../Commands/MoveLocalLibraryPathHandler.cs | 11 ++++- .../Commands/AddTraktListHandler.cs | 4 +- .../Commands/DeleteTraktListHandler.cs | 11 +++-- .../Commands/MatchTraktListItemsHandler.cs | 9 +++- .../Commands/TraktCommandBase.cs | 6 ++- .../Commands/RebuildSearchIndexHandler.cs | 5 +- .../QuerySearchIndexEpisodesHandler.cs | 48 +++++++++++++++++-- .../Metadata/FallbackMetadataProviderTests.cs | 7 +++ .../Metadata/MovieFolderScannerTests.cs | 2 + ErsatzTV.Core/Emby/EmbyCollectionScanner.cs | 6 ++- ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs | 2 + .../Emby/EmbyTelevisionLibraryScanner.cs | 2 + .../Interfaces/Search/ISearchIndex.cs | 15 ++++-- .../Jellyfin/JellyfinCollectionScanner.cs | 6 ++- .../Jellyfin/JellyfinMovieLibraryScanner.cs | 2 + .../JellyfinTelevisionLibraryScanner.cs | 2 + .../Metadata/FallbackMetadataProvider.cs | 8 +++- .../MediaServerMovieLibraryScanner.cs | 12 +++-- .../MediaServerTelevisionLibraryScanner.cs | 26 +++++++--- ErsatzTV.Core/Metadata/MovieFolderScanner.cs | 10 +++- .../Metadata/MusicVideoFolderScanner.cs | 15 ++++-- .../Metadata/OtherVideoFolderScanner.cs | 10 +++- ErsatzTV.Core/Metadata/SongFolderScanner.cs | 10 +++- .../Metadata/TelevisionFolderScanner.cs | 21 ++++++-- ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs | 2 + .../Plex/PlexTelevisionLibraryScanner.cs | 2 + .../NvidiaHardwareCapabilities.cs | 3 +- .../Data/Repositories/SearchRepository.cs | 26 +++++++++- ErsatzTV.Infrastructure/Search/SearchIndex.cs | 45 +++++++++++++---- 29 files changed, 272 insertions(+), 56 deletions(-) diff --git a/ErsatzTV.Application/Libraries/Commands/MoveLocalLibraryPathHandler.cs b/ErsatzTV.Application/Libraries/Commands/MoveLocalLibraryPathHandler.cs index 14f86f6b..e02bfae6 100644 --- a/ErsatzTV.Application/Libraries/Commands/MoveLocalLibraryPathHandler.cs +++ b/ErsatzTV.Application/Libraries/Commands/MoveLocalLibraryPathHandler.cs @@ -1,6 +1,7 @@ using Dapper; using ErsatzTV.Core; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Search; using ErsatzTV.Infrastructure.Data; @@ -13,6 +14,7 @@ namespace ErsatzTV.Application.Libraries; public class MoveLocalLibraryPathHandler : IRequestHandler> { private readonly IDbContextFactory _dbContextFactory; + private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly ILogger _logger; private readonly ISearchIndex _searchIndex; private readonly ISearchRepository _searchRepository; @@ -20,11 +22,13 @@ public class MoveLocalLibraryPathHandler : IRequestHandler dbContextFactory, ILogger logger) { _searchIndex = searchIndex; _searchRepository = searchRepository; + _fallbackMetadataProvider = fallbackMetadataProvider; _dbContextFactory = dbContextFactory; _logger = logger; } @@ -35,7 +39,7 @@ public class MoveLocalLibraryPathHandler : IRequestHandler validation = await Validate(dbContext, request); - return await LanguageExtensions.Apply(validation, parameters => MovePath(dbContext, parameters)); + return await validation.Apply(parameters => MovePath(dbContext, parameters)); } private async Task MovePath(TvContext dbContext, Parameters parameters) @@ -57,7 +61,10 @@ public class MoveLocalLibraryPathHandler : IRequestHandler { mediaItem }); + await _searchIndex.UpdateItems( + _searchRepository, + _fallbackMetadataProvider, + new List { mediaItem }); } } } diff --git a/ErsatzTV.Application/MediaCollections/Commands/AddTraktListHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/AddTraktListHandler.cs index 9f5083e9..4ae9cd25 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/AddTraktListHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/AddTraktListHandler.cs @@ -1,6 +1,7 @@ using System.Text.RegularExpressions; using ErsatzTV.Core; using ErsatzTV.Core.Interfaces.Locking; +using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Search; using ErsatzTV.Core.Interfaces.Trakt; @@ -19,10 +20,11 @@ public class AddTraktListHandler : TraktCommandBase, IRequestHandler dbContextFactory, ILogger logger, IEntityLocker entityLocker) - : base(traktApiClient, searchRepository, searchIndex, logger) + : base(traktApiClient, searchRepository, searchIndex, fallbackMetadataProvider, logger) { _dbContextFactory = dbContextFactory; _entityLocker = entityLocker; diff --git a/ErsatzTV.Application/MediaCollections/Commands/DeleteTraktListHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/DeleteTraktListHandler.cs index a038a45b..4e9577e4 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/DeleteTraktListHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/DeleteTraktListHandler.cs @@ -1,6 +1,7 @@ using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Locking; +using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Search; using ErsatzTV.Core.Interfaces.Trakt; @@ -14,6 +15,7 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler _dbContextFactory; private readonly IEntityLocker _entityLocker; + private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly ISearchIndex _searchIndex; private readonly ISearchRepository _searchRepository; @@ -21,13 +23,15 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler dbContextFactory, ILogger logger, IEntityLocker entityLocker) - : base(traktApiClient, searchRepository, searchIndex, logger) + : base(traktApiClient, searchRepository, searchIndex, fallbackMetadataProvider, logger) { _searchRepository = searchRepository; _searchIndex = searchIndex; + _fallbackMetadataProvider = fallbackMetadataProvider; _dbContextFactory = dbContextFactory; _entityLocker = entityLocker; } @@ -38,8 +42,7 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler validation = await TraktListMustExist(dbContext, request.TraktListId); return await LanguageExtensions.Apply(validation, c => DoDeletion(dbContext, c)); } @@ -56,7 +59,7 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler 0) { - await _searchIndex.RebuildItems(_searchRepository, mediaItemIds); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, mediaItemIds); } _searchIndex.Commit(); diff --git a/ErsatzTV.Application/MediaCollections/Commands/MatchTraktListItemsHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/MatchTraktListItemsHandler.cs index ef5355ef..fad2d2f1 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/MatchTraktListItemsHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/MatchTraktListItemsHandler.cs @@ -1,6 +1,7 @@ using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Locking; +using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Search; using ErsatzTV.Core.Interfaces.Trakt; @@ -20,9 +21,15 @@ public class MatchTraktListItemsHandler : TraktCommandBase, ITraktApiClient traktApiClient, ISearchRepository searchRepository, ISearchIndex searchIndex, + IFallbackMetadataProvider fallbackMetadataProvider, IDbContextFactory dbContextFactory, ILogger logger, - IEntityLocker entityLocker) : base(traktApiClient, searchRepository, searchIndex, logger) + IEntityLocker entityLocker) : base( + traktApiClient, + searchRepository, + searchIndex, + fallbackMetadataProvider, + logger) { _dbContextFactory = dbContextFactory; _entityLocker = entityLocker; diff --git a/ErsatzTV.Application/MediaCollections/Commands/TraktCommandBase.cs b/ErsatzTV.Application/MediaCollections/Commands/TraktCommandBase.cs index 633814be..5e407808 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/TraktCommandBase.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/TraktCommandBase.cs @@ -1,5 +1,6 @@ using ErsatzTV.Core; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Search; using ErsatzTV.Core.Interfaces.Trakt; @@ -13,6 +14,7 @@ namespace ErsatzTV.Application.MediaCollections; public abstract class TraktCommandBase { + private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly ILogger _logger; private readonly ISearchIndex _searchIndex; private readonly ISearchRepository _searchRepository; @@ -21,10 +23,12 @@ public abstract class TraktCommandBase ITraktApiClient traktApiClient, ISearchRepository searchRepository, ISearchIndex searchIndex, + IFallbackMetadataProvider fallbackMetadataProvider, ILogger logger) { _searchRepository = searchRepository; _searchIndex = searchIndex; + _fallbackMetadataProvider = fallbackMetadataProvider; _logger = logger; TraktApiClient = traktApiClient; } @@ -158,7 +162,7 @@ public abstract class TraktCommandBase if (await dbContext.SaveChangesAsync() > 0) { - await _searchIndex.RebuildItems(_searchRepository, ids.ToList()); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, ids.ToList()); } _searchIndex.Commit(); diff --git a/ErsatzTV.Application/Search/Commands/RebuildSearchIndexHandler.cs b/ErsatzTV.Application/Search/Commands/RebuildSearchIndexHandler.cs index 94ab3428..9637cf68 100644 --- a/ErsatzTV.Application/Search/Commands/RebuildSearchIndexHandler.cs +++ b/ErsatzTV.Application/Search/Commands/RebuildSearchIndexHandler.cs @@ -12,6 +12,7 @@ namespace ErsatzTV.Application.Search; public class RebuildSearchIndexHandler : IRequestHandler { private readonly IConfigElementRepository _configElementRepository; + private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly ILocalFileSystem _localFileSystem; private readonly ILogger _logger; private readonly ISearchIndex _searchIndex; @@ -22,6 +23,7 @@ public class RebuildSearchIndexHandler : IRequestHandler logger) { _searchIndex = searchIndex; @@ -29,6 +31,7 @@ public class RebuildSearchIndexHandler : IRequestHandler Handle(RebuildSearchIndex request, CancellationToken cancellationToken) @@ -48,7 +51,7 @@ public class RebuildSearchIndexHandler : IRequestHandler { + private readonly IDbContextFactory _dbContextFactory; private readonly IEmbyPathReplacementService _embyPathReplacementService; + private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly IJellyfinPathReplacementService _jellyfinPathReplacementService; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IPlexPathReplacementService _plexPathReplacementService; @@ -27,7 +33,9 @@ public class IMediaSourceRepository mediaSourceRepository, IPlexPathReplacementService plexPathReplacementService, IJellyfinPathReplacementService jellyfinPathReplacementService, - IEmbyPathReplacementService embyPathReplacementService) + IEmbyPathReplacementService embyPathReplacementService, + IFallbackMetadataProvider fallbackMetadataProvider, + IDbContextFactory dbContextFactory) { _searchIndex = searchIndex; _televisionRepository = televisionRepository; @@ -35,6 +43,8 @@ public class _plexPathReplacementService = plexPathReplacementService; _jellyfinPathReplacementService = jellyfinPathReplacementService; _embyPathReplacementService = embyPathReplacementService; + _fallbackMetadataProvider = fallbackMetadataProvider; + _dbContextFactory = dbContextFactory; } public async Task Handle( @@ -52,8 +62,40 @@ public class Option maybeEmby = await _mediaSourceRepository.GetAllEmby() .Map(list => list.HeadOrNone()); - List episodes = await _televisionRepository - .GetEpisodesForCards(searchResult.Items.Map(i => i.Id).ToList()); + var episodeIds = searchResult.Items.Map(i => i.Id).ToList(); + + List episodes = await _televisionRepository.GetEpisodesForCards(episodeIds); + + // try to load fallback metadata for episodes that have none + // this handles an edge case of trashed items with no saved metadata + var missingEpisodes = episodeIds.Except(episodes.Map(e => e.EpisodeId)).ToList(); + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + foreach (int missingEpisodeId in missingEpisodes) + { + Option maybeEpisode = await dbContext.Episodes + .AsNoTracking() + .Include(e => e.MediaVersions) + .ThenInclude(e => e.MediaFiles) + .Include(e => e.Season) + .ThenInclude(s => s.SeasonMetadata) + .ThenInclude(sm => sm.Artwork) + .Include(e => e.Season) + .ThenInclude(s => s.Show) + .ThenInclude(s => s.ShowMetadata) + .ThenInclude(sm => sm.Artwork) + .SelectOneAsync(e => e.Id, e => e.Id == missingEpisodeId); + + foreach (Episode episode in maybeEpisode) + { + foreach (EpisodeMetadata headMetadata in _fallbackMetadataProvider.GetFallbackMetadata(episode) + .HeadOrNone()) + { + headMetadata.Episode = episode; + episode.EpisodeMetadata = new List { headMetadata }; + episodes.Add(headMetadata); + } + } + } var items = new List(); diff --git a/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs b/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs index f5c42518..59535bac 100644 --- a/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs +++ b/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs @@ -40,6 +40,13 @@ public class FallbackMetadataProviderTests "Awesome.Show.S01E02.Description.more.Description.QUAlity.codec.CODEC-GROUP.mkv", 1, 2)] + [TestCase("Awesome Show - s01.e02.mkv", 1, 2)] + [TestCase("Awesome Show - S01.E02.mkv", 1, 2)] + [TestCase("Awesome Show - s01_e02.mkv", 1, 2)] + [TestCase("Awesome Show - S01_E02.mkv", 1, 2)] + [TestCase("Awesome Show - s01xe02.mkv", 1, 2)] + [TestCase("Awesome Show - S01XE02.mkv", 1, 2)] + [TestCase("Awesome Show - 1x02.mkv", 1, 2)] public void GetFallbackMetadata_ShouldHandleVariousFormats(string path, int season, int episode) { List metadata = _fallbackMetadataProvider.GetFallbackMetadata( diff --git a/ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs b/ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs index a20e86b8..84d40d75 100644 --- a/ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs +++ b/ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs @@ -623,6 +623,7 @@ public class MovieFolderScannerTests _imageCache.Object, new Mock().Object, new Mock().Object, + new Mock().Object, new Mock().Object, _mediaItemRepository.Object, new Mock().Object, @@ -643,6 +644,7 @@ public class MovieFolderScannerTests _imageCache.Object, new Mock().Object, new Mock().Object, + new Mock().Object, new Mock().Object, _mediaItemRepository.Object, new Mock().Object, diff --git a/ErsatzTV.Core/Emby/EmbyCollectionScanner.cs b/ErsatzTV.Core/Emby/EmbyCollectionScanner.cs index 3799a56e..85ed6c6d 100644 --- a/ErsatzTV.Core/Emby/EmbyCollectionScanner.cs +++ b/ErsatzTV.Core/Emby/EmbyCollectionScanner.cs @@ -1,5 +1,6 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Emby; +using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Search; using Microsoft.Extensions.Logging; @@ -10,6 +11,7 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner { private readonly IEmbyApiClient _embyApiClient; private readonly IEmbyCollectionRepository _embyCollectionRepository; + private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly ILogger _logger; private readonly ISearchIndex _searchIndex; private readonly ISearchRepository _searchRepository; @@ -19,12 +21,14 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner IEmbyApiClient embyApiClient, ISearchRepository searchRepository, ISearchIndex searchIndex, + IFallbackMetadataProvider fallbackMetadataProvider, ILogger logger) { _embyCollectionRepository = embyCollectionRepository; _embyApiClient = embyApiClient; _searchRepository = searchRepository; _searchIndex = searchIndex; + _fallbackMetadataProvider = fallbackMetadataProvider; _logger = logger; } @@ -107,7 +111,7 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner var changedIds = removedIds.Except(addedIds).ToList(); changedIds.AddRange(addedIds.Except(removedIds)); - await _searchIndex.RebuildItems(_searchRepository, changedIds); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, changedIds); _searchIndex.Commit(); } catch (Exception ex) diff --git a/ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs b/ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs index 4a95ea67..4f6ffaa6 100644 --- a/ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs +++ b/ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs @@ -26,6 +26,7 @@ public class EmbyMovieLibraryScanner : IMediaSourceRepository mediaSourceRepository, IEmbyMovieRepository embyMovieRepository, ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, IEmbyPathReplacementService pathReplacementService, ILocalFileSystem localFileSystem, ILocalStatisticsProvider localStatisticsProvider, @@ -38,6 +39,7 @@ public class EmbyMovieLibraryScanner : mediator, searchIndex, searchRepository, + fallbackMetadataProvider, logger) { _embyApiClient = embyApiClient; diff --git a/ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs b/ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs index e2744ab0..614dd7d2 100644 --- a/ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs +++ b/ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs @@ -25,6 +25,7 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner< IEmbyTelevisionRepository televisionRepository, ISearchIndex searchIndex, ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, IEmbyPathReplacementService pathReplacementService, ILocalFileSystem localFileSystem, ILocalStatisticsProvider localStatisticsProvider, @@ -37,6 +38,7 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner< localFileSystem, searchRepository, searchIndex, + fallbackMetadataProvider, mediator, logger) { diff --git a/ErsatzTV.Core/Interfaces/Search/ISearchIndex.cs b/ErsatzTV.Core/Interfaces/Search/ISearchIndex.cs index 329c9bb0..94550300 100644 --- a/ErsatzTV.Core/Interfaces/Search/ISearchIndex.cs +++ b/ErsatzTV.Core/Interfaces/Search/ISearchIndex.cs @@ -9,9 +9,18 @@ public interface ISearchIndex : IDisposable { public int Version { get; } Task Initialize(ILocalFileSystem localFileSystem, IConfigElementRepository configElementRepository); - Task Rebuild(ISearchRepository searchRepository); - Task RebuildItems(ISearchRepository searchRepository, List itemIds); - Task UpdateItems(ISearchRepository searchRepository, List items); + Task Rebuild(ISearchRepository searchRepository, IFallbackMetadataProvider fallbackMetadataProvider); + + Task RebuildItems( + ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, + List itemIds); + + Task UpdateItems( + ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, + List items); + Task RemoveItems(List ids); Task Search(string query, int skip, int limit, string searchField = ""); void Commit(); diff --git a/ErsatzTV.Core/Jellyfin/JellyfinCollectionScanner.cs b/ErsatzTV.Core/Jellyfin/JellyfinCollectionScanner.cs index 28e5e99d..d7776f66 100644 --- a/ErsatzTV.Core/Jellyfin/JellyfinCollectionScanner.cs +++ b/ErsatzTV.Core/Jellyfin/JellyfinCollectionScanner.cs @@ -1,5 +1,6 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Jellyfin; +using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Search; using Microsoft.Extensions.Logging; @@ -8,6 +9,7 @@ namespace ErsatzTV.Core.Jellyfin; public class JellyfinCollectionScanner : IJellyfinCollectionScanner { + private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly IJellyfinApiClient _jellyfinApiClient; private readonly IJellyfinCollectionRepository _jellyfinCollectionRepository; private readonly ILogger _logger; @@ -19,12 +21,14 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner IJellyfinApiClient jellyfinApiClient, ISearchRepository searchRepository, ISearchIndex searchIndex, + IFallbackMetadataProvider fallbackMetadataProvider, ILogger logger) { _jellyfinCollectionRepository = jellyfinCollectionRepository; _jellyfinApiClient = jellyfinApiClient; _searchRepository = searchRepository; _searchIndex = searchIndex; + _fallbackMetadataProvider = fallbackMetadataProvider; _logger = logger; } @@ -116,7 +120,7 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner var changedIds = removedIds.Except(addedIds).ToList(); changedIds.AddRange(addedIds.Except(removedIds)); - await _searchIndex.RebuildItems(_searchRepository, changedIds); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, changedIds); _searchIndex.Commit(); } catch (Exception ex) diff --git a/ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs b/ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs index b4aec737..cc873443 100644 --- a/ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs +++ b/ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs @@ -25,6 +25,7 @@ public class JellyfinMovieLibraryScanner : IMediator mediator, IJellyfinMovieRepository jellyfinMovieRepository, ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, IJellyfinPathReplacementService pathReplacementService, IMediaSourceRepository mediaSourceRepository, ILocalFileSystem localFileSystem, @@ -38,6 +39,7 @@ public class JellyfinMovieLibraryScanner : mediator, searchIndex, searchRepository, + fallbackMetadataProvider, logger) { _jellyfinApiClient = jellyfinApiClient; diff --git a/ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs b/ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs index 6b0a048e..efc96d5f 100644 --- a/ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs +++ b/ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs @@ -26,6 +26,7 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan IJellyfinTelevisionRepository televisionRepository, ISearchIndex searchIndex, ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, IJellyfinPathReplacementService pathReplacementService, ILocalFileSystem localFileSystem, ILocalStatisticsProvider localStatisticsProvider, @@ -38,6 +39,7 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan localFileSystem, searchRepository, searchIndex, + fallbackMetadataProvider, mediator, logger) { diff --git a/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs index d2ec4202..f4e01e99 100644 --- a/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs +++ b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs @@ -161,8 +161,14 @@ public class FallbackMetadataProvider : IFallbackMetadataProvider try { - const string PATTERN = @"[sS]\d+[eE]([e\-\d{1,2}]+)"; + const string PATTERN = @"[sS]\d+[\._xX]?[eE]([e\-\d{1,2}]+)"; + const string PATTERN_2 = @"\d+[\._xX]([e\-\d{1,2}]+)"; MatchCollection matches = Regex.Matches(fileName, PATTERN); + if (matches.Count == 0) + { + matches = Regex.Matches(fileName, PATTERN_2); + } + if (matches.Count > 0) { foreach (Match match in matches) diff --git a/ErsatzTV.Core/Metadata/MediaServerMovieLibraryScanner.cs b/ErsatzTV.Core/Metadata/MediaServerMovieLibraryScanner.cs index fb19b9e8..5cf31c87 100644 --- a/ErsatzTV.Core/Metadata/MediaServerMovieLibraryScanner.cs +++ b/ErsatzTV.Core/Metadata/MediaServerMovieLibraryScanner.cs @@ -15,6 +15,7 @@ public abstract class MediaServerMovieLibraryScanner { result.Item.Id }); + await _searchIndex.RebuildItems( + _searchRepository, + _fallbackMetadataProvider, + new List { result.Item.Id }); } } } @@ -176,7 +182,7 @@ public abstract class MediaServerMovieLibraryScanner m.MediaServerItemId).Except(incomingItemIds).ToList(); List ids = await movieRepository.FlagFileNotFound(library, fileNotFoundItemIds); - await _searchIndex.RebuildItems(_searchRepository, ids); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, ids); await _mediator.Publish(new LibraryScanProgress(library.Id, 0), cancellationToken); @@ -241,7 +247,7 @@ public abstract class MediaServerMovieLibraryScanner { id }); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, new List { id }); } } diff --git a/ErsatzTV.Core/Metadata/MediaServerTelevisionLibraryScanner.cs b/ErsatzTV.Core/Metadata/MediaServerTelevisionLibraryScanner.cs index c8f4722b..badf7d6f 100644 --- a/ErsatzTV.Core/Metadata/MediaServerTelevisionLibraryScanner.cs +++ b/ErsatzTV.Core/Metadata/MediaServerTelevisionLibraryScanner.cs @@ -18,6 +18,7 @@ public abstract class MediaServerTelevisionLibraryScanner { result.Item.Id }); + await _searchIndex.RebuildItems( + _searchRepository, + _fallbackMetadataProvider, + new List { result.Item.Id }); } } } @@ -204,7 +210,7 @@ public abstract class MediaServerTelevisionLibraryScanner s.MediaServerItemId).Except(incomingItemIds).ToList(); List ids = await televisionRepository.FlagFileNotFoundShows(library, fileNotFoundItemIds); - await _searchIndex.RebuildItems(_searchRepository, ids); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, ids); await _mediator.Publish(new LibraryScanProgress(library.Id, 0), cancellationToken); @@ -358,7 +364,10 @@ public abstract class MediaServerTelevisionLibraryScanner { result.Item.Id }); + await _searchIndex.RebuildItems( + _searchRepository, + _fallbackMetadataProvider, + new List { result.Item.Id }); } } } @@ -366,7 +375,7 @@ public abstract class MediaServerTelevisionLibraryScanner s.MediaServerItemId).Except(incomingItemIds).ToList(); List ids = await televisionRepository.FlagFileNotFoundSeasons(library, fileNotFoundItemIds); - await _searchIndex.RebuildItems(_searchRepository, ids); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, ids); return Unit.Default; } @@ -461,7 +470,10 @@ public abstract class MediaServerTelevisionLibraryScanner { result.Item.Id }); + await _searchIndex.RebuildItems( + _searchRepository, + _fallbackMetadataProvider, + new List { result.Item.Id }); } } } @@ -469,7 +481,7 @@ public abstract class MediaServerTelevisionLibraryScanner m.MediaServerItemId).Except(incomingItemIds).ToList(); List ids = await televisionRepository.FlagFileNotFoundEpisodes(library, fileNotFoundItemIds); - await _searchIndex.RebuildItems(_searchRepository, ids); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, ids); return Unit.Default; } @@ -511,7 +523,7 @@ public abstract class MediaServerTelevisionLibraryScanner { id }); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, new List { id }); } } diff --git a/ErsatzTV.Core/Metadata/MovieFolderScanner.cs b/ErsatzTV.Core/Metadata/MovieFolderScanner.cs index e0882693..751f6ff0 100644 --- a/ErsatzTV.Core/Metadata/MovieFolderScanner.cs +++ b/ErsatzTV.Core/Metadata/MovieFolderScanner.cs @@ -15,6 +15,7 @@ namespace ErsatzTV.Core.Metadata; public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner { private readonly IClient _client; + private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly ILibraryRepository _libraryRepository; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalMetadataProvider _localMetadataProvider; @@ -35,6 +36,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner IImageCache imageCache, ISearchIndex searchIndex, ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, ILibraryRepository libraryRepository, IMediaItemRepository mediaItemRepository, IMediator mediator, @@ -59,6 +61,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner _localMetadataProvider = localMetadataProvider; _searchIndex = searchIndex; _searchRepository = searchRepository; + _fallbackMetadataProvider = fallbackMetadataProvider; _libraryRepository = libraryRepository; _mediator = mediator; _client = client; @@ -160,7 +163,10 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner { if (result.IsAdded || result.IsUpdated) { - await _searchIndex.RebuildItems(_searchRepository, new List { result.Item.Id }); + await _searchIndex.RebuildItems( + _searchRepository, + _fallbackMetadataProvider, + new List { result.Item.Id }); } await _libraryRepository.SetEtag(libraryPath, knownFolder, movieFolder, etag); @@ -174,7 +180,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner { _logger.LogInformation("Flagging missing movie at {Path}", path); List ids = await FlagFileNotFound(libraryPath, path); - await _searchIndex.RebuildItems(_searchRepository, ids); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, ids); } else if (Path.GetFileName(path).StartsWith("._")) { diff --git a/ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs b/ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs index 0b9bc364..f95e9f8f 100644 --- a/ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs +++ b/ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs @@ -15,6 +15,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan { private readonly IArtistRepository _artistRepository; private readonly IClient _client; + private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly ILibraryRepository _libraryRepository; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalMetadataProvider _localMetadataProvider; @@ -34,6 +35,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan IImageCache imageCache, ISearchIndex searchIndex, ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, IArtistRepository artistRepository, IMusicVideoRepository musicVideoRepository, ILibraryRepository libraryRepository, @@ -58,6 +60,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan _localSubtitlesProvider = localSubtitlesProvider; _searchIndex = searchIndex; _searchRepository = searchRepository; + _fallbackMetadataProvider = fallbackMetadataProvider; _artistRepository = artistRepository; _musicVideoRepository = musicVideoRepository; _libraryRepository = libraryRepository; @@ -137,7 +140,10 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan if (result.IsAdded || result.IsUpdated) { - await _searchIndex.RebuildItems(_searchRepository, new List { result.Item.Id }); + await _searchIndex.RebuildItems( + _searchRepository, + _fallbackMetadataProvider, + new List { result.Item.Id }); } } } @@ -155,7 +161,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan { _logger.LogInformation("Flagging missing music video at {Path}", path); List musicVideoIds = await FlagFileNotFound(libraryPath, path); - await _searchIndex.RebuildItems(_searchRepository, musicVideoIds); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, musicVideoIds); } else if (Path.GetFileName(path).StartsWith("._")) { @@ -330,7 +336,10 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan { if (result.IsAdded || result.IsUpdated) { - await _searchIndex.RebuildItems(_searchRepository, new List { result.Item.Id }); + await _searchIndex.RebuildItems( + _searchRepository, + _fallbackMetadataProvider, + new List { result.Item.Id }); } } } diff --git a/ErsatzTV.Core/Metadata/OtherVideoFolderScanner.cs b/ErsatzTV.Core/Metadata/OtherVideoFolderScanner.cs index 9ac3bace..3b049f24 100644 --- a/ErsatzTV.Core/Metadata/OtherVideoFolderScanner.cs +++ b/ErsatzTV.Core/Metadata/OtherVideoFolderScanner.cs @@ -14,6 +14,7 @@ namespace ErsatzTV.Core.Metadata; public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScanner { private readonly IClient _client; + private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly ILibraryRepository _libraryRepository; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalMetadataProvider _localMetadataProvider; @@ -34,6 +35,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan IMediator mediator, ISearchIndex searchIndex, ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, IOtherVideoRepository otherVideoRepository, ILibraryRepository libraryRepository, IMediaItemRepository mediaItemRepository, @@ -57,6 +59,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan _mediator = mediator; _searchIndex = searchIndex; _searchRepository = searchRepository; + _fallbackMetadataProvider = fallbackMetadataProvider; _otherVideoRepository = otherVideoRepository; _libraryRepository = libraryRepository; _client = client; @@ -160,7 +163,10 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan { if (result.IsAdded || result.IsUpdated) { - await _searchIndex.RebuildItems(_searchRepository, new List { result.Item.Id }); + await _searchIndex.RebuildItems( + _searchRepository, + _fallbackMetadataProvider, + new List { result.Item.Id }); } } } @@ -178,7 +184,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan { _logger.LogInformation("Flagging missing other video at {Path}", path); List otherVideoIds = await FlagFileNotFound(libraryPath, path); - await _searchIndex.RebuildItems(_searchRepository, otherVideoIds); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, otherVideoIds); } else if (Path.GetFileName(path).StartsWith("._")) { diff --git a/ErsatzTV.Core/Metadata/SongFolderScanner.cs b/ErsatzTV.Core/Metadata/SongFolderScanner.cs index 9ea12e35..e2ba7683 100644 --- a/ErsatzTV.Core/Metadata/SongFolderScanner.cs +++ b/ErsatzTV.Core/Metadata/SongFolderScanner.cs @@ -15,6 +15,7 @@ namespace ErsatzTV.Core.Metadata; public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner { private readonly IClient _client; + private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly ILibraryRepository _libraryRepository; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalMetadataProvider _localMetadataProvider; @@ -33,6 +34,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner IMediator mediator, ISearchIndex searchIndex, ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, ISongRepository songRepository, ILibraryRepository libraryRepository, IMediaItemRepository mediaItemRepository, @@ -55,6 +57,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner _mediator = mediator; _searchIndex = searchIndex; _searchRepository = searchRepository; + _fallbackMetadataProvider = fallbackMetadataProvider; _songRepository = songRepository; _libraryRepository = libraryRepository; _client = client; @@ -155,7 +158,10 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner { if (result.IsAdded || result.IsUpdated) { - await _searchIndex.RebuildItems(_searchRepository, new List { result.Item.Id }); + await _searchIndex.RebuildItems( + _searchRepository, + _fallbackMetadataProvider, + new List { result.Item.Id }); } } } @@ -173,7 +179,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner { _logger.LogInformation("Flagging missing song at {Path}", path); List songIds = await FlagFileNotFound(libraryPath, path); - await _searchIndex.RebuildItems(_searchRepository, songIds); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, songIds); } else if (Path.GetFileName(path).StartsWith("._")) { diff --git a/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs b/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs index fc41f240..5670ed99 100644 --- a/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs +++ b/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs @@ -14,6 +14,7 @@ namespace ErsatzTV.Core.Metadata; public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScanner { private readonly IClient _client; + private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly ILibraryRepository _libraryRepository; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalMetadataProvider _localMetadataProvider; @@ -30,6 +31,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan ITelevisionRepository televisionRepository, ILocalStatisticsProvider localStatisticsProvider, ILocalMetadataProvider localMetadataProvider, + IFallbackMetadataProvider fallbackMetadataProvider, ILocalSubtitlesProvider localSubtitlesProvider, IMetadataRepository metadataRepository, IImageCache imageCache, @@ -55,6 +57,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan _localFileSystem = localFileSystem; _televisionRepository = televisionRepository; _localMetadataProvider = localMetadataProvider; + _fallbackMetadataProvider = fallbackMetadataProvider; _localSubtitlesProvider = localSubtitlesProvider; _metadataRepository = metadataRepository; _searchIndex = searchIndex; @@ -127,7 +130,10 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan if (result.IsAdded || result.IsUpdated) { - await _searchIndex.RebuildItems(_searchRepository, new List { result.Item.Id }); + await _searchIndex.RebuildItems( + _searchRepository, + _fallbackMetadataProvider, + new List { result.Item.Id }); } } } @@ -137,8 +143,9 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan if (!_localFileSystem.FileExists(path)) { _logger.LogInformation("Flagging missing episode at {Path}", path); + List episodeIds = await FlagFileNotFound(libraryPath, path); - await _searchIndex.RebuildItems(_searchRepository, episodeIds); + await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, episodeIds); } else if (Path.GetFileName(path).StartsWith("._")) { @@ -241,7 +248,10 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan await _libraryRepository.SetEtag(libraryPath, knownFolder, seasonFolder, etag); season.Show = show; - await _searchIndex.RebuildItems(_searchRepository, new List { season.Id }); + await _searchIndex.RebuildItems( + _searchRepository, + _fallbackMetadataProvider, + new List { season.Id }); } } } @@ -287,7 +297,10 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan foreach (Episode episode in maybeEpisode.RightToSeq()) { - await _searchIndex.RebuildItems(_searchRepository, new List { episode.Id }); + await _searchIndex.RebuildItems( + _searchRepository, + _fallbackMetadataProvider, + new List { episode.Id }); } } diff --git a/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs b/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs index c1dfde11..92bf69d1 100644 --- a/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs +++ b/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs @@ -28,6 +28,7 @@ public class PlexMovieLibraryScanner : IMetadataRepository metadataRepository, ISearchIndex searchIndex, ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, IMediator mediator, IMediaSourceRepository mediaSourceRepository, IPlexMovieRepository plexMovieRepository, @@ -43,6 +44,7 @@ public class PlexMovieLibraryScanner : mediator, searchIndex, searchRepository, + fallbackMetadataProvider, logger) { _plexServerApiClient = plexServerApiClient; diff --git a/ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs b/ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs index cd68d4e7..51b792c3 100644 --- a/ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs +++ b/ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs @@ -28,6 +28,7 @@ public class PlexTelevisionLibraryScanner : IMetadataRepository metadataRepository, ISearchIndex searchIndex, ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, IMediator mediator, IMediaSourceRepository mediaSourceRepository, IPlexPathReplacementService plexPathReplacementService, @@ -42,6 +43,7 @@ public class PlexTelevisionLibraryScanner : localFileSystem, searchRepository, searchIndex, + fallbackMetadataProvider, mediator, logger) { diff --git a/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs index c4c08e9f..b2439357 100644 --- a/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs @@ -1,4 +1,3 @@ -using System.Numerics; using ErsatzTV.FFmpeg.Format; namespace ErsatzTV.FFmpeg.Capabilities; @@ -42,7 +41,7 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities // nvidia cannot encode 10-bit h264 VideoFormat.H264 when bitDepth == 10 => false, - + _ => true }; } diff --git a/ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs index 34ca2530..dfa8eb47 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs @@ -9,6 +9,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories; public class SearchRepository : ISearchRepository { private readonly IDbContextFactory _dbContextFactory; + private readonly SemaphoreSlim _slim = new(1, 1); + private List _allLanguageCodes; public SearchRepository(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; @@ -49,6 +51,8 @@ public class SearchRepository : ISearchRepository .ThenInclude(em => em.Guids) .Include(mi => (mi as Episode).MediaVersions) .ThenInclude(em => em.Streams) + .Include(mi => (mi as Episode).MediaVersions) + .ThenInclude(em => em.MediaFiles) .Include(mi => (mi as Episode).Season) .ThenInclude(s => s.Show) .ThenInclude(s => s.ShowMetadata) @@ -162,8 +166,24 @@ public class SearchRepository : ISearchRepository public async Task> GetAllLanguageCodes(List mediaCodes) { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); - return await dbContext.LanguageCodes.GetAllLanguageCodes(mediaCodes); + if (_allLanguageCodes == null) + { + await _slim.WaitAsync(); + try + { + if (_allLanguageCodes == null) + { + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); + _allLanguageCodes = await dbContext.LanguageCodes.GetAllLanguageCodes(mediaCodes); + } + } + finally + { + _slim.Release(); + } + } + + return _allLanguageCodes; } public IAsyncEnumerable GetAllMediaItems() @@ -203,6 +223,8 @@ public class SearchRepository : ISearchRepository .ThenInclude(em => em.Guids) .Include(mi => (mi as Episode).MediaVersions) .ThenInclude(em => em.Streams) + .Include(mi => (mi as Episode).MediaVersions) + .ThenInclude(em => em.MediaFiles) .Include(mi => (mi as Episode).Season) .ThenInclude(s => s.Show) .ThenInclude(s => s.ShowMetadata) diff --git a/ErsatzTV.Infrastructure/Search/SearchIndex.cs b/ErsatzTV.Infrastructure/Search/SearchIndex.cs index 578b0130..03c9b3a8 100644 --- a/ErsatzTV.Infrastructure/Search/SearchIndex.cs +++ b/ErsatzTV.Infrastructure/Search/SearchIndex.cs @@ -88,7 +88,7 @@ public sealed class SearchIndex : ISearchIndex _initialized = false; } - public int Version => 27; + public int Version => 28; public async Task Initialize( ILocalFileSystem localFileSystem, @@ -117,7 +117,10 @@ public sealed class SearchIndex : ISearchIndex return _initialized; } - public async Task UpdateItems(ISearchRepository searchRepository, List items) + public async Task UpdateItems( + ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, + List items) { foreach (MediaItem item in items) { @@ -139,7 +142,7 @@ public sealed class SearchIndex : ISearchIndex await UpdateMusicVideo(searchRepository, musicVideo); break; case Episode episode: - await UpdateEpisode(searchRepository, episode); + await UpdateEpisode(searchRepository, fallbackMetadataProvider, episode); break; case OtherVideo otherVideo: await UpdateOtherVideo(searchRepository, otherVideo); @@ -215,27 +218,32 @@ public sealed class SearchIndex : ISearchIndex _directory?.Dispose(); } - public async Task Rebuild(ISearchRepository searchRepository) + public async Task Rebuild( + ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider) { _writer.DeleteAll(); _writer.Commit(); await foreach (MediaItem mediaItem in searchRepository.GetAllMediaItems()) { - await RebuildItem(searchRepository, mediaItem); + await RebuildItem(searchRepository, fallbackMetadataProvider, mediaItem); } _writer.Commit(); return Unit.Default; } - public async Task RebuildItems(ISearchRepository searchRepository, List itemIds) + public async Task RebuildItems( + ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, + List itemIds) { foreach (int id in itemIds) { foreach (MediaItem mediaItem in await searchRepository.GetItemToIndex(id)) { - await RebuildItem(searchRepository, mediaItem); + await RebuildItem(searchRepository, fallbackMetadataProvider, mediaItem); } } @@ -268,7 +276,10 @@ public sealed class SearchIndex : ISearchIndex } } - private async Task RebuildItem(ISearchRepository searchRepository, MediaItem mediaItem) + private async Task RebuildItem( + ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, + MediaItem mediaItem) { switch (mediaItem) { @@ -288,7 +299,7 @@ public sealed class SearchIndex : ISearchIndex await UpdateMusicVideo(searchRepository, musicVideo); break; case Episode episode: - await UpdateEpisode(searchRepository, episode); + await UpdateEpisode(searchRepository, fallbackMetadataProvider, episode); break; case OtherVideo otherVideo: await UpdateOtherVideo(searchRepository, otherVideo); @@ -807,8 +818,22 @@ public sealed class SearchIndex : ISearchIndex } } - private async Task UpdateEpisode(ISearchRepository searchRepository, Episode episode) + private async Task UpdateEpisode( + ISearchRepository searchRepository, + IFallbackMetadataProvider fallbackMetadataProvider, + Episode episode) { + // try to load metadata here, since episodes without metadata won't index + if (episode.EpisodeMetadata.Count == 0) + { + episode.EpisodeMetadata ??= new List(); + episode.EpisodeMetadata = fallbackMetadataProvider.GetFallbackMetadata(episode); + foreach (EpisodeMetadata metadata in episode.EpisodeMetadata) + { + metadata.Episode = episode; + } + } + foreach (EpisodeMetadata metadata in episode.EpisodeMetadata) { try