Browse Source

fix missing trashed episodes (#881)

* fix episodes missing from trash

* cleanup
pull/882/head
Jason Dove 3 years ago committed by GitHub
parent
commit
9c02a6738b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      ErsatzTV.Application/Libraries/Commands/MoveLocalLibraryPathHandler.cs
  2. 4
      ErsatzTV.Application/MediaCollections/Commands/AddTraktListHandler.cs
  3. 11
      ErsatzTV.Application/MediaCollections/Commands/DeleteTraktListHandler.cs
  4. 9
      ErsatzTV.Application/MediaCollections/Commands/MatchTraktListItemsHandler.cs
  5. 6
      ErsatzTV.Application/MediaCollections/Commands/TraktCommandBase.cs
  6. 5
      ErsatzTV.Application/Search/Commands/RebuildSearchIndexHandler.cs
  7. 48
      ErsatzTV.Application/Search/Queries/QuerySearchIndexEpisodesHandler.cs
  8. 7
      ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs
  9. 2
      ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs
  10. 6
      ErsatzTV.Core/Emby/EmbyCollectionScanner.cs
  11. 2
      ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs
  12. 2
      ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs
  13. 15
      ErsatzTV.Core/Interfaces/Search/ISearchIndex.cs
  14. 6
      ErsatzTV.Core/Jellyfin/JellyfinCollectionScanner.cs
  15. 2
      ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs
  16. 2
      ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs
  17. 8
      ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs
  18. 12
      ErsatzTV.Core/Metadata/MediaServerMovieLibraryScanner.cs
  19. 26
      ErsatzTV.Core/Metadata/MediaServerTelevisionLibraryScanner.cs
  20. 10
      ErsatzTV.Core/Metadata/MovieFolderScanner.cs
  21. 15
      ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs
  22. 10
      ErsatzTV.Core/Metadata/OtherVideoFolderScanner.cs
  23. 10
      ErsatzTV.Core/Metadata/SongFolderScanner.cs
  24. 21
      ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs
  25. 2
      ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs
  26. 2
      ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs
  27. 3
      ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs
  28. 26
      ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs
  29. 45
      ErsatzTV.Infrastructure/Search/SearchIndex.cs

11
ErsatzTV.Application/Libraries/Commands/MoveLocalLibraryPathHandler.cs

@ -1,6 +1,7 @@ @@ -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; @@ -13,6 +14,7 @@ namespace ErsatzTV.Application.Libraries;
public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath, Either<BaseError, Unit>>
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
private readonly ILogger<MoveLocalLibraryPathHandler> _logger;
private readonly ISearchIndex _searchIndex;
private readonly ISearchRepository _searchRepository;
@ -20,11 +22,13 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath, @@ -20,11 +22,13 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath,
public MoveLocalLibraryPathHandler(
ISearchIndex searchIndex,
ISearchRepository searchRepository,
IFallbackMetadataProvider fallbackMetadataProvider,
IDbContextFactory<TvContext> dbContextFactory,
ILogger<MoveLocalLibraryPathHandler> logger)
{
_searchIndex = searchIndex;
_searchRepository = searchRepository;
_fallbackMetadataProvider = fallbackMetadataProvider;
_dbContextFactory = dbContextFactory;
_logger = logger;
}
@ -35,7 +39,7 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath, @@ -35,7 +39,7 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath,
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Parameters> 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<Unit> MovePath(TvContext dbContext, Parameters parameters)
@ -57,7 +61,10 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath, @@ -57,7 +61,10 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath,
foreach (MediaItem mediaItem in maybeMediaItem)
{
_logger.LogInformation("Moving item at {Path}", await GetPath(dbContext, mediaItem));
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { mediaItem });
await _searchIndex.UpdateItems(
_searchRepository,
_fallbackMetadataProvider,
new List<MediaItem> { mediaItem });
}
}
}

4
ErsatzTV.Application/MediaCollections/Commands/AddTraktListHandler.cs

@ -1,6 +1,7 @@ @@ -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<AddTraktLis @@ -19,10 +20,11 @@ public class AddTraktListHandler : TraktCommandBase, IRequestHandler<AddTraktLis
ITraktApiClient traktApiClient,
ISearchRepository searchRepository,
ISearchIndex searchIndex,
IFallbackMetadataProvider fallbackMetadataProvider,
IDbContextFactory<TvContext> dbContextFactory,
ILogger<AddTraktListHandler> logger,
IEntityLocker entityLocker)
: base(traktApiClient, searchRepository, searchIndex, logger)
: base(traktApiClient, searchRepository, searchIndex, fallbackMetadataProvider, logger)
{
_dbContextFactory = dbContextFactory;
_entityLocker = entityLocker;

11
ErsatzTV.Application/MediaCollections/Commands/DeleteTraktListHandler.cs

@ -1,6 +1,7 @@ @@ -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<DeleteTr @@ -14,6 +15,7 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler<DeleteTr
{
private readonly IDbContextFactory<TvContext> _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<DeleteTr @@ -21,13 +23,15 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler<DeleteTr
ITraktApiClient traktApiClient,
ISearchRepository searchRepository,
ISearchIndex searchIndex,
IFallbackMetadataProvider fallbackMetadataProvider,
IDbContextFactory<TvContext> dbContextFactory,
ILogger<DeleteTraktListHandler> 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<DeleteTr @@ -38,8 +42,7 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler<DeleteTr
{
try
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, TraktList> validation = await TraktListMustExist(dbContext, request.TraktListId);
return await LanguageExtensions.Apply(validation, c => DoDeletion(dbContext, c));
}
@ -56,7 +59,7 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler<DeleteTr @@ -56,7 +59,7 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler<DeleteTr
dbContext.TraktLists.Remove(traktList);
if (await dbContext.SaveChangesAsync() > 0)
{
await _searchIndex.RebuildItems(_searchRepository, mediaItemIds);
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, mediaItemIds);
}
_searchIndex.Commit();

9
ErsatzTV.Application/MediaCollections/Commands/MatchTraktListItemsHandler.cs

@ -1,6 +1,7 @@ @@ -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, @@ -20,9 +21,15 @@ public class MatchTraktListItemsHandler : TraktCommandBase,
ITraktApiClient traktApiClient,
ISearchRepository searchRepository,
ISearchIndex searchIndex,
IFallbackMetadataProvider fallbackMetadataProvider,
IDbContextFactory<TvContext> dbContextFactory,
ILogger<MatchTraktListItemsHandler> logger,
IEntityLocker entityLocker) : base(traktApiClient, searchRepository, searchIndex, logger)
IEntityLocker entityLocker) : base(
traktApiClient,
searchRepository,
searchIndex,
fallbackMetadataProvider,
logger)
{
_dbContextFactory = dbContextFactory;
_entityLocker = entityLocker;

6
ErsatzTV.Application/MediaCollections/Commands/TraktCommandBase.cs

@ -1,5 +1,6 @@ @@ -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; @@ -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 @@ -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 @@ -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();

5
ErsatzTV.Application/Search/Commands/RebuildSearchIndexHandler.cs

@ -12,6 +12,7 @@ namespace ErsatzTV.Application.Search; @@ -12,6 +12,7 @@ namespace ErsatzTV.Application.Search;
public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Unit>
{
private readonly IConfigElementRepository _configElementRepository;
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger<RebuildSearchIndexHandler> _logger;
private readonly ISearchIndex _searchIndex;
@ -22,6 +23,7 @@ public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Uni @@ -22,6 +23,7 @@ public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Uni
ISearchRepository searchRepository,
IConfigElementRepository configElementRepository,
ILocalFileSystem localFileSystem,
IFallbackMetadataProvider fallbackMetadataProvider,
ILogger<RebuildSearchIndexHandler> logger)
{
_searchIndex = searchIndex;
@ -29,6 +31,7 @@ public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Uni @@ -29,6 +31,7 @@ public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Uni
_searchRepository = searchRepository;
_configElementRepository = configElementRepository;
_localFileSystem = localFileSystem;
_fallbackMetadataProvider = fallbackMetadataProvider;
}
public async Task<Unit> Handle(RebuildSearchIndex request, CancellationToken cancellationToken)
@ -48,7 +51,7 @@ public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Uni @@ -48,7 +51,7 @@ public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Uni
_logger.LogInformation("Migrating search index to version {Version}", _searchIndex.Version);
var sw = Stopwatch.StartNew();
await _searchIndex.Rebuild(_searchRepository);
await _searchIndex.Rebuild(_searchRepository, _fallbackMetadataProvider);
await _configElementRepository.Upsert(ConfigElementKey.SearchIndexVersion, _searchIndex.Version);
sw.Stop();

48
ErsatzTV.Application/Search/Queries/QuerySearchIndexEpisodesHandler.cs

@ -3,10 +3,14 @@ using ErsatzTV.Core.Domain; @@ -3,10 +3,14 @@ using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Jellyfin;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
using ErsatzTV.Core.Search;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
using static ErsatzTV.Application.MediaCards.Mapper;
namespace ErsatzTV.Application.Search;
@ -14,7 +18,9 @@ namespace ErsatzTV.Application.Search; @@ -14,7 +18,9 @@ namespace ErsatzTV.Application.Search;
public class
QuerySearchIndexEpisodesHandler : IRequestHandler<QuerySearchIndexEpisodes, TelevisionEpisodeCardResultsViewModel>
{
private readonly IDbContextFactory<TvContext> _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 @@ -27,7 +33,9 @@ public class
IMediaSourceRepository mediaSourceRepository,
IPlexPathReplacementService plexPathReplacementService,
IJellyfinPathReplacementService jellyfinPathReplacementService,
IEmbyPathReplacementService embyPathReplacementService)
IEmbyPathReplacementService embyPathReplacementService,
IFallbackMetadataProvider fallbackMetadataProvider,
IDbContextFactory<TvContext> dbContextFactory)
{
_searchIndex = searchIndex;
_televisionRepository = televisionRepository;
@ -35,6 +43,8 @@ public class @@ -35,6 +43,8 @@ public class
_plexPathReplacementService = plexPathReplacementService;
_jellyfinPathReplacementService = jellyfinPathReplacementService;
_embyPathReplacementService = embyPathReplacementService;
_fallbackMetadataProvider = fallbackMetadataProvider;
_dbContextFactory = dbContextFactory;
}
public async Task<TelevisionEpisodeCardResultsViewModel> Handle(
@ -52,8 +62,40 @@ public class @@ -52,8 +62,40 @@ public class
Option<EmbyMediaSource> maybeEmby = await _mediaSourceRepository.GetAllEmby()
.Map(list => list.HeadOrNone());
List<EpisodeMetadata> episodes = await _televisionRepository
.GetEpisodesForCards(searchResult.Items.Map(i => i.Id).ToList());
var episodeIds = searchResult.Items.Map(i => i.Id).ToList();
List<EpisodeMetadata> 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<Episode> 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<EpisodeMetadata> { headMetadata };
episodes.Add(headMetadata);
}
}
}
var items = new List<TelevisionEpisodeCardViewModel>();

7
ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs

@ -40,6 +40,13 @@ public class FallbackMetadataProviderTests @@ -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<EpisodeMetadata> metadata = _fallbackMetadataProvider.GetFallbackMetadata(

2
ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs

@ -623,6 +623,7 @@ public class MovieFolderScannerTests @@ -623,6 +623,7 @@ public class MovieFolderScannerTests
_imageCache.Object,
new Mock<ISearchIndex>().Object,
new Mock<ISearchRepository>().Object,
new Mock<IFallbackMetadataProvider>().Object,
new Mock<ILibraryRepository>().Object,
_mediaItemRepository.Object,
new Mock<IMediator>().Object,
@ -643,6 +644,7 @@ public class MovieFolderScannerTests @@ -643,6 +644,7 @@ public class MovieFolderScannerTests
_imageCache.Object,
new Mock<ISearchIndex>().Object,
new Mock<ISearchRepository>().Object,
new Mock<IFallbackMetadataProvider>().Object,
new Mock<ILibraryRepository>().Object,
_mediaItemRepository.Object,
new Mock<IMediator>().Object,

6
ErsatzTV.Core/Emby/EmbyCollectionScanner.cs

@ -1,5 +1,6 @@ @@ -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 @@ -10,6 +11,7 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner
{
private readonly IEmbyApiClient _embyApiClient;
private readonly IEmbyCollectionRepository _embyCollectionRepository;
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
private readonly ILogger<EmbyCollectionScanner> _logger;
private readonly ISearchIndex _searchIndex;
private readonly ISearchRepository _searchRepository;
@ -19,12 +21,14 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner @@ -19,12 +21,14 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner
IEmbyApiClient embyApiClient,
ISearchRepository searchRepository,
ISearchIndex searchIndex,
IFallbackMetadataProvider fallbackMetadataProvider,
ILogger<EmbyCollectionScanner> logger)
{
_embyCollectionRepository = embyCollectionRepository;
_embyApiClient = embyApiClient;
_searchRepository = searchRepository;
_searchIndex = searchIndex;
_fallbackMetadataProvider = fallbackMetadataProvider;
_logger = logger;
}
@ -107,7 +111,7 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner @@ -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)

2
ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs

@ -26,6 +26,7 @@ public class EmbyMovieLibraryScanner : @@ -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 : @@ -38,6 +39,7 @@ public class EmbyMovieLibraryScanner :
mediator,
searchIndex,
searchRepository,
fallbackMetadataProvider,
logger)
{
_embyApiClient = embyApiClient;

2
ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs

@ -25,6 +25,7 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner< @@ -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< @@ -37,6 +38,7 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner<
localFileSystem,
searchRepository,
searchIndex,
fallbackMetadataProvider,
mediator,
logger)
{

15
ErsatzTV.Core/Interfaces/Search/ISearchIndex.cs

@ -9,9 +9,18 @@ public interface ISearchIndex : IDisposable @@ -9,9 +9,18 @@ public interface ISearchIndex : IDisposable
{
public int Version { get; }
Task<bool> Initialize(ILocalFileSystem localFileSystem, IConfigElementRepository configElementRepository);
Task<Unit> Rebuild(ISearchRepository searchRepository);
Task<Unit> RebuildItems(ISearchRepository searchRepository, List<int> itemIds);
Task<Unit> UpdateItems(ISearchRepository searchRepository, List<MediaItem> items);
Task<Unit> Rebuild(ISearchRepository searchRepository, IFallbackMetadataProvider fallbackMetadataProvider);
Task<Unit> RebuildItems(
ISearchRepository searchRepository,
IFallbackMetadataProvider fallbackMetadataProvider,
List<int> itemIds);
Task<Unit> UpdateItems(
ISearchRepository searchRepository,
IFallbackMetadataProvider fallbackMetadataProvider,
List<MediaItem> items);
Task<Unit> RemoveItems(List<int> ids);
Task<SearchResult> Search(string query, int skip, int limit, string searchField = "");
void Commit();

6
ErsatzTV.Core/Jellyfin/JellyfinCollectionScanner.cs

@ -1,5 +1,6 @@ @@ -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; @@ -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<JellyfinCollectionScanner> _logger;
@ -19,12 +21,14 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner @@ -19,12 +21,14 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner
IJellyfinApiClient jellyfinApiClient,
ISearchRepository searchRepository,
ISearchIndex searchIndex,
IFallbackMetadataProvider fallbackMetadataProvider,
ILogger<JellyfinCollectionScanner> logger)
{
_jellyfinCollectionRepository = jellyfinCollectionRepository;
_jellyfinApiClient = jellyfinApiClient;
_searchRepository = searchRepository;
_searchIndex = searchIndex;
_fallbackMetadataProvider = fallbackMetadataProvider;
_logger = logger;
}
@ -116,7 +120,7 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner @@ -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)

2
ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs

@ -25,6 +25,7 @@ public class JellyfinMovieLibraryScanner : @@ -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 : @@ -38,6 +39,7 @@ public class JellyfinMovieLibraryScanner :
mediator,
searchIndex,
searchRepository,
fallbackMetadataProvider,
logger)
{
_jellyfinApiClient = jellyfinApiClient;

2
ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs

@ -26,6 +26,7 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan @@ -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 @@ -38,6 +39,7 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan
localFileSystem,
searchRepository,
searchIndex,
fallbackMetadataProvider,
mediator,
logger)
{

8
ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs

@ -161,8 +161,14 @@ public class FallbackMetadataProvider : IFallbackMetadataProvider @@ -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)

12
ErsatzTV.Core/Metadata/MediaServerMovieLibraryScanner.cs

@ -15,6 +15,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib @@ -15,6 +15,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
where TMovie : Movie
where TEtag : MediaServerItemEtag
{
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalStatisticsProvider _localStatisticsProvider;
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
@ -30,6 +31,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib @@ -30,6 +31,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
IMediator mediator,
ISearchIndex searchIndex,
ISearchRepository searchRepository,
IFallbackMetadataProvider fallbackMetadataProvider,
ILogger logger)
{
_localStatisticsProvider = localStatisticsProvider;
@ -38,6 +40,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib @@ -38,6 +40,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
_mediator = mediator;
_searchIndex = searchIndex;
_searchRepository = searchRepository;
_fallbackMetadataProvider = fallbackMetadataProvider;
_logger = logger;
}
@ -168,7 +171,10 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib @@ -168,7 +171,10 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
if (result.IsAdded || result.IsUpdated)
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
await _searchIndex.RebuildItems(
_searchRepository,
_fallbackMetadataProvider,
new List<int> { result.Item.Id });
}
}
}
@ -176,7 +182,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib @@ -176,7 +182,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
// trash movies that are no longer present on the media server
var fileNotFoundItemIds = existingMovies.Map(m => m.MediaServerItemId).Except(incomingItemIds).ToList();
List<int> 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<TConnectionParameters, TLib @@ -241,7 +247,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
{
foreach (int id in await movieRepository.FlagUnavailable(library, incoming))
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { id });
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, new List<int> { id });
}
}

26
ErsatzTV.Core/Metadata/MediaServerTelevisionLibraryScanner.cs

@ -18,6 +18,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters, @@ -18,6 +18,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
where TEpisode : Episode
where TEtag : MediaServerItemEtag
{
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalStatisticsProvider _localStatisticsProvider;
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
@ -32,6 +33,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters, @@ -32,6 +33,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
ILocalFileSystem localFileSystem,
ISearchRepository searchRepository,
ISearchIndex searchIndex,
IFallbackMetadataProvider fallbackMetadataProvider,
IMediator mediator,
ILogger logger)
{
@ -40,6 +42,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters, @@ -40,6 +42,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
_localFileSystem = localFileSystem;
_searchRepository = searchRepository;
_searchIndex = searchIndex;
_fallbackMetadataProvider = fallbackMetadataProvider;
_mediator = mediator;
_logger = logger;
}
@ -196,7 +199,10 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters, @@ -196,7 +199,10 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
if (result.IsAdded || result.IsUpdated)
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
await _searchIndex.RebuildItems(
_searchRepository,
_fallbackMetadataProvider,
new List<int> { result.Item.Id });
}
}
}
@ -204,7 +210,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters, @@ -204,7 +210,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
// trash shows that are no longer present on the media server
var fileNotFoundItemIds = existingShows.Map(s => s.MediaServerItemId).Except(incomingItemIds).ToList();
List<int> 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<TConnectionParameters, @@ -358,7 +364,10 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
if (result.IsAdded || result.IsUpdated)
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
await _searchIndex.RebuildItems(
_searchRepository,
_fallbackMetadataProvider,
new List<int> { result.Item.Id });
}
}
}
@ -366,7 +375,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters, @@ -366,7 +375,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
// trash seasons that are no longer present on the media server
var fileNotFoundItemIds = existingSeasons.Map(s => s.MediaServerItemId).Except(incomingItemIds).ToList();
List<int> 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<TConnectionParameters, @@ -461,7 +470,10 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
if (result.IsAdded || result.IsUpdated)
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
await _searchIndex.RebuildItems(
_searchRepository,
_fallbackMetadataProvider,
new List<int> { result.Item.Id });
}
}
}
@ -469,7 +481,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters, @@ -469,7 +481,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
// trash episodes that are no longer present on the media server
var fileNotFoundItemIds = existingEpisodes.Map(m => m.MediaServerItemId).Except(incomingItemIds).ToList();
List<int> 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<TConnectionParameters, @@ -511,7 +523,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
{
foreach (int id in await televisionRepository.FlagUnavailable(library, incoming))
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { id });
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, new List<int> { id });
}
}

10
ErsatzTV.Core/Metadata/MovieFolderScanner.cs

@ -15,6 +15,7 @@ namespace ErsatzTV.Core.Metadata; @@ -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 @@ -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 @@ -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 @@ -160,7 +163,10 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
{
if (result.IsAdded || result.IsUpdated)
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
await _searchIndex.RebuildItems(
_searchRepository,
_fallbackMetadataProvider,
new List<int> { result.Item.Id });
}
await _libraryRepository.SetEtag(libraryPath, knownFolder, movieFolder, etag);
@ -174,7 +180,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner @@ -174,7 +180,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
{
_logger.LogInformation("Flagging missing movie at {Path}", path);
List<int> ids = await FlagFileNotFound(libraryPath, path);
await _searchIndex.RebuildItems(_searchRepository, ids);
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, ids);
}
else if (Path.GetFileName(path).StartsWith("._"))
{

15
ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs

@ -15,6 +15,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan @@ -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 @@ -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 @@ -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 @@ -137,7 +140,10 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
if (result.IsAdded || result.IsUpdated)
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
await _searchIndex.RebuildItems(
_searchRepository,
_fallbackMetadataProvider,
new List<int> { result.Item.Id });
}
}
}
@ -155,7 +161,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan @@ -155,7 +161,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
{
_logger.LogInformation("Flagging missing music video at {Path}", path);
List<int> 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 @@ -330,7 +336,10 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
{
if (result.IsAdded || result.IsUpdated)
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
await _searchIndex.RebuildItems(
_searchRepository,
_fallbackMetadataProvider,
new List<int> { result.Item.Id });
}
}
}

10
ErsatzTV.Core/Metadata/OtherVideoFolderScanner.cs

@ -14,6 +14,7 @@ namespace ErsatzTV.Core.Metadata; @@ -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 @@ -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 @@ -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 @@ -160,7 +163,10 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
{
if (result.IsAdded || result.IsUpdated)
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
await _searchIndex.RebuildItems(
_searchRepository,
_fallbackMetadataProvider,
new List<int> { result.Item.Id });
}
}
}
@ -178,7 +184,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan @@ -178,7 +184,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
{
_logger.LogInformation("Flagging missing other video at {Path}", path);
List<int> otherVideoIds = await FlagFileNotFound(libraryPath, path);
await _searchIndex.RebuildItems(_searchRepository, otherVideoIds);
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, otherVideoIds);
}
else if (Path.GetFileName(path).StartsWith("._"))
{

10
ErsatzTV.Core/Metadata/SongFolderScanner.cs

@ -15,6 +15,7 @@ namespace ErsatzTV.Core.Metadata; @@ -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 @@ -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 @@ -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 @@ -155,7 +158,10 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
{
if (result.IsAdded || result.IsUpdated)
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
await _searchIndex.RebuildItems(
_searchRepository,
_fallbackMetadataProvider,
new List<int> { result.Item.Id });
}
}
}
@ -173,7 +179,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner @@ -173,7 +179,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
{
_logger.LogInformation("Flagging missing song at {Path}", path);
List<int> songIds = await FlagFileNotFound(libraryPath, path);
await _searchIndex.RebuildItems(_searchRepository, songIds);
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, songIds);
}
else if (Path.GetFileName(path).StartsWith("._"))
{

21
ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs

@ -14,6 +14,7 @@ namespace ErsatzTV.Core.Metadata; @@ -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 @@ -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 @@ -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 @@ -127,7 +130,10 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
if (result.IsAdded || result.IsUpdated)
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
await _searchIndex.RebuildItems(
_searchRepository,
_fallbackMetadataProvider,
new List<int> { result.Item.Id });
}
}
}
@ -137,8 +143,9 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -137,8 +143,9 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
if (!_localFileSystem.FileExists(path))
{
_logger.LogInformation("Flagging missing episode at {Path}", path);
List<int> 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 @@ -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<int> { season.Id });
await _searchIndex.RebuildItems(
_searchRepository,
_fallbackMetadataProvider,
new List<int> { season.Id });
}
}
}
@ -287,7 +297,10 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -287,7 +297,10 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
foreach (Episode episode in maybeEpisode.RightToSeq())
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { episode.Id });
await _searchIndex.RebuildItems(
_searchRepository,
_fallbackMetadataProvider,
new List<int> { episode.Id });
}
}

2
ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs

@ -28,6 +28,7 @@ public class PlexMovieLibraryScanner : @@ -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 : @@ -43,6 +44,7 @@ public class PlexMovieLibraryScanner :
mediator,
searchIndex,
searchRepository,
fallbackMetadataProvider,
logger)
{
_plexServerApiClient = plexServerApiClient;

2
ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs

@ -28,6 +28,7 @@ public class PlexTelevisionLibraryScanner : @@ -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 : @@ -42,6 +43,7 @@ public class PlexTelevisionLibraryScanner :
localFileSystem,
searchRepository,
searchIndex,
fallbackMetadataProvider,
mediator,
logger)
{

3
ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
using System.Numerics;
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Capabilities;
@ -42,7 +41,7 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities @@ -42,7 +41,7 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
// nvidia cannot encode 10-bit h264
VideoFormat.H264 when bitDepth == 10 => false,
_ => true
};
}

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

@ -9,6 +9,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories; @@ -9,6 +9,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories;
public class SearchRepository : ISearchRepository
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly SemaphoreSlim _slim = new(1, 1);
private List<string> _allLanguageCodes;
public SearchRepository(IDbContextFactory<TvContext> dbContextFactory) => _dbContextFactory = dbContextFactory;
@ -49,6 +51,8 @@ public class SearchRepository : ISearchRepository @@ -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 @@ -162,8 +166,24 @@ public class SearchRepository : ISearchRepository
public async Task<List<string>> GetAllLanguageCodes(List<string> 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<MediaItem> GetAllMediaItems()
@ -203,6 +223,8 @@ public class SearchRepository : ISearchRepository @@ -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)

45
ErsatzTV.Infrastructure/Search/SearchIndex.cs

@ -88,7 +88,7 @@ public sealed class SearchIndex : ISearchIndex @@ -88,7 +88,7 @@ public sealed class SearchIndex : ISearchIndex
_initialized = false;
}
public int Version => 27;
public int Version => 28;
public async Task<bool> Initialize(
ILocalFileSystem localFileSystem,
@ -117,7 +117,10 @@ public sealed class SearchIndex : ISearchIndex @@ -117,7 +117,10 @@ public sealed class SearchIndex : ISearchIndex
return _initialized;
}
public async Task<Unit> UpdateItems(ISearchRepository searchRepository, List<MediaItem> items)
public async Task<Unit> UpdateItems(
ISearchRepository searchRepository,
IFallbackMetadataProvider fallbackMetadataProvider,
List<MediaItem> items)
{
foreach (MediaItem item in items)
{
@ -139,7 +142,7 @@ public sealed class SearchIndex : ISearchIndex @@ -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 @@ -215,27 +218,32 @@ public sealed class SearchIndex : ISearchIndex
_directory?.Dispose();
}
public async Task<Unit> Rebuild(ISearchRepository searchRepository)
public async Task<Unit> 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<Unit> RebuildItems(ISearchRepository searchRepository, List<int> itemIds)
public async Task<Unit> RebuildItems(
ISearchRepository searchRepository,
IFallbackMetadataProvider fallbackMetadataProvider,
List<int> 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 @@ -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 @@ -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 @@ -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<EpisodeMetadata>();
episode.EpisodeMetadata = fallbackMetadataProvider.GetFallbackMetadata(episode);
foreach (EpisodeMetadata metadata in episode.EpisodeMetadata)
{
metadata.Episode = episode;
}
}
foreach (EpisodeMetadata metadata in episode.EpisodeMetadata)
{
try

Loading…
Cancel
Save