diff --git a/ErsatzTV.Application/Artists/Queries/GetAllArtistsHandler.cs b/ErsatzTV.Application/Artists/Queries/GetAllArtistsHandler.cs index 9fc031bf..d11e19b7 100644 --- a/ErsatzTV.Application/Artists/Queries/GetAllArtistsHandler.cs +++ b/ErsatzTV.Application/Artists/Queries/GetAllArtistsHandler.cs @@ -10,10 +10,7 @@ public class GetAllArtistsHandler : IRequestHandler _dbContextFactory; - public GetAllArtistsHandler(IDbContextFactory dbContextFactory) - { - _dbContextFactory = dbContextFactory; - } + public GetAllArtistsHandler(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; public async Task> Handle( GetAllArtists request, @@ -24,8 +21,8 @@ public class GetAllArtistsHandler : IRequestHandler allArtists = await dbContext.Artists .AsNoTracking() .Include(a => a.ArtistMetadata) - .ToListAsync(cancellationToken: cancellationToken); - + .ToListAsync(cancellationToken); + return allArtists.Bind(a => ProjectArtist(a)).ToList(); } diff --git a/ErsatzTV.Application/Channels/Commands/DeleteChannelHandler.cs b/ErsatzTV.Application/Channels/Commands/DeleteChannelHandler.cs index 7df95987..e247e252 100644 --- a/ErsatzTV.Application/Channels/Commands/DeleteChannelHandler.cs +++ b/ErsatzTV.Application/Channels/Commands/DeleteChannelHandler.cs @@ -1,19 +1,18 @@ -using System.Threading; using System.Threading.Channels; using ErsatzTV.Core; -using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; +using Channel = ErsatzTV.Core.Domain.Channel; namespace ErsatzTV.Application.Channels; public class DeleteChannelHandler : IRequestHandler> { - private readonly ChannelWriter _workerChannel; private readonly IDbContextFactory _dbContextFactory; private readonly ILocalFileSystem _localFileSystem; + private readonly ChannelWriter _workerChannel; public DeleteChannelHandler( ChannelWriter workerChannel, @@ -28,12 +27,12 @@ public class DeleteChannelHandler : IRequestHandler> Handle(DeleteChannel request, CancellationToken cancellationToken) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); - Validation validation = await ChannelMustExist(dbContext, request); + Validation validation = await ChannelMustExist(dbContext, request); - return await validation.Apply(c => DoDeletion(dbContext, c, cancellationToken)); + return await LanguageExtensions.Apply(validation, c => DoDeletion(dbContext, c, cancellationToken)); } - private async Task DoDeletion(TvContext dbContext, Core.Domain.Channel channel, CancellationToken cancellationToken) + private async Task DoDeletion(TvContext dbContext, Channel channel, CancellationToken cancellationToken) { dbContext.Channels.Remove(channel); await dbContext.SaveChangesAsync(); @@ -51,9 +50,11 @@ public class DeleteChannelHandler : IRequestHandler> ChannelMustExist(TvContext dbContext, DeleteChannel deleteChannel) + private static async Task> ChannelMustExist( + TvContext dbContext, + DeleteChannel deleteChannel) { - Option maybeChannel = await dbContext.Channels + Option maybeChannel = await dbContext.Channels .SelectOneAsync(c => c.Id, c => c.Id == deleteChannel.ChannelId); return maybeChannel.ToValidation($"Channel {deleteChannel.ChannelId} does not exist."); } diff --git a/ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs b/ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs index 07a6e6dc..98d296cf 100644 --- a/ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs +++ b/ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs @@ -14,10 +14,10 @@ namespace ErsatzTV.Application.Channels; public class RefreshChannelDataHandler : IRequestHandler { - private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; private readonly IDbContextFactory _dbContextFactory; private readonly ILocalFileSystem _localFileSystem; private readonly ILogger _logger; + private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; public RefreshChannelDataHandler( RecyclableMemoryStreamManager recyclableMemoryStreamManager, @@ -359,7 +359,7 @@ public class RefreshChannelDataHandler : IRequestHandler string targetFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{request.ChannelNumber}.xml"); File.Move(tempFile, targetFile, true); } - + private static string GetArtworkUrl(Artwork artwork, ArtworkKind artworkKind) { string artworkPath = artwork.Path; @@ -456,7 +456,7 @@ public class RefreshChannelDataHandler : IRequestHandler _ => string.Empty }; } - + private Option GetContentRating(PlayoutItem playoutItem) { try @@ -478,7 +478,7 @@ public class RefreshChannelDataHandler : IRequestHandler return None; } } - + private static Option ParseContentRating(string contentRating, string system) { Option maybeFirst = (contentRating ?? string.Empty).Split('/').HeadOrNone(); @@ -499,8 +499,6 @@ public class RefreshChannelDataHandler : IRequestHandler }).Flatten(); } - private record ContentRating(Option System, string Value); - private string GetPrioritizedArtworkPath(Metadata metadata) { Option maybeArtwork = Optional(metadata.Artwork).Flatten() @@ -518,4 +516,6 @@ public class RefreshChannelDataHandler : IRequestHandler return maybeArtwork.IfNone(string.Empty); } + + private record ContentRating(Option System, string Value); } diff --git a/ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs b/ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs index 107b3edd..67a99a3e 100644 --- a/ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs +++ b/ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs @@ -12,9 +12,9 @@ namespace ErsatzTV.Application.Channels; public class RefreshChannelListHandler : IRequestHandler { - private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; private readonly IDbContextFactory _dbContextFactory; private readonly ILocalFileSystem _localFileSystem; + private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; public RefreshChannelListHandler( RecyclableMemoryStreamManager recyclableMemoryStreamManager, @@ -29,7 +29,7 @@ public class RefreshChannelListHandler : IRequestHandler public async Task Handle(RefreshChannelList request, CancellationToken cancellationToken) { _localFileSystem.EnsureFolderExists(FileSystemLayout.ChannelGuideCacheFolder); - + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); using MemoryStream ms = _recyclableMemoryStreamManager.GetStream(); @@ -73,7 +73,7 @@ public class RefreshChannelListHandler : IRequestHandler string tempFile = Path.GetTempFileName(); await File.WriteAllBytesAsync(tempFile, ms.ToArray(), cancellationToken); - + string targetFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, "channels.xml"); File.Move(tempFile, targetFile, true); } @@ -89,13 +89,16 @@ public class RefreshChannelListHandler : IRequestHandler await using var reader = (DbDataReader)await dbContext.Connection.ExecuteReaderAsync(QUERY); Func rowParser = reader.GetRowParser(); - while (await reader.ReadAsync()) { + while (await reader.ReadAsync()) + { yield return rowParser(reader); } - - while (await reader.NextResultAsync()) {} + + while (await reader.NextResultAsync()) + { + } } - + private static List GetCategories(string categories) => (categories ?? string.Empty).Split(',') .Map(s => s.Trim()) diff --git a/ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs b/ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs index e92dcf19..564e89c1 100644 --- a/ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs +++ b/ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs @@ -31,7 +31,7 @@ public class UpdateChannelHandler : IRequestHandler validation = await Validate(dbContext, request); - return await validation.Apply(c => ApplyUpdateRequest(dbContext, c, request)); + return await LanguageExtensions.Apply(validation, c => ApplyUpdateRequest(dbContext, c, request)); } private async Task ApplyUpdateRequest(TvContext dbContext, Channel c, UpdateChannel update) diff --git a/ErsatzTV.Application/Channels/Queries/GetChannelGuideHandler.cs b/ErsatzTV.Application/Channels/Queries/GetChannelGuideHandler.cs index eb857975..68bfe156 100644 --- a/ErsatzTV.Application/Channels/Queries/GetChannelGuideHandler.cs +++ b/ErsatzTV.Application/Channels/Queries/GetChannelGuideHandler.cs @@ -11,8 +11,8 @@ namespace ErsatzTV.Application.Channels; public class GetChannelGuideHandler : IRequestHandler> { private readonly IDbContextFactory _dbContextFactory; - private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; private readonly ILocalFileSystem _localFileSystem; + private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; public GetChannelGuideHandler( IDbContextFactory dbContextFactory, @@ -35,7 +35,7 @@ public class GetChannelGuideHandler : IRequestHandler _dbContextFactory; - public GetChannelNameByPlayoutIdHandler(IDbContextFactory dbContextFactory) - { + public GetChannelNameByPlayoutIdHandler(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; - } public async Task> Handle(GetChannelNameByPlayoutId request, CancellationToken cancellationToken) { diff --git a/ErsatzTV.Application/Channels/Queries/GetChannelPlaylist.cs b/ErsatzTV.Application/Channels/Queries/GetChannelPlaylist.cs index 101e8642..2c6b2156 100644 --- a/ErsatzTV.Application/Channels/Queries/GetChannelPlaylist.cs +++ b/ErsatzTV.Application/Channels/Queries/GetChannelPlaylist.cs @@ -2,4 +2,5 @@ using ErsatzTV.Core.Iptv; namespace ErsatzTV.Application.Channels; -public record GetChannelPlaylist(string Scheme, string Host, string BaseUrl, string Mode, string AccessToken) : IRequest; +public record GetChannelPlaylist + (string Scheme, string Host, string BaseUrl, string Mode, string AccessToken) : IRequest; diff --git a/ErsatzTV.Application/Channels/Queries/GetChannelPlaylistHandler.cs b/ErsatzTV.Application/Channels/Queries/GetChannelPlaylistHandler.cs index 00791119..3fd6b2f2 100644 --- a/ErsatzTV.Application/Channels/Queries/GetChannelPlaylistHandler.cs +++ b/ErsatzTV.Application/Channels/Queries/GetChannelPlaylistHandler.cs @@ -14,7 +14,13 @@ public class GetChannelPlaylistHandler : IRequestHandler Handle(GetChannelPlaylist request, CancellationToken cancellationToken) => _channelRepository.GetAll() .Map(channels => EnsureMode(channels, request.Mode)) - .Map(channels => new ChannelPlaylist(request.Scheme, request.Host, request.BaseUrl, channels, request.AccessToken)); + .Map( + channels => new ChannelPlaylist( + request.Scheme, + request.Host, + request.BaseUrl, + channels, + request.AccessToken)); private static List EnsureMode(IEnumerable channels, string mode) { diff --git a/ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKeyHandler.cs b/ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKeyHandler.cs index 530c15d8..a075a537 100644 --- a/ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKeyHandler.cs +++ b/ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKeyHandler.cs @@ -9,8 +9,6 @@ public class SaveConfigElementByKeyHandler : IRequestHandler _configElementRepository = configElementRepository; - public async Task Handle(SaveConfigElementByKey request, CancellationToken cancellationToken) - { + public async Task Handle(SaveConfigElementByKey request, CancellationToken cancellationToken) => await _configElementRepository.Upsert(request.Key, request.Value); - } } diff --git a/ErsatzTV.Application/Emby/Commands/CallEmbyCollectionScannerHandler.cs b/ErsatzTV.Application/Emby/Commands/CallEmbyCollectionScannerHandler.cs index ca28b701..1a1ffb5d 100644 --- a/ErsatzTV.Application/Emby/Commands/CallEmbyCollectionScannerHandler.cs +++ b/ErsatzTV.Application/Emby/Commands/CallEmbyCollectionScannerHandler.cs @@ -22,6 +22,23 @@ public class CallEmbyCollectionScannerHandler : CallLibraryScannerHandler> + Handle(SynchronizeEmbyCollections request, CancellationToken cancellationToken) + { + Validation validation = await Validate(request); + return await validation.Match( + scanner => PerformScan(scanner, request, cancellationToken), + error => + { + foreach (ScanIsNotRequired scanIsNotRequired in error.OfType()) + { + return Task.FromResult>(scanIsNotRequired); + } + + return Task.FromResult>(error.Join()); + }); + } + protected override async Task GetLastScan(TvContext dbContext, SynchronizeEmbyCollections request) { DateTime minDateTime = await dbContext.EmbyMediaSources @@ -42,26 +59,9 @@ public class CallEmbyCollectionScannerHandler : CallLibraryScannerHandler 0 && nextScan < DateTimeOffset.Now); + return request.ForceScan || libraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now; } - public async Task> - Handle(SynchronizeEmbyCollections request, CancellationToken cancellationToken) - { - Validation validation = await Validate(request); - return await validation.Match( - scanner => PerformScan(scanner, request, cancellationToken), - error => - { - foreach (ScanIsNotRequired scanIsNotRequired in error.OfType()) - { - return Task.FromResult>(scanIsNotRequired); - } - - return Task.FromResult>(error.Join()); - }); - } - private async Task> PerformScan( string scanner, SynchronizeEmbyCollections request, diff --git a/ErsatzTV.Application/Emby/Commands/CallEmbyLibraryScannerHandler.cs b/ErsatzTV.Application/Emby/Commands/CallEmbyLibraryScannerHandler.cs index d4eb0b8a..da9e4af6 100644 --- a/ErsatzTV.Application/Emby/Commands/CallEmbyLibraryScannerHandler.cs +++ b/ErsatzTV.Application/Emby/Commands/CallEmbyLibraryScannerHandler.cs @@ -28,9 +28,10 @@ public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler Handle(request, cancellationToken); - Task> IRequestHandler>.Handle( - SynchronizeEmbyLibraryByIdIfNeeded request, - CancellationToken cancellationToken) => Handle(request, cancellationToken); + Task> IRequestHandler>. + Handle( + SynchronizeEmbyLibraryByIdIfNeeded request, + CancellationToken cancellationToken) => Handle(request, cancellationToken); private async Task> Handle( ISynchronizeEmbyLibraryById request, @@ -80,7 +81,7 @@ public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler l.Id, l => l.Id == request.EmbyLibraryId) .Match(l => l.LastScan ?? SystemTime.MinValueUtc, () => SystemTime.MaxValueUtc); - + return new DateTimeOffset(minDateTime, TimeSpan.Zero); } @@ -95,6 +96,6 @@ public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler 0 && nextScan < DateTimeOffset.Now); + return request.ForceScan || libraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now; } } diff --git a/ErsatzTV.Application/Emby/Commands/SynchronizeEmbyMediaSourcesHandler.cs b/ErsatzTV.Application/Emby/Commands/SynchronizeEmbyMediaSourcesHandler.cs index 9793ecf6..20963420 100644 --- a/ErsatzTV.Application/Emby/Commands/SynchronizeEmbyMediaSourcesHandler.cs +++ b/ErsatzTV.Application/Emby/Commands/SynchronizeEmbyMediaSourcesHandler.cs @@ -8,8 +8,8 @@ namespace ErsatzTV.Application.Emby; public class SynchronizeEmbyMediaSourcesHandler : IRequestHandler>> { - private readonly ChannelWriter _scannerWorkerChannel; private readonly IMediaSourceRepository _mediaSourceRepository; + private readonly ChannelWriter _scannerWorkerChannel; public SynchronizeEmbyMediaSourcesHandler( IMediaSourceRepository mediaSourceRepository, diff --git a/ErsatzTV.Application/Emby/Queries/GetEmbyConnectionParametersHandler.cs b/ErsatzTV.Application/Emby/Queries/GetEmbyConnectionParametersHandler.cs index fb2832e5..672edfb8 100644 --- a/ErsatzTV.Application/Emby/Queries/GetEmbyConnectionParametersHandler.cs +++ b/ErsatzTV.Application/Emby/Queries/GetEmbyConnectionParametersHandler.cs @@ -10,8 +10,8 @@ namespace ErsatzTV.Application.Emby; public class GetEmbyConnectionParametersHandler : IRequestHandler> { - private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IEmbySecretStore _embySecretStore; + private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMemoryCache _memoryCache; public GetEmbyConnectionParametersHandler( @@ -65,7 +65,7 @@ public class GetEmbyConnectionParametersHandler : IRequestHandler new ConnectionParameters(embyMediaSource, connection)) .ToValidation("Emby media source requires an active connection"); } - + private async Task> MediaSourceMustHaveApiKey( ConnectionParameters connectionParameters) { diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/DeleteFFmpegProfileHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/DeleteFFmpegProfileHandler.cs index 035e46ef..126c8e84 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Commands/DeleteFFmpegProfileHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Commands/DeleteFFmpegProfileHandler.cs @@ -19,7 +19,7 @@ public class DeleteFFmpegProfileHandler : IRequestHandler validation = await FFmpegProfileMustExist(dbContext, request); - return await LanguageExtensions.Apply(validation, p => DoDeletion(dbContext, p)); + return await validation.Apply(p => DoDeletion(dbContext, p)); } private static async Task DoDeletion(TvContext dbContext, FFmpegProfile ffmpegProfile) diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs index e4ec0640..9550cee6 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs @@ -20,7 +20,7 @@ public class { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); Validation validation = await Validate(dbContext, request); - return await validation.Apply(p => ApplyUpdateRequest(dbContext, p, request)); + return await LanguageExtensions.Apply(validation, p => ApplyUpdateRequest(dbContext, p, request)); } private async Task ApplyUpdateRequest( @@ -36,12 +36,12 @@ public class p.QsvExtraHardwareFrames = update.QsvExtraHardwareFrames; p.ResolutionId = update.ResolutionId; p.VideoFormat = update.VideoFormat; - + // mpeg2video only supports 8-bit content p.BitDepth = update.VideoFormat == FFmpegProfileVideoFormat.Mpeg2Video ? FFmpegProfileBitDepth.EightBit : update.BitDepth; - + p.VideoBitrate = update.VideoBitrate; p.VideoBufferSize = update.VideoBufferSize; p.AudioFormat = update.AudioFormat; diff --git a/ErsatzTV.Application/Filler/Commands/DeleteFillerPresetHandler.cs b/ErsatzTV.Application/Filler/Commands/DeleteFillerPresetHandler.cs index ac134e12..08406e57 100644 --- a/ErsatzTV.Application/Filler/Commands/DeleteFillerPresetHandler.cs +++ b/ErsatzTV.Application/Filler/Commands/DeleteFillerPresetHandler.cs @@ -19,7 +19,7 @@ public class DeleteFillerPresetHandler : IRequestHandler validation = await FillerPresetMustExist(dbContext, request); - return await LanguageExtensions.Apply(validation, ps => DoDeletion(dbContext, ps)); + return await validation.Apply(ps => DoDeletion(dbContext, ps)); } private static Task DoDeletion(TvContext dbContext, FillerPreset fillerPreset) diff --git a/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinLibraryScannerHandler.cs b/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinLibraryScannerHandler.cs index 3e55e098..0804c2e6 100644 --- a/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinLibraryScannerHandler.cs +++ b/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinLibraryScannerHandler.cs @@ -24,13 +24,15 @@ public class CallJellyfinLibraryScannerHandler : CallLibraryScannerHandler> IRequestHandler>.Handle( - ForceSynchronizeJellyfinLibraryById request, - CancellationToken cancellationToken) => Handle(request, cancellationToken); + Task> IRequestHandler>. + Handle( + ForceSynchronizeJellyfinLibraryById request, + CancellationToken cancellationToken) => Handle(request, cancellationToken); - Task> IRequestHandler>.Handle( - SynchronizeJellyfinLibraryByIdIfNeeded request, - CancellationToken cancellationToken) => Handle(request, cancellationToken); + Task> IRequestHandler>. + Handle( + SynchronizeJellyfinLibraryByIdIfNeeded request, + CancellationToken cancellationToken) => Handle(request, cancellationToken); private async Task> Handle( ISynchronizeJellyfinLibraryById request, @@ -64,7 +66,7 @@ public class CallJellyfinLibraryScannerHandler : CallLibraryScannerHandler 0 && nextScan < DateTimeOffset.Now); + return request.ForceScan || libraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now; } } diff --git a/ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinMediaSourcesHandler.cs b/ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinMediaSourcesHandler.cs index 67eec5d4..371ee31e 100644 --- a/ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinMediaSourcesHandler.cs +++ b/ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinMediaSourcesHandler.cs @@ -8,8 +8,8 @@ namespace ErsatzTV.Application.Jellyfin; public class SynchronizeJellyfinMediaSourcesHandler : IRequestHandler>> { - private readonly ChannelWriter _scannerWorkerChannel; private readonly IMediaSourceRepository _mediaSourceRepository; + private readonly ChannelWriter _scannerWorkerChannel; public SynchronizeJellyfinMediaSourcesHandler( IMediaSourceRepository mediaSourceRepository, @@ -26,7 +26,9 @@ public class SynchronizeJellyfinMediaSourcesHandler : IRequestHandler mediaSources = await _mediaSourceRepository.GetAllJellyfin(); foreach (JellyfinMediaSource mediaSource in mediaSources) { - await _scannerWorkerChannel.WriteAsync(new SynchronizeJellyfinAdminUserId(mediaSource.Id), cancellationToken); + await _scannerWorkerChannel.WriteAsync( + new SynchronizeJellyfinAdminUserId(mediaSource.Id), + cancellationToken); await _scannerWorkerChannel.WriteAsync(new SynchronizeJellyfinLibraries(mediaSource.Id), cancellationToken); } diff --git a/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs b/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs index 7f9be9b5..ef96e004 100644 --- a/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs +++ b/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs @@ -20,9 +20,9 @@ namespace ErsatzTV.Application.Libraries; public abstract class CallLibraryScannerHandler { - private readonly IDbContextFactory _dbContextFactory; - private readonly IConfigElementRepository _configElementRepository; private readonly ChannelWriter _channel; + private readonly IConfigElementRepository _configElementRepository; + private readonly IDbContextFactory _dbContextFactory; private readonly IMediator _mediator; private readonly IRuntimeInfo _runtimeInfo; private string _libraryName; @@ -152,9 +152,9 @@ public abstract class CallLibraryScannerHandler .IfNoneAsync(0); libraryRefreshInterval = Math.Clamp(libraryRefreshInterval, 0, 999_999); - + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); - + DateTimeOffset lastScan = await GetLastScan(dbContext, request); if (!ScanIsRequired(lastScan, libraryRefreshInterval, request)) { @@ -164,7 +164,7 @@ public abstract class CallLibraryScannerHandler string executable = _runtimeInfo.IsOSPlatform(OSPlatform.Windows) ? "ErsatzTV.Scanner.exe" : "ErsatzTV.Scanner"; - + string processFileName = Environment.ProcessPath ?? string.Empty; if (!string.IsNullOrWhiteSpace(processFileName)) { diff --git a/ErsatzTV.Application/Libraries/Commands/CreateLocalLibraryHandler.cs b/ErsatzTV.Application/Libraries/Commands/CreateLocalLibraryHandler.cs index fbaca344..c6ac9280 100644 --- a/ErsatzTV.Application/Libraries/Commands/CreateLocalLibraryHandler.cs +++ b/ErsatzTV.Application/Libraries/Commands/CreateLocalLibraryHandler.cs @@ -32,7 +32,7 @@ public class CreateLocalLibraryHandler : LocalLibraryHandlerBase, { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); Validation validation = await Validate(dbContext, request); - return await validation.Apply(localLibrary => PersistLocalLibrary(dbContext, localLibrary)); + return await LanguageExtensions.Apply(validation, localLibrary => PersistLocalLibrary(dbContext, localLibrary)); } private async Task PersistLocalLibrary( diff --git a/ErsatzTV.Application/Libraries/Commands/MoveLocalLibraryPathHandler.cs b/ErsatzTV.Application/Libraries/Commands/MoveLocalLibraryPathHandler.cs index e223e29f..36211f84 100644 --- a/ErsatzTV.Application/Libraries/Commands/MoveLocalLibraryPathHandler.cs +++ b/ErsatzTV.Application/Libraries/Commands/MoveLocalLibraryPathHandler.cs @@ -39,7 +39,7 @@ public class MoveLocalLibraryPathHandler : IRequestHandler validation = await Validate(dbContext, request); - return await validation.Apply(parameters => MovePath(dbContext, parameters)); + return await LanguageExtensions.Apply(validation, parameters => MovePath(dbContext, parameters)); } private async Task MovePath(TvContext dbContext, Parameters parameters) diff --git a/ErsatzTV.Application/Libraries/Commands/UpdateLocalLibraryHandler.cs b/ErsatzTV.Application/Libraries/Commands/UpdateLocalLibraryHandler.cs index abffb513..9e25b816 100644 --- a/ErsatzTV.Application/Libraries/Commands/UpdateLocalLibraryHandler.cs +++ b/ErsatzTV.Application/Libraries/Commands/UpdateLocalLibraryHandler.cs @@ -16,8 +16,8 @@ public class UpdateLocalLibraryHandler : LocalLibraryHandlerBase, { private readonly IDbContextFactory _dbContextFactory; private readonly IEntityLocker _entityLocker; - private readonly ISearchIndex _searchIndex; private readonly ChannelWriter _scannerWorkerChannel; + private readonly ISearchIndex _searchIndex; public UpdateLocalLibraryHandler( ChannelWriter scannerWorkerChannel, diff --git a/ErsatzTV.Application/Libraries/Queries/GetExternalCollectionsHandler.cs b/ErsatzTV.Application/Libraries/Queries/GetExternalCollectionsHandler.cs index 1e216762..26ee6aec 100644 --- a/ErsatzTV.Application/Libraries/Queries/GetExternalCollectionsHandler.cs +++ b/ErsatzTV.Application/Libraries/Queries/GetExternalCollectionsHandler.cs @@ -8,10 +8,8 @@ public class GetExternalCollectionsHandler : IRequestHandler _dbContextFactory; - public GetExternalCollectionsHandler(IDbContextFactory dbContextFactory) - { + public GetExternalCollectionsHandler(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; - } public async Task> Handle( GetExternalCollections request, @@ -21,7 +19,7 @@ public class GetExternalCollectionsHandler : IRequestHandler mediaSourceIds = await dbContext.EmbyMediaSources .Filter(ems => ems.Libraries.Any(l => ((EmbyLibrary)l).ShouldSyncItems)) .Map(ems => ems.Id) - .ToListAsync(cancellationToken: cancellationToken); + .ToListAsync(cancellationToken); return mediaSourceIds.Map( id => new LibraryViewModel( diff --git a/ErsatzTV.Application/Logs/Mapper.cs b/ErsatzTV.Application/Logs/Mapper.cs index fe12365d..fae7927a 100644 --- a/ErsatzTV.Application/Logs/Mapper.cs +++ b/ErsatzTV.Application/Logs/Mapper.cs @@ -7,7 +7,7 @@ internal partial class Mapper { [GeneratedRegex(@"(.*)\[(DBG|INF|WRN|ERR|FTL)\](.*)")] private static partial Regex LogEntryRegex(); - + internal static Option ProjectToViewModel(string line) { Match match = LogEntryRegex().Match(line); diff --git a/ErsatzTV.Application/Logs/Queries/GetRecentLogEntriesHandler.cs b/ErsatzTV.Application/Logs/Queries/GetRecentLogEntriesHandler.cs index ca6aac47..4bc0b295 100644 --- a/ErsatzTV.Application/Logs/Queries/GetRecentLogEntriesHandler.cs +++ b/ErsatzTV.Application/Logs/Queries/GetRecentLogEntriesHandler.cs @@ -8,10 +8,7 @@ public class GetRecentLogEntriesHandler : IRequestHandler _localFileSystem = localFileSystem; public Task Handle( GetRecentLogEntries request, diff --git a/ErsatzTV.Application/Maintenance/Commands/DeleteOrphanedSubtitlesHandler.cs b/ErsatzTV.Application/Maintenance/Commands/DeleteOrphanedSubtitlesHandler.cs index 47d69f05..6bd9b52b 100644 --- a/ErsatzTV.Application/Maintenance/Commands/DeleteOrphanedSubtitlesHandler.cs +++ b/ErsatzTV.Application/Maintenance/Commands/DeleteOrphanedSubtitlesHandler.cs @@ -9,10 +9,8 @@ public class DeleteOrphanedSubtitlesHandler : IRequestHandler _dbContextFactory; - public DeleteOrphanedSubtitlesHandler(IDbContextFactory dbContextFactory) - { + public DeleteOrphanedSubtitlesHandler(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; - } public async Task> Handle( DeleteOrphanedSubtitles request, diff --git a/ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs b/ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs index c4b0be28..1fdf05a2 100644 --- a/ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs +++ b/ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs @@ -7,7 +7,7 @@ namespace ErsatzTV.Application.Maintenance; public class ReleaseMemoryHandler : IRequestHandler { private static long _lastRelease; - + private readonly IFFmpegSegmenterService _ffmpegSegmenterService; private readonly ILogger _logger; @@ -31,12 +31,12 @@ public class ReleaseMemoryHandler : IRequestHandler if (request.ForceAggressive || !hasActiveWorkers) { _logger.LogDebug("Starting aggressive garbage collection"); - GC.Collect(2, GCCollectionMode.Aggressive, blocking: true, compacting: true); + GC.Collect(2, GCCollectionMode.Aggressive, true, true); } else { _logger.LogDebug("Starting garbage collection"); - GC.Collect(2, GCCollectionMode.Forced, blocking: false); + GC.Collect(2, GCCollectionMode.Forced, false); } GC.WaitForPendingFinalizers(); diff --git a/ErsatzTV.Application/MediaCollections/Commands/CreateCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/CreateCollectionHandler.cs index 49fd0531..7f59d257 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/CreateCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/CreateCollectionHandler.cs @@ -20,7 +20,7 @@ public class CreateCollectionHandler : { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); Validation validation = await Validate(dbContext, request); - return await LanguageExtensions.Apply(validation, c => PersistCollection(dbContext, c)); + return await validation.Apply(c => PersistCollection(dbContext, c)); } private static async Task PersistCollection( diff --git a/ErsatzTV.Application/MediaCollections/Commands/CreateMultiCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/CreateMultiCollectionHandler.cs index 69ffcfdb..5495f96c 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/CreateMultiCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/CreateMultiCollectionHandler.cs @@ -20,7 +20,7 @@ public class CreateMultiCollectionHandler : { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); Validation validation = await Validate(dbContext, request); - return await LanguageExtensions.Apply(validation, c => PersistCollection(dbContext, c)); + return await validation.Apply(c => PersistCollection(dbContext, c)); } private static async Task PersistCollection( diff --git a/ErsatzTV.Application/MediaCollections/Commands/CreateSmartCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/CreateSmartCollectionHandler.cs index 93e2340a..49ac8017 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/CreateSmartCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/CreateSmartCollectionHandler.cs @@ -20,7 +20,7 @@ public class CreateSmartCollectionHandler : { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); Validation validation = await Validate(dbContext, request); - return await LanguageExtensions.Apply(validation, c => PersistCollection(dbContext, c)); + return await validation.Apply(c => PersistCollection(dbContext, c)); } private static async Task PersistCollection( diff --git a/ErsatzTV.Application/MediaCollections/Commands/DeleteCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/DeleteCollectionHandler.cs index cd08f42b..dffd6852 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/DeleteCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/DeleteCollectionHandler.cs @@ -20,7 +20,7 @@ public class DeleteCollectionHandler : IRequestHandler validation = await CollectionMustExist(dbContext, request); - return await LanguageExtensions.Apply(validation, c => DoDeletion(dbContext, c)); + return await validation.Apply(c => DoDeletion(dbContext, c)); } private static Task DoDeletion(TvContext dbContext, Collection collection) diff --git a/ErsatzTV.Application/MediaCollections/Commands/DeleteMultiCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/DeleteMultiCollectionHandler.cs index 42d5036c..266c69be 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/DeleteMultiCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/DeleteMultiCollectionHandler.cs @@ -20,7 +20,7 @@ public class DeleteMultiCollectionHandler : IRequestHandler validation = await MultiCollectionMustExist(dbContext, request); - return await LanguageExtensions.Apply(validation, c => DoDeletion(dbContext, c)); + return await validation.Apply(c => DoDeletion(dbContext, c)); } private static Task DoDeletion(TvContext dbContext, MultiCollection multiCollection) diff --git a/ErsatzTV.Application/MediaCollections/Commands/DeleteSmartCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/DeleteSmartCollectionHandler.cs index 3ba6f0d5..26e6d591 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/DeleteSmartCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/DeleteSmartCollectionHandler.cs @@ -20,7 +20,7 @@ public class DeleteSmartCollectionHandler : IRequestHandler validation = await SmartCollectionMustExist(dbContext, request); - return await LanguageExtensions.Apply(validation, c => DoDeletion(dbContext, c)); + return await validation.Apply(c => DoDeletion(dbContext, c)); } private static Task DoDeletion(TvContext dbContext, SmartCollection smartCollection) diff --git a/ErsatzTV.Application/MediaItems/Queries/GetMediaItemInfoHandler.cs b/ErsatzTV.Application/MediaItems/Queries/GetMediaItemInfoHandler.cs index 27d2a2ab..db43da24 100644 --- a/ErsatzTV.Application/MediaItems/Queries/GetMediaItemInfoHandler.cs +++ b/ErsatzTV.Application/MediaItems/Queries/GetMediaItemInfoHandler.cs @@ -11,10 +11,8 @@ public class GetMediaItemInfoHandler : IRequestHandler _dbContextFactory; - public GetMediaItemInfoHandler(IDbContextFactory dbContextFactory) - { + public GetMediaItemInfoHandler(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; - } public async Task> Handle( GetMediaItemInfo request, @@ -56,7 +54,7 @@ public class GetMediaItemInfoHandler : IRequestHandler jellyfinMediaSource.ServerName, _ => null }; - + return new MediaItemInfo( mediaItem.Id, mediaItem.GetType().Name, diff --git a/ErsatzTV.Application/MediaSources/Commands/CallLocalLibraryScannerHandler.cs b/ErsatzTV.Application/MediaSources/Commands/CallLocalLibraryScannerHandler.cs index fe122b0d..6a59b5aa 100644 --- a/ErsatzTV.Application/MediaSources/Commands/CallLocalLibraryScannerHandler.cs +++ b/ErsatzTV.Application/MediaSources/Commands/CallLocalLibraryScannerHandler.cs @@ -71,7 +71,7 @@ public class CallLocalLibraryScannerHandler : CallLibraryScannerHandler libraryPaths = await dbContext.LibraryPaths .Filter(lp => lp.LibraryId == request.LibraryId) .ToListAsync(); - + DateTime minDateTime = libraryPaths.Any() ? libraryPaths.Min(lp => lp.LastScan ?? SystemTime.MinValueUtc) : SystemTime.MaxValueUtc; @@ -90,6 +90,6 @@ public class CallLocalLibraryScannerHandler : CallLibraryScannerHandler 0 && nextScan < DateTimeOffset.Now); + return request.ForceScan || libraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now; } } diff --git a/ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs index be0663ba..fc8e25b0 100644 --- a/ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs @@ -19,8 +19,8 @@ public class BuildPlayoutHandler : IRequestHandler _dbContextFactory; private readonly IFFmpegSegmenterService _ffmpegSegmenterService; - private readonly ChannelWriter _workerChannel; private readonly IPlayoutBuilder _playoutBuilder; + private readonly ChannelWriter _workerChannel; public BuildPlayoutHandler( IClient client, @@ -108,7 +108,6 @@ public class BuildPlayoutHandler : IRequestHandler p.Channel) .Include(p => p.Items) - .Include(p => p.ProgramScheduleAlternates) .ThenInclude(a => a.ProgramSchedule) .ThenInclude(ps => ps.Items) @@ -137,12 +136,10 @@ public class BuildPlayoutHandler : IRequestHandler a.ProgramSchedule) .ThenInclude(ps => ps.Items) .ThenInclude(psi => psi.FallbackFiller) - .Include(p => p.ProgramScheduleAnchors) .ThenInclude(psa => psa.EnumeratorState) .Include(p => p.ProgramScheduleAnchors) .ThenInclude(a => a.MediaItem) - .Include(p => p.ProgramSchedule) .ThenInclude(ps => ps.Items) .ThenInclude(psi => psi.Collection) @@ -164,7 +161,6 @@ public class BuildPlayoutHandler : IRequestHandler p.ProgramSchedule) .ThenInclude(ps => ps.Items) .ThenInclude(psi => psi.FallbackFiller) - .SelectOneAsync(p => p.Id, p => p.Id == buildPlayout.PlayoutId) .Map(o => o.ToValidation("Playout does not exist.")); } diff --git a/ErsatzTV.Application/Playouts/Commands/CreatePlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/CreatePlayoutHandler.cs index faf08aef..30fcfb2e 100644 --- a/ErsatzTV.Application/Playouts/Commands/CreatePlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/CreatePlayoutHandler.cs @@ -29,7 +29,7 @@ public class CreatePlayoutHandler : IRequestHandler validation = await Validate(dbContext, request); - return await validation.Apply(playout => PersistPlayout(dbContext, playout)); + return await LanguageExtensions.Apply(validation, playout => PersistPlayout(dbContext, playout)); } private async Task PersistPlayout(TvContext dbContext, Playout playout) diff --git a/ErsatzTV.Application/Playouts/Commands/DeletePlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/DeletePlayoutHandler.cs index a1289452..3736b77e 100644 --- a/ErsatzTV.Application/Playouts/Commands/DeletePlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/DeletePlayoutHandler.cs @@ -11,9 +11,9 @@ namespace ErsatzTV.Application.Playouts; public class DeletePlayoutHandler : IRequestHandler> { - private readonly ChannelWriter _workerChannel; private readonly IDbContextFactory _dbContextFactory; private readonly ILocalFileSystem _localFileSystem; + private readonly ChannelWriter _workerChannel; public DeletePlayoutHandler( ChannelWriter workerChannel, diff --git a/ErsatzTV.Application/Playouts/Commands/ReplacePlayoutAlternateScheduleItemsHandler.cs b/ErsatzTV.Application/Playouts/Commands/ReplacePlayoutAlternateScheduleItemsHandler.cs index f4ec3cdd..71169eaa 100644 --- a/ErsatzTV.Application/Playouts/Commands/ReplacePlayoutAlternateScheduleItemsHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/ReplacePlayoutAlternateScheduleItemsHandler.cs @@ -12,8 +12,8 @@ namespace ErsatzTV.Application.Playouts; public class ReplacePlayoutAlternateScheduleItemsHandler : IRequestHandler> { - private readonly IDbContextFactory _dbContextFactory; private readonly ChannelWriter _channel; + private readonly IDbContextFactory _dbContextFactory; private readonly ILogger _logger; public ReplacePlayoutAlternateScheduleItemsHandler( @@ -46,7 +46,7 @@ public class ReplacePlayoutAlternateScheduleItemsHandler : { var existingScheduleMap = new Dictionary(); var daysToCheck = new List(); - + Option maybeLastPlayoutItem = await dbContext.PlayoutItems .Filter(pi => pi.PlayoutId == request.PlayoutId) .OrderByDescending(pi => pi.Start) @@ -146,7 +146,7 @@ public class ReplacePlayoutAlternateScheduleItemsHandler : } } } - + return Unit.Default; } catch (Exception ex) diff --git a/ErsatzTV.Application/Playouts/Commands/UpdatePlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/UpdatePlayoutHandler.cs index 95e56cd4..1e97e0cf 100644 --- a/ErsatzTV.Application/Playouts/Commands/UpdatePlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/UpdatePlayoutHandler.cs @@ -19,7 +19,7 @@ public class UpdatePlayoutHandler : IRequestHandler validation = await Validate(dbContext, request); - return await LanguageExtensions.Apply(validation, playout => ApplyUpdateRequest(dbContext, request, playout)); + return await validation.Apply(playout => ApplyUpdateRequest(dbContext, request, playout)); } private static async Task ApplyUpdateRequest( diff --git a/ErsatzTV.Application/Plex/Commands/CallPlexLibraryScannerHandler.cs b/ErsatzTV.Application/Plex/Commands/CallPlexLibraryScannerHandler.cs index fb1ec4f1..20d2cbdc 100644 --- a/ErsatzTV.Application/Plex/Commands/CallPlexLibraryScannerHandler.cs +++ b/ErsatzTV.Application/Plex/Commands/CallPlexLibraryScannerHandler.cs @@ -28,9 +28,10 @@ public class CallPlexLibraryScannerHandler : CallLibraryScannerHandler Handle(request, cancellationToken); - Task> IRequestHandler>.Handle( - SynchronizePlexLibraryByIdIfNeeded request, - CancellationToken cancellationToken) => Handle(request, cancellationToken); + Task> IRequestHandler>. + Handle( + SynchronizePlexLibraryByIdIfNeeded request, + CancellationToken cancellationToken) => Handle(request, cancellationToken); private async Task> Handle( ISynchronizePlexLibraryById request, @@ -77,10 +78,10 @@ public class CallPlexLibraryScannerHandler : CallLibraryScannerHandler l.Id, l => l.Id == request.PlexLibraryId) .Match(l => l.LastScan ?? SystemTime.MinValueUtc, () => SystemTime.MaxValueUtc); - + return new DateTimeOffset(minDateTime, TimeSpan.Zero); } @@ -95,6 +96,6 @@ public class CallPlexLibraryScannerHandler : CallLibraryScannerHandler 0 && nextScan < DateTimeOffset.Now); + return request.ForceScan || libraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now; } } diff --git a/ErsatzTV.Application/ProgramSchedules/Commands/CopyProgramScheduleHandler.cs b/ErsatzTV.Application/ProgramSchedules/Commands/CopyProgramScheduleHandler.cs index c836a8ac..fc540ae0 100644 --- a/ErsatzTV.Application/ProgramSchedules/Commands/CopyProgramScheduleHandler.cs +++ b/ErsatzTV.Application/ProgramSchedules/Commands/CopyProgramScheduleHandler.cs @@ -23,7 +23,9 @@ public class { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); Validation validation = await Validate(dbContext, request); - return await validation.Apply(p => PerformCopy(dbContext, p, request, cancellationToken)); + return await LanguageExtensions.Apply( + validation, + p => PerformCopy(dbContext, p, request, cancellationToken)); } catch (Exception ex) { diff --git a/ErsatzTV.Application/ProgramSchedules/Commands/DeleteProgramScheduleHandler.cs b/ErsatzTV.Application/ProgramSchedules/Commands/DeleteProgramScheduleHandler.cs index cddbcd2b..10a33be4 100644 --- a/ErsatzTV.Application/ProgramSchedules/Commands/DeleteProgramScheduleHandler.cs +++ b/ErsatzTV.Application/ProgramSchedules/Commands/DeleteProgramScheduleHandler.cs @@ -19,7 +19,7 @@ public class DeleteProgramScheduleHandler : IRequestHandler validation = await ProgramScheduleMustExist(dbContext, request); - return await LanguageExtensions.Apply(validation, ps => DoDeletion(dbContext, ps)); + return await validation.Apply(ps => DoDeletion(dbContext, ps)); } private static Task DoDeletion(TvContext dbContext, ProgramSchedule programSchedule) diff --git a/ErsatzTV.Application/Search/Queries/QuerySearchIndexEpisodesHandler.cs b/ErsatzTV.Application/Search/Queries/QuerySearchIndexEpisodesHandler.cs index 3fdade4d..59508072 100644 --- a/ErsatzTV.Application/Search/Queries/QuerySearchIndexEpisodesHandler.cs +++ b/ErsatzTV.Application/Search/Queries/QuerySearchIndexEpisodesHandler.cs @@ -19,13 +19,13 @@ namespace ErsatzTV.Application.Search; public class QuerySearchIndexEpisodesHandler : IRequestHandler { + private readonly IClient _client; private readonly IDbContextFactory _dbContextFactory; private readonly IEmbyPathReplacementService _embyPathReplacementService; private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly IJellyfinPathReplacementService _jellyfinPathReplacementService; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IPlexPathReplacementService _plexPathReplacementService; - private readonly IClient _client; private readonly ISearchIndex _searchIndex; private readonly ITelevisionRepository _televisionRepository; diff --git a/ErsatzTV.Application/Search/Queries/QuerySearchIndexMoviesHandler.cs b/ErsatzTV.Application/Search/Queries/QuerySearchIndexMoviesHandler.cs index 917985b4..34813096 100644 --- a/ErsatzTV.Application/Search/Queries/QuerySearchIndexMoviesHandler.cs +++ b/ErsatzTV.Application/Search/Queries/QuerySearchIndexMoviesHandler.cs @@ -10,9 +10,9 @@ namespace ErsatzTV.Application.Search; public class QuerySearchIndexMoviesHandler : IRequestHandler { + private readonly IClient _client; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMovieRepository _movieRepository; - private readonly IClient _client; private readonly ISearchIndex _searchIndex; public QuerySearchIndexMoviesHandler( diff --git a/ErsatzTV.Application/Search/Queries/QuerySearchIndexMusicVideosHandler.cs b/ErsatzTV.Application/Search/Queries/QuerySearchIndexMusicVideosHandler.cs index 0b8dfa98..9ddf05ea 100644 --- a/ErsatzTV.Application/Search/Queries/QuerySearchIndexMusicVideosHandler.cs +++ b/ErsatzTV.Application/Search/Queries/QuerySearchIndexMusicVideosHandler.cs @@ -15,11 +15,11 @@ namespace ErsatzTV.Application.Search; public class QuerySearchIndexMusicVideosHandler : IRequestHandler { + private readonly IClient _client; private readonly IEmbyPathReplacementService _embyPathReplacementService; private readonly IJellyfinPathReplacementService _jellyfinPathReplacementService; private readonly IMusicVideoRepository _musicVideoRepository; private readonly IPlexPathReplacementService _plexPathReplacementService; - private readonly IClient _client; private readonly ISearchIndex _searchIndex; public QuerySearchIndexMusicVideosHandler( diff --git a/ErsatzTV.Application/Search/Queries/QuerySearchIndexOtherVideosHandler.cs b/ErsatzTV.Application/Search/Queries/QuerySearchIndexOtherVideosHandler.cs index d45c73ee..9769f5e5 100644 --- a/ErsatzTV.Application/Search/Queries/QuerySearchIndexOtherVideosHandler.cs +++ b/ErsatzTV.Application/Search/Queries/QuerySearchIndexOtherVideosHandler.cs @@ -11,8 +11,8 @@ public class QuerySearchIndexOtherVideosHandler : IRequestHandler { - private readonly IOtherVideoRepository _otherVideoRepository; private readonly IClient _client; + private readonly IOtherVideoRepository _otherVideoRepository; private readonly ISearchIndex _searchIndex; public QuerySearchIndexOtherVideosHandler( diff --git a/ErsatzTV.Application/Search/Queries/QuerySearchIndexSeasonsHandler.cs b/ErsatzTV.Application/Search/Queries/QuerySearchIndexSeasonsHandler.cs index 45f6ee5f..ff4526d9 100644 --- a/ErsatzTV.Application/Search/Queries/QuerySearchIndexSeasonsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/QuerySearchIndexSeasonsHandler.cs @@ -11,8 +11,8 @@ namespace ErsatzTV.Application.Search; public class QuerySearchIndexSeasonsHandler : IRequestHandler { - private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IClient _client; + private readonly IMediaSourceRepository _mediaSourceRepository; private readonly ISearchIndex _searchIndex; private readonly ITelevisionRepository _televisionRepository; diff --git a/ErsatzTV.Application/Search/Queries/QuerySearchIndexShowsHandler.cs b/ErsatzTV.Application/Search/Queries/QuerySearchIndexShowsHandler.cs index 498dd5aa..e6247308 100644 --- a/ErsatzTV.Application/Search/Queries/QuerySearchIndexShowsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/QuerySearchIndexShowsHandler.cs @@ -11,8 +11,8 @@ namespace ErsatzTV.Application.Search; public class QuerySearchIndexShowsHandler : IRequestHandler { - private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IClient _client; + private readonly IMediaSourceRepository _mediaSourceRepository; private readonly ISearchIndex _searchIndex; private readonly ITelevisionRepository _televisionRepository; diff --git a/ErsatzTV.Application/Search/Queries/SearchCollectionsHandler.cs b/ErsatzTV.Application/Search/Queries/SearchCollectionsHandler.cs index d34888b5..739965c6 100644 --- a/ErsatzTV.Application/Search/Queries/SearchCollectionsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/SearchCollectionsHandler.cs @@ -12,7 +12,9 @@ public class SearchCollectionsHandler : IRequestHandler dbContextFactory) => _dbContextFactory = dbContextFactory; - public async Task> Handle(SearchCollections request, CancellationToken cancellationToken) + public async Task> Handle( + SearchCollections request, + CancellationToken cancellationToken) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); return await dbContext.Collections.FromSqlRaw( diff --git a/ErsatzTV.Application/Search/Queries/SearchMultiCollectionsHandler.cs b/ErsatzTV.Application/Search/Queries/SearchMultiCollectionsHandler.cs index af900339..1f4ec927 100644 --- a/ErsatzTV.Application/Search/Queries/SearchMultiCollectionsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/SearchMultiCollectionsHandler.cs @@ -12,7 +12,9 @@ public class SearchMultiCollectionsHandler : IRequestHandler dbContextFactory) => _dbContextFactory = dbContextFactory; - public async Task> Handle(SearchMultiCollections request, CancellationToken cancellationToken) + public async Task> Handle( + SearchMultiCollections request, + CancellationToken cancellationToken) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); return await dbContext.MultiCollections.FromSqlRaw( diff --git a/ErsatzTV.Application/Search/Queries/SearchSmartCollectionsHandler.cs b/ErsatzTV.Application/Search/Queries/SearchSmartCollectionsHandler.cs index bdc6c322..dbbd7b60 100644 --- a/ErsatzTV.Application/Search/Queries/SearchSmartCollectionsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/SearchSmartCollectionsHandler.cs @@ -12,7 +12,9 @@ public class SearchSmartCollectionsHandler : IRequestHandler dbContextFactory) => _dbContextFactory = dbContextFactory; - public async Task> Handle(SearchSmartCollections request, CancellationToken cancellationToken) + public async Task> Handle( + SearchSmartCollections request, + CancellationToken cancellationToken) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); return await dbContext.SmartCollections.FromSqlRaw( diff --git a/ErsatzTV.Application/Search/Queries/SearchTelevisionSeasonsHandler.cs b/ErsatzTV.Application/Search/Queries/SearchTelevisionSeasonsHandler.cs index e9289460..04575c0f 100644 --- a/ErsatzTV.Application/Search/Queries/SearchTelevisionSeasonsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/SearchTelevisionSeasonsHandler.cs @@ -12,7 +12,9 @@ public class SearchTelevisionSeasonsHandler : IRequestHandler dbContextFactory) => _dbContextFactory = dbContextFactory; - public async Task> Handle(SearchTelevisionSeasons request, CancellationToken cancellationToken) + public async Task> Handle( + SearchTelevisionSeasons request, + CancellationToken cancellationToken) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); return await dbContext.Connection.QueryAsync( diff --git a/ErsatzTV.Application/Search/Queries/SearchTelevisionShowsHandler.cs b/ErsatzTV.Application/Search/Queries/SearchTelevisionShowsHandler.cs index 079ac7a5..a9dcd3b0 100644 --- a/ErsatzTV.Application/Search/Queries/SearchTelevisionShowsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/SearchTelevisionShowsHandler.cs @@ -12,7 +12,9 @@ public class SearchTelevisionShowsHandler : IRequestHandler dbContextFactory) => _dbContextFactory = dbContextFactory; - public async Task> Handle(SearchTelevisionShows request, CancellationToken cancellationToken) + public async Task> Handle( + SearchTelevisionShows request, + CancellationToken cancellationToken) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); return await dbContext.Connection.QueryAsync( diff --git a/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs b/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs index 02b82e30..1778790b 100644 --- a/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs +++ b/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs @@ -16,11 +16,11 @@ namespace ErsatzTV.Application.Streaming; public class StartFFmpegSessionHandler : IRequestHandler> { private readonly IConfigElementRepository _configElementRepository; - private readonly ChannelWriter _workerChannel; private readonly IFFmpegSegmenterService _ffmpegSegmenterService; private readonly ILocalFileSystem _localFileSystem; private readonly ILogger _logger; private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ChannelWriter _workerChannel; public StartFFmpegSessionHandler( ILocalFileSystem localFileSystem, @@ -104,7 +104,7 @@ public class StartFFmpegSessionHandler : IRequestHandler jellyfinItemId = playoutItem.MediaItem switch { @@ -480,7 +480,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< playoutItem, $"http://localhost:{Settings.ListenPort}/media/jellyfin/{itemId}"); } - + // attempt to remotely stream emby Option embyItemId = playoutItem.MediaItem switch { diff --git a/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs b/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs index 82e55f0f..3f4575f8 100644 --- a/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs +++ b/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs @@ -20,9 +20,9 @@ namespace ErsatzTV.Application.Subtitles; public class ExtractEmbeddedSubtitlesHandler : IRequestHandler> { private readonly IDbContextFactory _dbContextFactory; - private readonly ChannelWriter _workerChannel; private readonly ILocalFileSystem _localFileSystem; private readonly ILogger _logger; + private readonly ChannelWriter _workerChannel; public ExtractEmbeddedSubtitlesHandler( IDbContextFactory dbContextFactory, @@ -177,7 +177,8 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler em.Subtitles.Any( s => s.SubtitleKind == SubtitleKind.Embedded && - s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) + s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && + s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) .Map(em => em.EpisodeId) .ToListAsync(cancellationToken); result.AddRange(episodeIds); @@ -188,7 +189,8 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler mm.Subtitles.Any( s => s.SubtitleKind == SubtitleKind.Embedded && - s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) + s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && + s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) .Map(mm => mm.MovieId) .ToListAsync(cancellationToken); result.AddRange(movieIds); @@ -199,7 +201,8 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler mm.Subtitles.Any( s => s.SubtitleKind == SubtitleKind.Embedded && - s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) + s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && + s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) .Map(mm => mm.MusicVideoId) .ToListAsync(cancellationToken); result.AddRange(musicVideoIds); @@ -210,7 +213,8 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler ovm.Subtitles.Any( s => s.SubtitleKind == SubtitleKind.Embedded && - s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) + s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && + s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) .Map(ovm => ovm.OtherVideoId) .ToListAsync(cancellationToken); result.AddRange(otherVideoIds); diff --git a/ErsatzTV.Application/Subtitles/Queries/GetSubtitlePathByIdHandler.cs b/ErsatzTV.Application/Subtitles/Queries/GetSubtitlePathByIdHandler.cs index 02f473bf..a3928990 100644 --- a/ErsatzTV.Application/Subtitles/Queries/GetSubtitlePathByIdHandler.cs +++ b/ErsatzTV.Application/Subtitles/Queries/GetSubtitlePathByIdHandler.cs @@ -12,10 +12,8 @@ public class GetSubtitlePathByIdHandler : IRequestHandler _dbContextFactory; - public GetSubtitlePathByIdHandler(IDbContextFactory dbContextFactory) - { + public GetSubtitlePathByIdHandler(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; - } public async Task> Handle( GetSubtitlePathById request, @@ -34,7 +32,7 @@ public class GetSubtitlePathByIdHandler : IRequestHandler.None; } - + private static async Task> GetEmbyUrl( GetSubtitlePathById request, TvContext dbContext, @@ -165,7 +163,7 @@ public class GetSubtitlePathByIdHandler : IRequestHandler.None; } } diff --git a/ErsatzTV.Application/Templates/Queries/GetMusicVideoCreditTemplatesHandler.cs b/ErsatzTV.Application/Templates/Queries/GetMusicVideoCreditTemplatesHandler.cs index 586de339..23481199 100644 --- a/ErsatzTV.Application/Templates/Queries/GetMusicVideoCreditTemplatesHandler.cs +++ b/ErsatzTV.Application/Templates/Queries/GetMusicVideoCreditTemplatesHandler.cs @@ -7,10 +7,7 @@ public class GetMusicVideoCreditTemplatesHandler : IRequestHandler _localFileSystem = localFileSystem; public Task> Handle(GetMusicVideoCreditTemplates request, CancellationToken cancellationToken) => _localFileSystem.ListFiles(FileSystemLayout.MusicVideoCreditsTemplatesFolder) diff --git a/ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs b/ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs index 20ed6335..5874e63c 100644 --- a/ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs +++ b/ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs @@ -9,7 +9,6 @@ using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.FFmpeg.Capabilities; using ErsatzTV.FFmpeg.Runtime; using ErsatzTV.Infrastructure.Data; -using ErsatzTV.Infrastructure.Runtime; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; @@ -17,12 +16,12 @@ namespace ErsatzTV.Application.Troubleshooting.Queries; public class GetTroubleshootingInfoHandler : IRequestHandler { + private readonly IConfigElementRepository _configElementRepository; private readonly IDbContextFactory _dbContextFactory; - private readonly IHealthCheckService _healthCheckService; private readonly IHardwareCapabilitiesFactory _hardwareCapabilitiesFactory; - private readonly IConfigElementRepository _configElementRepository; - private readonly IRuntimeInfo _runtimeInfo; + private readonly IHealthCheckService _healthCheckService; private readonly IMemoryCache _memoryCache; + private readonly IRuntimeInfo _runtimeInfo; public GetTroubleshootingInfoHandler( IDbContextFactory dbContextFactory, @@ -105,7 +104,8 @@ public class GetTroubleshootingInfoHandler : IRequestHandler GetFFmpegSettings() { diff --git a/ErsatzTV.Application/Watermarks/Commands/CreateWatermarkHandler.cs b/ErsatzTV.Application/Watermarks/Commands/CreateWatermarkHandler.cs index 7dfb9796..9106d012 100644 --- a/ErsatzTV.Application/Watermarks/Commands/CreateWatermarkHandler.cs +++ b/ErsatzTV.Application/Watermarks/Commands/CreateWatermarkHandler.cs @@ -18,7 +18,7 @@ public class CreateWatermarkHandler : IRequestHandler validation = Validate(request); - return await LanguageExtensions.Apply(validation, profile => PersistChannelWatermark(dbContext, profile)); + return await validation.Apply(profile => PersistChannelWatermark(dbContext, profile)); } private static async Task PersistChannelWatermark( diff --git a/ErsatzTV.Application/Watermarks/Commands/DeleteWatermarkHandler.cs b/ErsatzTV.Application/Watermarks/Commands/DeleteWatermarkHandler.cs index 95bb9809..da57f237 100644 --- a/ErsatzTV.Application/Watermarks/Commands/DeleteWatermarkHandler.cs +++ b/ErsatzTV.Application/Watermarks/Commands/DeleteWatermarkHandler.cs @@ -19,7 +19,7 @@ public class DeleteWatermarkHandler : IRequestHandler validation = await WatermarkMustExist(dbContext, request); - return await LanguageExtensions.Apply(validation, p => DoDeletion(dbContext, p)); + return await validation.Apply(p => DoDeletion(dbContext, p)); } private static async Task DoDeletion(TvContext dbContext, ChannelWatermark watermark) diff --git a/ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs b/ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs index 30817395..5eebfbd8 100644 --- a/ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs +++ b/ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs @@ -1,8 +1,8 @@ using System.Runtime.InteropServices; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; -using ErsatzTV.FFmpeg.Runtime; using ErsatzTV.Core.Jellyfin; +using ErsatzTV.FFmpeg.Runtime; using FluentAssertions; using Microsoft.Extensions.Logging; using Moq; diff --git a/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs b/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs index 7c011de3..7a2ed898 100644 --- a/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs +++ b/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs @@ -70,7 +70,7 @@ public class FallbackMetadataProviderTests // metadata.Season.Should().Be(season); metadata.Head().EpisodeNumber.Should().Be(episode); } - + [TestCase("Awesome Show - S01_BLAH.mkv", 0)] [TestCase("Awesome Show - NO_EPISODE_NUMBER_HERE.mkv", 0)] public void GetFallbackMetadata_ShouldHandleNonEpisodes(string path, int episode) diff --git a/ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs b/ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs index 9e9db750..0cd0d1bc 100644 --- a/ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs +++ b/ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs @@ -1,8 +1,8 @@ using System.Runtime.InteropServices; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; -using ErsatzTV.FFmpeg.Runtime; using ErsatzTV.Core.Plex; +using ErsatzTV.FFmpeg.Runtime; using FluentAssertions; using Microsoft.Extensions.Logging; using Moq; diff --git a/ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs index e0734b69..e9a69d60 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs @@ -8,14 +8,11 @@ namespace ErsatzTV.Core.Tests.Scheduling; [TestFixture] public class ChronologicalContentTests { - private CancellationToken _cancellationToken; - [SetUp] - public void SetUp() - { - _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; - } - + public void SetUp() => _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; + + private CancellationToken _cancellationToken; + [Test] public void Episodes_Should_Sort_By_Aired() { diff --git a/ErsatzTV.Core.Tests/Scheduling/CustomOrderContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/CustomOrderContentTests.cs index 5c8b419f..8a4c750c 100644 --- a/ErsatzTV.Core.Tests/Scheduling/CustomOrderContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/CustomOrderContentTests.cs @@ -8,12 +8,9 @@ namespace ErsatzTV.Core.Tests.Scheduling; public class CustomOrderContentTests { private CancellationToken _cancellationToken; - + [SetUp] - public void SetUp() - { - _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; - } + public void SetUp() => _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; [Test] public void MediaItems_Should_Sort_By_CustomOrder() diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs index 8643b1d4..0cf96e21 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs @@ -17,6 +17,9 @@ namespace ErsatzTV.Core.Tests.Scheduling; [TestFixture] public class PlayoutBuilderTests { + [SetUp] + public void SetUp() => _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; + private readonly ILogger _logger; public PlayoutBuilderTests() @@ -33,12 +36,6 @@ public class PlayoutBuilderTests } private CancellationToken _cancellationToken; - - [SetUp] - public void SetUp() - { - _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; - } [TestFixture] public class NewPlayout : PlayoutBuilderTests @@ -2369,7 +2366,7 @@ public class PlayoutBuilderTests public async Task ShuffleFlood_Should_MaintainRandomSeed_MultipleDays() { var mediaItems = new List(); - for (int i = 1; i <= 25; i++) + for (var i = 1; i <= 25; i++) { mediaItems.Add(TestMovie(i, TimeSpan.FromMinutes(55), DateTime.Today.AddHours(i))); } @@ -2389,11 +2386,13 @@ public class PlayoutBuilderTests .First(); lastCheckpoint.EnumeratorState.Seed.Should().BeGreaterThan(0); lastCheckpoint.EnumeratorState.Index.Should().Be(3); - + // we need to mess up the ordering to trigger the problematic behavior // this simulates the way the rows are loaded with EF - PlayoutProgramScheduleAnchor oldest = result.ProgramScheduleAnchors.OrderByDescending(a => a.AnchorDate).Last(); - PlayoutProgramScheduleAnchor newest = result.ProgramScheduleAnchors.OrderByDescending(a => a.AnchorDate).First(); + PlayoutProgramScheduleAnchor oldest = result.ProgramScheduleAnchors.OrderByDescending(a => a.AnchorDate) + .Last(); + PlayoutProgramScheduleAnchor newest = result.ProgramScheduleAnchors.OrderByDescending(a => a.AnchorDate) + .First(); result.ProgramScheduleAnchors = new List { @@ -2416,11 +2415,11 @@ public class PlayoutBuilderTests PlayoutProgramScheduleAnchor continueAnchor = result2.ProgramScheduleAnchors.First(x => x.AnchorDate is null); int secondSeedValue = continueAnchor.EnumeratorState.Seed; - + // the continue anchor should have the same seed as the most recent (last) checkpoint from the first run firstSeedValue.Should().Be(secondSeedValue); } - + [Test] public async Task ShuffleFlood_MultipleSmartCollections_Should_MaintainRandomSeed() { @@ -2440,7 +2439,8 @@ public class PlayoutBuilderTests result.Items.Count.Should().Be(6); result.ProgramScheduleAnchors.Count.Should().Be(2); - PlayoutProgramScheduleAnchor primaryAnchor = result.ProgramScheduleAnchors.First(a => a.SmartCollectionId == 1); + PlayoutProgramScheduleAnchor primaryAnchor = + result.ProgramScheduleAnchors.First(a => a.SmartCollectionId == 1); primaryAnchor.EnumeratorState.Seed.Should().BeGreaterThan(0); primaryAnchor.EnumeratorState.Index.Should().Be(0); @@ -2463,12 +2463,12 @@ public class PlayoutBuilderTests primaryAnchor.EnumeratorState.Index.Should().Be(0); } - + [Test] public async Task ShuffleFlood_MultipleSmartCollections_Should_MaintainRandomSeed_MultipleDays() { var mediaItems = new List(); - for (int i = 1; i <= 100; i++) + for (var i = 1; i <= 100; i++) { mediaItems.Add(TestMovie(i, TimeSpan.FromMinutes(55), DateTime.Today.AddHours(i))); } @@ -2490,7 +2490,7 @@ public class PlayoutBuilderTests .First(); lastCheckpoint.EnumeratorState.Seed.Should().BeGreaterThan(0); lastCheckpoint.EnumeratorState.Index.Should().Be(53); - + int firstSeedValue = lastCheckpoint.EnumeratorState.Seed; for (var i = 1; i < 20; i++) @@ -2866,7 +2866,7 @@ public class PlayoutBuilderTests Collection = mediaCollection, CollectionId = mediaCollection.Id, StartTime = null, - PlaybackOrder = playbackOrder, + PlaybackOrder = playbackOrder }; private static ProgramScheduleItem Flood( @@ -2950,7 +2950,7 @@ public class PlayoutBuilderTests return new TestData(builder, playout); } - + private TestData TestDataFloodForSmartCollectionItems( List mediaItems, PlaybackOrder playbackOrder, diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerBaseTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerBaseTests.cs index 75c208f6..5a7eee80 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerBaseTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerBaseTests.cs @@ -13,9 +13,6 @@ namespace ErsatzTV.Core.Tests.Scheduling; [TestFixture] public class PlayoutModeSchedulerBaseTests : SchedulerTestBase { - private CancellationToken _cancellationToken; - private PlayoutModeSchedulerBase _scheduler; - [SetUp] public void SetUp() { @@ -23,6 +20,9 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase _scheduler = new TestScheduler(); } + private CancellationToken _cancellationToken; + private PlayoutModeSchedulerBase _scheduler; + [TestFixture] public class CalculateEndTimeWithFiller : PlayoutModeSchedulerBaseTests { @@ -369,14 +369,14 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase playoutItems[2].MediaItemId.Should().Be(1); playoutItems[2].StartOffset.Should().Be(startState.CurrentTime + TimeSpan.FromMinutes(11)); } - + [Test] public void Should_Schedule_Post_Roll_After_Padded_Mid_Roll() { // content 45 min, mid roll pad to 60, post roll 5 min // content + post = 50 min, mid roll will add two 5 min items // content + mid + post = 60 min - + Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(45)); Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(5)); Collection collectionThree = TwoItemCollection(5, 6, TimeSpan.FromMinutes(5)); @@ -453,35 +453,35 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase _cancellationToken); playoutItems.Count.Should().Be(5); - + // content chapter 1 playoutItems[0].MediaItemId.Should().Be(1); playoutItems[0].StartOffset.Should().Be(startState.CurrentTime); - + // mid-roll 1 playoutItems[1].MediaItemId.Should().Be(3); playoutItems[1].StartOffset.Should().Be(startState.CurrentTime + TimeSpan.FromMinutes(6)); - + // mid-roll 2 playoutItems[2].MediaItemId.Should().Be(4); playoutItems[2].StartOffset.Should().Be(startState.CurrentTime + TimeSpan.FromMinutes(11)); - + // content chapter 2 playoutItems[3].MediaItemId.Should().Be(1); playoutItems[3].StartOffset.Should().Be(startState.CurrentTime + TimeSpan.FromMinutes(16)); - + // post-roll playoutItems[4].MediaItemId.Should().Be(5); playoutItems[4].StartOffset.Should().Be(startState.CurrentTime + TimeSpan.FromMinutes(55)); } - + [Test] public void Should_Schedule_Padded_Post_Roll_After_Mid_Roll_Count() { // content 45 min, mid roll 5 min, post roll pad to 60 // content + mid = 50 min, post roll will add two 5 min items // content + mid + post = 60 min - + Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(45)); Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(5)); Collection collectionThree = TwoItemCollection(5, 6, TimeSpan.FromMinutes(5)); @@ -558,15 +558,15 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase _cancellationToken); playoutItems.Count.Should().Be(5); - + // content chapter 1 playoutItems[0].MediaItemId.Should().Be(1); playoutItems[0].StartOffset.Should().Be(startState.CurrentTime); - + // mid-roll 1 playoutItems[1].MediaItemId.Should().Be(3); playoutItems[1].StartOffset.Should().Be(startState.CurrentTime + TimeSpan.FromMinutes(6)); - + // content chapter 2 playoutItems[2].MediaItemId.Should().Be(1); playoutItems[2].StartOffset.Should().Be(startState.CurrentTime + TimeSpan.FromMinutes(11)); @@ -574,7 +574,7 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase // post-roll 1 playoutItems[3].MediaItemId.Should().Be(5); playoutItems[3].StartOffset.Should().Be(startState.CurrentTime + TimeSpan.FromMinutes(50)); - + // post-roll 2 playoutItems[4].MediaItemId.Should().Be(6); playoutItems[4].StartOffset.Should().Be(startState.CurrentTime + TimeSpan.FromMinutes(55)); diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs index 630a6406..29791529 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs @@ -11,13 +11,10 @@ namespace ErsatzTV.Core.Tests.Scheduling; [TestFixture] public class PlayoutModeSchedulerDurationTests : SchedulerTestBase { - private CancellationToken _cancellationToken; - [SetUp] - public void SetUp() - { - _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; - } + public void SetUp() => _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; + + private CancellationToken _cancellationToken; [Test] public void Should_Fill_Exact_Duration() @@ -301,7 +298,7 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase playoutItems[2].StartOffset.Should().Be(startState.CurrentTime.Add(new TimeSpan(1, 50, 0))); playoutItems[2].GuideGroup.Should().Be(3); playoutItems[2].FillerKind.Should().Be(FillerKind.None); - + // offline should not set guide finish playoutItems[2].GuideFinish.HasValue.Should().BeFalse(); } diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs index 9c89eaf8..a806417a 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs @@ -11,13 +11,10 @@ namespace ErsatzTV.Core.Tests.Scheduling; [TestFixture] public class PlayoutModeSchedulerFloodTests : SchedulerTestBase { - private CancellationToken _cancellationToken; - [SetUp] - public void SetUp() - { - _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; - } + public void SetUp() => _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; + + private CancellationToken _cancellationToken; [Test] public void Should_Fill_Exactly_To_Next_Schedule_Item() @@ -94,7 +91,7 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase playoutItems[2].FillerKind.Should().Be(FillerKind.None); playoutItems[2].CustomTitle.Should().Be("CustomTitle"); } - + [Test] public void Should_Schedule_Single_Item_Fixed_Start_Flood() { @@ -174,13 +171,13 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase playoutItems[3].GuideGroup.Should().Be(1); playoutItems[3].FillerKind.Should().Be(FillerKind.None); playoutItems[3].CustomTitle.Should().Be("CustomTitle"); - + playoutItems[4].MediaItemId.Should().Be(1); playoutItems[4].StartOffset.Should().Be(startState.CurrentTime.AddHours(4)); playoutItems[4].GuideGroup.Should().Be(1); playoutItems[4].FillerKind.Should().Be(FillerKind.None); playoutItems[4].CustomTitle.Should().Be("CustomTitle"); - + playoutItems[5].MediaItemId.Should().Be(2); playoutItems[5].StartOffset.Should().Be(startState.CurrentTime.AddHours(5)); playoutItems[5].GuideGroup.Should().Be(1); @@ -840,7 +837,7 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase playoutItems[6].GuideGroup.Should().Be(3); playoutItems[6].FillerKind.Should().Be(FillerKind.Fallback); } - + [Test] public void Should_Not_Schedule_Fallback_Filler_Incomplete_Flood() { @@ -885,7 +882,7 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase PlayoutBuilderState startState = StartState(scheduleItemsEnumerator); var scheduler = new PlayoutModeSchedulerFlood(new Mock().Object); - + // hard stop at 2, an hour before the "next schedule item" at 3 DateTimeOffset hardStop = StartState(scheduleItemsEnumerator).CurrentTime.AddHours(2); @@ -946,7 +943,7 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase playoutItems[5].GuideGroup.Should().Be(6); playoutItems[5].FillerKind.Should().Be(FillerKind.None); } - + [Test] public void Should_Not_Schedule_Tail_Filler_Incomplete_Flood() { @@ -991,7 +988,7 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase PlayoutBuilderState startState = StartState(scheduleItemsEnumerator); var scheduler = new PlayoutModeSchedulerFlood(new Mock().Object); - + // hard stop at 2, an hour before the "next schedule item" at 3 DateTimeOffset hardStop = StartState(scheduleItemsEnumerator).CurrentTime.AddHours(2); diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs index 5473e051..66ad876b 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs @@ -11,13 +11,10 @@ namespace ErsatzTV.Core.Tests.Scheduling; [TestFixture] public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase { - private CancellationToken _cancellationToken; - [SetUp] - public void SetUp() - { - _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; - } + public void SetUp() => _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; + + private CancellationToken _cancellationToken; [Test] public void Should_Fill_Exactly_To_Next_Schedule_Item() diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerOneTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerOneTests.cs index 4146deb6..39aedb3f 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerOneTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerOneTests.cs @@ -11,13 +11,10 @@ namespace ErsatzTV.Core.Tests.Scheduling; [TestFixture] public class PlayoutModeSchedulerOneTests : SchedulerTestBase { - private CancellationToken _cancellationToken; - [SetUp] - public void SetUp() - { - _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; - } + public void SetUp() => _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; + + private CancellationToken _cancellationToken; [Test] public void Should_Have_Gap_With_No_Tail_No_Fallback() diff --git a/ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs index 6c72ebb6..7bd92838 100644 --- a/ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs @@ -8,6 +8,9 @@ namespace ErsatzTV.Core.Tests.Scheduling; [TestFixture] public class RandomizedContentTests { + [SetUp] + public void SetUp() => _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; + private const int KnownSeed = 22295; private readonly List _expected = new() @@ -18,12 +21,6 @@ public class RandomizedContentTests }; private CancellationToken _cancellationToken; - - [SetUp] - public void SetUp() - { - _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; - } [Test] public void Episodes_Should_Randomize() diff --git a/ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs b/ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs index d7463c8d..200bb57a 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs @@ -30,10 +30,12 @@ namespace ErsatzTV.Core.Tests.Scheduling; [Explicit] public class ScheduleIntegrationTests { + [SetUp] + public void SetUp() => _cancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token; + private CancellationToken _cancellationToken; - public ScheduleIntegrationTests() - { + public ScheduleIntegrationTests() => Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) @@ -41,13 +43,6 @@ public class ScheduleIntegrationTests .WriteTo.Console() .Destructure.UsingAttributes() .CreateLogger(); - } - - [SetUp] - public void SetUp() - { - _cancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token; - } [Test] public async Task TestExistingData() @@ -82,13 +77,13 @@ public class ScheduleIntegrationTests o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); o.MigrationsAssembly("ErsatzTV.Infrastructure"); })); - + SqlMapper.AddTypeHandler(new DateTimeOffsetHandler()); SqlMapper.AddTypeHandler(new GuidHandler()); SqlMapper.AddTypeHandler(new TimeSpanHandler()); services.AddSingleton((Func)(_ => new SerilogLoggerFactory())); - + services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -119,7 +114,7 @@ public class ScheduleIntegrationTests await searchIndex.Rebuild( provider.GetRequiredService(), provider.GetRequiredService()); - + var builder = new PlayoutBuilder( new ConfigElementRepository(factory), new MediaCollectionRepository(new Mock().Object, searchIndex, factory), @@ -140,10 +135,10 @@ public class ScheduleIntegrationTests await context.SaveChangesAsync(_cancellationToken); } - for (var i = 1; i <= (24 * 1); i++) + for (var i = 1; i <= 24 * 1; i++) { await using TvContext context = await factory.CreateDbContextAsync(_cancellationToken); - + Option maybePlayout = await GetPlayout(context, PLAYOUT_ID); Playout playout = maybePlayout.ValueUnsafe(); @@ -156,11 +151,11 @@ public class ScheduleIntegrationTests await context.SaveChangesAsync(_cancellationToken); } - + for (var i = 25; i <= 26; i++) { await using TvContext context = await factory.CreateDbContextAsync(_cancellationToken); - + Option maybePlayout = await GetPlayout(context, PLAYOUT_ID); Playout playout = maybePlayout.ValueUnsafe(); @@ -204,7 +199,7 @@ public class ScheduleIntegrationTests o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); o.MigrationsAssembly("ErsatzTV.Infrastructure"); })); - + SqlMapper.AddTypeHandler(new DateTimeOffsetHandler()); SqlMapper.AddTypeHandler(new GuidHandler()); SqlMapper.AddTypeHandler(new TimeSpanHandler()); @@ -226,7 +221,7 @@ public class ScheduleIntegrationTests { Path = "Test LibraryPath" }; - + var library = new LocalLibrary { MediaKind = LibraryMediaKind.Movies, @@ -236,7 +231,7 @@ public class ScheduleIntegrationTests await dbContext.Libraries.AddAsync(library, _cancellationToken); await dbContext.SaveChangesAsync(_cancellationToken); - + var movies = new List(); for (var i = 1; i < 25; i++) { @@ -287,9 +282,9 @@ public class ScheduleIntegrationTests int playoutId = await AddTestData(dbContext, scheduleItems); - DateTimeOffset start = new DateTimeOffset(2022, 7, 26, 8, 0, 5, TimeSpan.FromHours(-5)); + var start = new DateTimeOffset(2022, 7, 26, 8, 0, 5, TimeSpan.FromHours(-5)); DateTimeOffset finish = start.AddDays(2); - + var builder = new PlayoutBuilder( new ConfigElementRepository(factory), new MediaCollectionRepository(new Mock().Object, new Mock().Object, factory), @@ -299,10 +294,10 @@ public class ScheduleIntegrationTests new Mock().Object, provider.GetRequiredService>()); - for (var i = 0; i <= (24 * 4); i++) + for (var i = 0; i <= 24 * 4; i++) { await using TvContext context = await factory.CreateDbContextAsync(_cancellationToken); - + Option maybePlayout = await GetPlayout(context, playoutId); Playout playout = maybePlayout.ValueUnsafe(); @@ -326,7 +321,7 @@ public class ScheduleIntegrationTests await dbContext.FFmpegProfiles.AddAsync(ffmpegProfile); await dbContext.SaveChangesAsync(); - + var channel = new Channel(Guid.Parse("00000000-0000-0000-0000-000000000001")) { Name = "Test Channel", @@ -360,12 +355,10 @@ public class ScheduleIntegrationTests return playout.Id; } - private static async Task> GetPlayout(TvContext dbContext, int playoutId) - { - return await dbContext.Playouts + private static async Task> GetPlayout(TvContext dbContext, int playoutId) => + await dbContext.Playouts .Include(p => p.Channel) .Include(p => p.Items) - .Include(p => p.ProgramScheduleAlternates) .ThenInclude(a => a.ProgramSchedule) .ThenInclude(ps => ps.Items) @@ -394,12 +387,10 @@ public class ScheduleIntegrationTests .ThenInclude(a => a.ProgramSchedule) .ThenInclude(ps => ps.Items) .ThenInclude(psi => psi.FallbackFiller) - .Include(p => p.ProgramScheduleAnchors) .ThenInclude(a => a.EnumeratorState) .Include(p => p.ProgramScheduleAnchors) .ThenInclude(a => a.MediaItem) - .Include(p => p.ProgramSchedule) .ThenInclude(ps => ps.Items) .ThenInclude(psi => psi.Collection) @@ -422,5 +413,4 @@ public class ScheduleIntegrationTests .ThenInclude(ps => ps.Items) .ThenInclude(psi => psi.FallbackFiller) .SelectOneAsync(p => p.Id, p => p.Id == playoutId); - } } diff --git a/ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs index 7d3a3acf..5c3960a5 100644 --- a/ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs @@ -8,13 +8,10 @@ namespace ErsatzTV.Core.Tests.Scheduling; [TestFixture] public class SeasonEpisodeContentTests { - private CancellationToken _cancellationToken; - [SetUp] - public void SetUp() - { - _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; - } + public void SetUp() => _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; + + private CancellationToken _cancellationToken; [Test] public void Episodes_Should_Sort_By_EpisodeNumber() diff --git a/ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs index 8973a62f..d598b198 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs @@ -8,16 +8,13 @@ namespace ErsatzTV.Core.Tests.Scheduling; [TestFixture] public class ShuffledContentTests { + [SetUp] + public void SetUp() => _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; + // this seed will produce (shuffle) 1-10 in order private const int MagicSeed = 670596; private CancellationToken _cancellationToken; - - [SetUp] - public void SetUp() - { - _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; - } [Test] public void Episodes_Should_Not_Duplicate_When_Reshuffling() diff --git a/ErsatzTV.Core.Tests/Scheduling/ShuffledMediaCollectionEnumeratorTests.cs b/ErsatzTV.Core.Tests/Scheduling/ShuffledMediaCollectionEnumeratorTests.cs index 67125acc..30fa546b 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ShuffledMediaCollectionEnumeratorTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ShuffledMediaCollectionEnumeratorTests.cs @@ -9,20 +9,17 @@ namespace ErsatzTV.Core.Tests.Scheduling; [TestFixture] public class ShuffledMediaCollectionEnumeratorTests { + [SetUp] + public void SetUp() => _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; + private readonly List _mediaItems = new() { new GroupedMediaItem(new MediaItem { Id = 1 }, new List()), new GroupedMediaItem(new MediaItem { Id = 2 }, new List()), new GroupedMediaItem(new MediaItem { Id = 3 }, new List()) }; - + private CancellationToken _cancellationToken; - - [SetUp] - public void SetUp() - { - _cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; - } [Test] public void Peek_Zero_Should_Match_Current() diff --git a/ErsatzTV.Core/Domain/Filler/FillerKind.cs b/ErsatzTV.Core/Domain/Filler/FillerKind.cs index bb90dd10..53dda31a 100644 --- a/ErsatzTV.Core/Domain/Filler/FillerKind.cs +++ b/ErsatzTV.Core/Domain/Filler/FillerKind.cs @@ -8,6 +8,6 @@ public enum FillerKind PostRoll = 3, Tail = 4, Fallback = 5, - + GuideMode = 99 } diff --git a/ErsatzTV.Core/Domain/MediaItem/Episode.cs b/ErsatzTV.Core/Domain/MediaItem/Episode.cs index 5337f83f..5035061b 100644 --- a/ErsatzTV.Core/Domain/MediaItem/Episode.cs +++ b/ErsatzTV.Core/Domain/MediaItem/Episode.cs @@ -2,7 +2,8 @@ namespace ErsatzTV.Core.Domain; -[DebuggerDisplay("{EpisodeMetadata != null && EpisodeMetadata.Count > 0 ? EpisodeMetadata[0].Title : \"[unknown episode]\"}")] +[DebuggerDisplay( + "{EpisodeMetadata != null && EpisodeMetadata.Count > 0 ? EpisodeMetadata[0].Title : \"[unknown episode]\"}")] public class Episode : MediaItem { public int SeasonId { get; set; } diff --git a/ErsatzTV.Core/Domain/ProgramScheduleAlternate.cs b/ErsatzTV.Core/Domain/ProgramScheduleAlternate.cs index 8d934144..2d05438e 100644 --- a/ErsatzTV.Core/Domain/ProgramScheduleAlternate.cs +++ b/ErsatzTV.Core/Domain/ProgramScheduleAlternate.cs @@ -2,6 +2,16 @@ namespace ErsatzTV.Core.Domain; public class ProgramScheduleAlternate { + public int Id { get; set; } + public int PlayoutId { get; set; } + public Playout Playout { get; set; } + public int ProgramScheduleId { get; set; } + public ProgramSchedule ProgramSchedule { get; set; } + public int Index { get; set; } + public ICollection DaysOfWeek { get; set; } + public ICollection DaysOfMonth { get; set; } + public ICollection MonthsOfYear { get; set; } + public static List AllDaysOfWeek() => new() { DayOfWeek.Monday, @@ -15,14 +25,4 @@ public class ProgramScheduleAlternate public static List AllDaysOfMonth() => Enumerable.Range(1, 31).ToList(); public static List AllMonthsOfYear() => Enumerable.Range(1, 12).ToList(); - - public int Id { get; set; } - public int PlayoutId { get; set; } - public Playout Playout { get; set; } - public int ProgramScheduleId { get; set; } - public ProgramSchedule ProgramSchedule { get; set; } - public int Index { get; set; } - public ICollection DaysOfWeek { get; set; } - public ICollection DaysOfMonth { get; set; } - public ICollection MonthsOfYear { get; set; } } diff --git a/ErsatzTV.Core/Emby/EmbyUrl.cs b/ErsatzTV.Core/Emby/EmbyUrl.cs index ed60d185..dacd1752 100644 --- a/ErsatzTV.Core/Emby/EmbyUrl.cs +++ b/ErsatzTV.Core/Emby/EmbyUrl.cs @@ -40,7 +40,7 @@ public static class EmbyUrl .AppendPathSegment(pathSegment) .SetQueryParams(query); } - + public static string PlaceholderProxyForArtwork(string artwork, ArtworkKind artworkKind, int height) { string[] split = artwork.Replace("emby://", string.Empty).Split('?'); diff --git a/ErsatzTV.Core/Errors/ScanIsNotRequired.cs b/ErsatzTV.Core/Errors/ScanIsNotRequired.cs index 59ef5ccf..df3ed7de 100644 --- a/ErsatzTV.Core/Errors/ScanIsNotRequired.cs +++ b/ErsatzTV.Core/Errors/ScanIsNotRequired.cs @@ -5,4 +5,4 @@ public class ScanIsNotRequired : BaseError public ScanIsNotRequired() : base("Scan is not required") { } -} \ No newline at end of file +} diff --git a/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs index 9417cf34..40b81b48 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs @@ -96,7 +96,7 @@ public class FFmpegComplexFilterBuilder var complexFilter = new StringBuilder(); - string videoLabel = $"{videoInput}:{(isSong ? "v" : videoStreamIndex.ToString())}"; + var videoLabel = $"{videoInput}:{(isSong ? "v" : videoStreamIndex.ToString())}"; string audioLabel = audioStreamIndex.Match(index => $"{audioInput}:{index}", () => "0:a"); var videoFilterQueue = new List(); @@ -229,7 +229,7 @@ public class FFmpegComplexFilterBuilder complexFilter.Append("[vt];"); } - string watermarkLabel = $"[{audioInput + 1}:v]"; + var watermarkLabel = $"[{audioInput + 1}:v]"; foreach (int index in _watermarkIndex) { watermarkLabel = $"[{audioInput + 1}:{index}]"; diff --git a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs index 76446118..7bb7c0f4 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs @@ -18,9 +18,9 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService private readonly FFmpegProcessService _ffmpegProcessService; private readonly IFFmpegStreamSelector _ffmpegStreamSelector; private readonly ILogger _logger; + private readonly IPipelineBuilderFactory _pipelineBuilderFactory; private readonly FFmpegPlaybackSettingsCalculator _playbackSettingsCalculator; private readonly ITempFilePool _tempFilePool; - private readonly IPipelineBuilderFactory _pipelineBuilderFactory; public FFmpegLibraryProcessService( FFmpegProcessService ffmpegProcessService, @@ -154,7 +154,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService videoPath != audioPath || channel.StreamingMode == StreamingMode.HttpLiveStreamingDirect; ILogger pixelFormatLogger = isUnknownPixelFormatExpected ? null : _logger; - IPixelFormat pixelFormat = await AvailablePixelFormats.ForPixelFormat(videoStream.PixelFormat, pixelFormatLogger) + IPixelFormat pixelFormat = await AvailablePixelFormats + .ForPixelFormat(videoStream.PixelFormat, pixelFormatLogger) .IfNoneAsync( () => { @@ -165,7 +166,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService _ => new PixelFormatUnknown(videoStream.BitsPerRawSample) }; }); - + var ffmpegVideoStream = new VideoStream( videoStream.Index, videoStream.Codec, @@ -429,7 +430,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService FileSystemLayout.FFmpegReportsFolder, FileSystemLayout.FontsCacheFolder, ffmpegPath); - + FFmpegPipeline pipeline = pipelineBuilder.Build(ffmpegState, desiredState); return GetCommand(ffmpegPath, videoInputFile, audioInputFile, None, None, pipeline); @@ -459,7 +460,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService FileSystemLayout.FFmpegReportsFolder, FileSystemLayout.FontsCacheFolder, ffmpegPath); - + FFmpegPipeline pipeline = pipelineBuilder.Concat( concatInputFile, FFmpegState.Concat(saveReports, channel.Name)); diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcess.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcess.cs index 41e3c87b..2c0fc62e 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcess.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcess.cs @@ -6,10 +6,7 @@ public class FFmpegProcess : Process { public static int ProcessCount; - public FFmpegProcess() - { - Interlocked.Increment(ref ProcessCount); - } + public FFmpegProcess() => Interlocked.Increment(ref ProcessCount); protected override void Dispose(bool disposing) { diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs index 63adf63b..350e287d 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs @@ -31,10 +31,7 @@ internal class FFmpegProcessBuilder private readonly string _ffmpegPath; private FFmpegComplexFilterBuilder _complexFilterBuilder = new(); - public FFmpegProcessBuilder(string ffmpegPath) - { - _ffmpegPath = ffmpegPath; - } + public FFmpegProcessBuilder(string ffmpegPath) => _ffmpegPath = ffmpegPath; public FFmpegProcessBuilder WithThreads(int threads) { @@ -166,8 +163,8 @@ internal class FFmpegProcessBuilder audioIndex = 1; } - string videoLabel = $"{videoIndex}:{videoStreamIndex}"; - string audioLabel = $"{audioIndex}:{maybeIndex.Match(i => i.ToString(), () => "a")}"; + var videoLabel = $"{videoIndex}:{videoStreamIndex}"; + var audioLabel = $"{audioIndex}:{maybeIndex.Match(i => i.ToString(), () => "a")}"; Option maybeFilter = _complexFilterBuilder.Build( audioPath.IsNone, diff --git a/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs b/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs index e5f5e1a9..51312757 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs @@ -12,12 +12,12 @@ namespace ErsatzTV.Core.FFmpeg; public class FFmpegStreamSelector : IFFmpegStreamSelector { - private readonly IScriptEngine _scriptEngine; - private readonly IStreamSelectorRepository _streamSelectorRepository; - private readonly ISearchRepository _searchRepository; private readonly IConfigElementRepository _configElementRepository; private readonly ILocalFileSystem _localFileSystem; private readonly ILogger _logger; + private readonly IScriptEngine _scriptEngine; + private readonly ISearchRepository _searchRepository; + private readonly IStreamSelectorRepository _streamSelectorRepository; public FFmpegStreamSelector( IScriptEngine scriptEngine, @@ -74,7 +74,7 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector { _logger.LogDebug("Preferred audio language has multiple codes {Codes}", allLanguageCodes); } - + try { switch (version.MediaItem) @@ -92,6 +92,7 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector { return result; } + break; case Movie: var sw2 = Stopwatch.StartNew(); @@ -106,6 +107,7 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector { return result2; } + break; // let default fall through } @@ -248,7 +250,7 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector return PrioritizeDefault(streams); } - + _logger.LogDebug("Unable to find audio stream with preferred title {Title}", title); return PrioritizeDefault(streams); @@ -322,7 +324,8 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector _logger.LogDebug("Checking for JS Script at {Path}", jsScriptPath); if (!_localFileSystem.FileExists(jsScriptPath)) { - _logger.LogInformation("Unable to locate movie audio stream selector script; falling back to built-in logic"); + _logger.LogInformation( + "Unable to locate movie audio stream selector script; falling back to built-in logic"); return Option.None; } diff --git a/ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs b/ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs index e4829e79..1b6e2cc0 100644 --- a/ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs +++ b/ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs @@ -170,7 +170,7 @@ public class SongVideoGenerator : ISongVideoGenerator Streams = new List { new() { MediaStreamKind = MediaStreamKind.Video, Index = 0 } - }, + } }; string customPath = _imageCache.GetPathForImage( diff --git a/ErsatzTV.Core/Iptv/ChannelGuide.cs b/ErsatzTV.Core/Iptv/ChannelGuide.cs index 22a278f5..8607a352 100644 --- a/ErsatzTV.Core/Iptv/ChannelGuide.cs +++ b/ErsatzTV.Core/Iptv/ChannelGuide.cs @@ -6,8 +6,8 @@ namespace ErsatzTV.Core.Iptv; public class ChannelGuide { - private readonly string _channelsFragment; private readonly Dictionary _channelDataFragments; + private readonly string _channelsFragment; private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; public ChannelGuide( @@ -28,7 +28,7 @@ public class ChannelGuide xml.WriteStartElement("tv"); xml.WriteAttributeString("generator-info-name", "ersatztv"); - + xml.WriteRaw(_channelsFragment); foreach ((string channelNumber, string channelDataFragment) in _channelDataFragments.OrderBy( diff --git a/ErsatzTV.Core/Iptv/ChannelPlaylist.cs b/ErsatzTV.Core/Iptv/ChannelPlaylist.cs index ffd70811..8d5da3e0 100644 --- a/ErsatzTV.Core/Iptv/ChannelPlaylist.cs +++ b/ErsatzTV.Core/Iptv/ChannelPlaylist.cs @@ -5,11 +5,11 @@ namespace ErsatzTV.Core.Iptv; public class ChannelPlaylist { + private readonly string _accessToken; + private readonly string _baseUrl; private readonly List _channels; private readonly string _host; - private readonly string _baseUrl; private readonly string _scheme; - private readonly string _accessToken; public ChannelPlaylist(string scheme, string host, string baseUrl, List channels, string accessToken) { @@ -23,7 +23,7 @@ public class ChannelPlaylist public string ToM3U() { var sb = new StringBuilder(); - + string accessTokenUri = string.Empty; string accessTokenUriAmp = string.Empty; if (_accessToken != null) diff --git a/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs index 3971cf2f..991a1834 100644 --- a/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs +++ b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs @@ -19,7 +19,7 @@ public partial class FallbackMetadataProvider : IFallbackMetadataProvider { return seasonNumber; } - + Match match = SeasonPattern.Match(folderName); if (match.Success && int.TryParse(match.Groups[1].Value, out seasonNumber)) { @@ -30,7 +30,7 @@ public partial class FallbackMetadataProvider : IFallbackMetadataProvider { return seasonNumber; } - + if (folder.EndsWith("specials", StringComparison.OrdinalIgnoreCase)) { return 0; @@ -38,9 +38,6 @@ public partial class FallbackMetadataProvider : IFallbackMetadataProvider return None; } - - [GeneratedRegex(@"s(?:eason)?\s?(\d+)(?![e\d])", RegexOptions.IgnoreCase)] - private static partial Regex SeasonNumber(); public ShowMetadata GetFallbackMetadataForShow(string showFolder) { @@ -156,6 +153,9 @@ public partial class FallbackMetadataProvider : IFallbackMetadataProvider return GetSongMetadata(path, metadata); } + [GeneratedRegex(@"s(?:eason)?\s?(\d+)(?![e\d])", RegexOptions.IgnoreCase)] + private static partial Regex SeasonNumber(); + private List GetEpisodeMetadata(string fileName, EpisodeMetadata baseMetadata) { var result = new List { baseMetadata }; diff --git a/ErsatzTV.Core/Scheduling/MultiCollectionGroup.cs b/ErsatzTV.Core/Scheduling/MultiCollectionGroup.cs index 8bb56747..111bee88 100644 --- a/ErsatzTV.Core/Scheduling/MultiCollectionGroup.cs +++ b/ErsatzTV.Core/Scheduling/MultiCollectionGroup.cs @@ -23,20 +23,20 @@ public class MultiCollectionGroup : GroupedMediaItem switch (collectionWithItems.PlaybackOrder) { case PlaybackOrder.Chronological: - { - var sortedItems = collectionWithItems.MediaItems.OrderBy(identity, new ChronologicalMediaComparer()) - .ToList(); - First = sortedItems.Head(); - Additional = sortedItems.Tail().ToList(); - } + { + var sortedItems = collectionWithItems.MediaItems.OrderBy(identity, new ChronologicalMediaComparer()) + .ToList(); + First = sortedItems.Head(); + Additional = sortedItems.Tail().ToList(); + } break; case PlaybackOrder.SeasonEpisode: - { - var sortedItems = collectionWithItems.MediaItems.OrderBy(identity, new SeasonEpisodeMediaComparer()) - .ToList(); - First = sortedItems.Head(); - Additional = sortedItems.Tail().ToList(); - } + { + var sortedItems = collectionWithItems.MediaItems.OrderBy(identity, new SeasonEpisodeMediaComparer()) + .ToList(); + First = sortedItems.Head(); + Additional = sortedItems.Tail().ToList(); + } break; default: throw new NotSupportedException( diff --git a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs index 3a955730..77060dc7 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs @@ -3,7 +3,6 @@ using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Scheduling; -using LanguageExt.UnsafeValueAccess; using Microsoft.Extensions.Logging; using Map = LanguageExt.Map; @@ -15,11 +14,11 @@ public class PlayoutBuilder : IPlayoutBuilder { private static readonly Random Random = new(); private readonly IArtistRepository _artistRepository; - private readonly IMultiEpisodeShuffleCollectionEnumeratorFactory _multiEpisodeFactory; - private readonly ILocalFileSystem _localFileSystem; private readonly IConfigElementRepository _configElementRepository; + private readonly ILocalFileSystem _localFileSystem; private readonly ILogger _logger; private readonly IMediaCollectionRepository _mediaCollectionRepository; + private readonly IMultiEpisodeShuffleCollectionEnumeratorFactory _multiEpisodeFactory; private readonly ITelevisionRepository _televisionRepository; public PlayoutBuilder( @@ -395,7 +394,7 @@ public class PlayoutBuilder : IPlayoutBuilder // random start points are disabled in some scenarios, so ensure it's enabled and active randomStartPoint = randomStartPoint && activeSchedule.RandomStartPoint; - + var sortedScheduleItems = activeSchedule.Items.OrderBy(i => i.Index).ToList(); CollectionEnumeratorState scheduleItemsEnumeratorState = playout.Anchor?.ScheduleItemsEnumeratorState ?? new CollectionEnumeratorState @@ -469,7 +468,7 @@ public class PlayoutBuilder : IPlayoutBuilder var schedulerFlood = new PlayoutModeSchedulerFlood(_logger); var timeCount = new Dictionary(); - + // loop until we're done filling the desired amount of time while (playoutBuilderState.CurrentTime < playoutFinish && !cancellationToken.IsCancellationRequested) { diff --git a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs index 32a0407c..ed0ff8ac 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs @@ -27,7 +27,7 @@ public abstract class PlayoutModeSchedulerBase : IPlayoutModeScheduler whe DateTimeOffset hardStop) { DateTimeOffset startTime = GetStartTimeAfter(state, scheduleItem); - + // filler should always stop at the hard stop if (hardStop < startTime) { @@ -553,7 +553,7 @@ public abstract class PlayoutModeSchedulerBase : IPlayoutModeScheduler whe // remove post-roll to add after mid-roll/content var postRoll = result.Where(i => i.FillerKind == FillerKind.PostRoll).ToList(); result.RemoveAll(i => i.FillerKind == FillerKind.PostRoll); - + for (var i = 0; i < effectiveChapters.Count; i++) { result.Add(playoutItem.ForChapter(effectiveChapters[i])); diff --git a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs index 98770ef8..59dbde14 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs @@ -205,7 +205,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase p.FillerKind == FillerKind.Fallback); - + var playoutItemsToClear = playoutItems .Filter(pi => pi.FillerKind == FillerKind.None) .ToList(); @@ -217,7 +217,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase _collections; private readonly int _mediaItemCount; private readonly bool _randomStartPoint; - private readonly CancellationToken _cancellationToken; private Random _random; private IList _shuffled; diff --git a/ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs b/ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs index 22e429fd..badc44a6 100644 --- a/ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs @@ -5,9 +5,9 @@ namespace ErsatzTV.Core.Scheduling; public class ShuffledMediaCollectionEnumerator : IMediaCollectionEnumerator { + private readonly CancellationToken _cancellationToken; private readonly int _mediaItemCount; private readonly IList _mediaItems; - private readonly CancellationToken _cancellationToken; private CloneableRandom _random; private IList _shuffled; diff --git a/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs b/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs index 32278acc..4d8fb784 100644 --- a/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs +++ b/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs @@ -218,12 +218,14 @@ public class PipelineBuilderBaseTests command.Should().Be( "-nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -f concat -safe 0 -protocol_whitelist file,http,tcp,https,tcp,tls -probesize 32 -re -stream_loop -1 -i http://localhost:8080/ffmpeg/concat/1 -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -sc_threshold 0 -c copy -map_metadata -1 -metadata service_provider=\"ErsatzTV\" -metadata service_name=\"Some Channel\" -f mpegts -mpegts_flags +initial_discontinuity pipe:1"); } - + [Test] public void Wrap_Segmenter_Test() { var resolution = new FrameSize(1920, 1080); - var concatInputFile = new ConcatInputFile("http://localhost:8080/iptv/channel/1.m3u8?mode=segmenter", resolution); + var concatInputFile = new ConcatInputFile( + "http://localhost:8080/iptv/channel/1.m3u8?mode=segmenter", + resolution); var builder = new SoftwarePipelineBuilder( new DefaultFFmpegCapabilities(), @@ -474,7 +476,7 @@ public class PipelineBuilderBaseTests return command; } - + public class DefaultFFmpegCapabilities : FFmpegCapabilities { public DefaultFFmpegCapabilities() diff --git a/ErsatzTV.FFmpeg/Capabilities/AmfHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/AmfHardwareCapabilities.cs index dfb66e9d..b826ced1 100644 --- a/ErsatzTV.FFmpeg/Capabilities/AmfHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/AmfHardwareCapabilities.cs @@ -9,7 +9,10 @@ public class AmfHardwareCapabilities : IHardwareCapabilities Option videoProfile, Option maybePixelFormat) => FFmpegCapability.Software; - public FFmpegCapability CanEncode(string videoFormat, Option videoProfile, Option maybePixelFormat) + public FFmpegCapability CanEncode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat) { int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); diff --git a/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs index 92d854dd..74a5c122 100644 --- a/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs @@ -4,7 +4,10 @@ namespace ErsatzTV.FFmpeg.Capabilities; public class DefaultHardwareCapabilities : IHardwareCapabilities { - public FFmpegCapability CanDecode(string videoFormat, Option videoProfile, Option maybePixelFormat) + public FFmpegCapability CanDecode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat) { int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); @@ -17,7 +20,10 @@ public class DefaultHardwareCapabilities : IHardwareCapabilities }; } - public FFmpegCapability CanEncode(string videoFormat, Option videoProfile, Option maybePixelFormat) + public FFmpegCapability CanEncode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat) { int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs index 87c2b6b4..aafc8edb 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs @@ -6,8 +6,8 @@ namespace ErsatzTV.FFmpeg.Capabilities; public class FFmpegCapabilities : IFFmpegCapabilities { private readonly IReadOnlySet _ffmpegDecoders; - private readonly IReadOnlySet _ffmpegFilters; private readonly IReadOnlySet _ffmpegEncoders; + private readonly IReadOnlySet _ffmpegFilters; public FFmpegCapabilities( IReadOnlySet ffmpegDecoders, diff --git a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs index 11eb2f03..3193eebf 100644 --- a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs +++ b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs @@ -39,16 +39,14 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory string ffmpegPath, HardwareAccelerationMode hardwareAccelerationMode, Option vaapiDriver, - Option vaapiDevice) - { - return hardwareAccelerationMode switch + Option vaapiDevice) => + hardwareAccelerationMode switch { HardwareAccelerationMode.Nvenc => await GetNvidiaCapabilities(ffmpegPath, ffmpegCapabilities), HardwareAccelerationMode.Vaapi => await GetVaapiCapabilities(vaapiDriver, vaapiDevice), HardwareAccelerationMode.Amf => new AmfHardwareCapabilities(), _ => new DefaultHardwareCapabilities() }; - } public async Task GetNvidiaOutput(string ffmpegPath) { @@ -69,7 +67,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory string output = string.IsNullOrWhiteSpace(result.StandardOutput) ? result.StandardError : result.StandardOutput; - + return output; } @@ -108,7 +106,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory { return cachedDecoders; } - + string[] arguments = { "-hide_banner", $"-{capabilities}" }; BufferedCommandResult result = await Cli.Wrap(ffmpegPath) @@ -165,7 +163,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory _logger.LogWarning("Unable to determine VAAPI capabilities; please install vainfo"); return new DefaultHardwareCapabilities(); } - + profileEntrypoints = new List(); foreach (string line in string.Join("", output).Split("\n")) diff --git a/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs index fa6d7eaf..3065a5dd 100644 --- a/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs @@ -4,6 +4,13 @@ namespace ErsatzTV.FFmpeg.Capabilities; public interface IHardwareCapabilities { - public FFmpegCapability CanDecode(string videoFormat, Option videoProfile, Option maybePixelFormat); - public FFmpegCapability CanEncode(string videoFormat, Option videoProfile, Option maybePixelFormat); + public FFmpegCapability CanDecode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat); + + public FFmpegCapability CanEncode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat); } diff --git a/ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs index 082355cc..f05cac9c 100644 --- a/ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs @@ -4,9 +4,15 @@ namespace ErsatzTV.FFmpeg.Capabilities; public class NoHardwareCapabilities : IHardwareCapabilities { - public FFmpegCapability CanDecode(string videoFormat, Option videoProfile, Option maybePixelFormat) => + public FFmpegCapability CanDecode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat) => FFmpegCapability.Software; - public FFmpegCapability CanEncode(string videoFormat, Option videoProfile, Option maybePixelFormat) => + public FFmpegCapability CanEncode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat) => FFmpegCapability.Software; } diff --git a/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs index 7da29516..b0c48aca 100644 --- a/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs @@ -6,10 +6,10 @@ namespace ErsatzTV.FFmpeg.Capabilities; public class NvidiaHardwareCapabilities : IHardwareCapabilities { private readonly int _architecture; - private readonly List _maxwellGm206 = new() { "GTX 750", "GTX 950", "GTX 960", "GTX 965M" }; - private readonly string _model; private readonly IFFmpegCapabilities _ffmpegCapabilities; private readonly ILogger _logger; + private readonly List _maxwellGm206 = new() { "GTX 750", "GTX 950", "GTX 960", "GTX 965M" }; + private readonly string _model; public NvidiaHardwareCapabilities( int architecture, @@ -23,7 +23,13 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities _logger = logger; } - public FFmpegCapability CanDecode(string videoFormat, Option videoProfile, Option maybePixelFormat) + // this fails with some 1650 cards, so let's try greater than 75 + public bool HevcBFrames => _architecture > 75; + + public FFmpegCapability CanDecode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat) { int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); @@ -37,23 +43,23 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities // some second gen maxwell can decode vp9, otherwise pascal is required VideoFormat.Vp9 => _architecture == 52 && _maxwellGm206.Contains(_model) || _architecture >= 60, - + // no hardware decoding of 10-bit h264 VideoFormat.H264 when bitDepth == 10 => false, - + VideoFormat.Mpeg2Video => true, - + VideoFormat.Vc1 => true, - + // too many issues with odd mpeg4 content, so use software VideoFormat.Mpeg4 => false, - + // ampere is required for av1 decoding VideoFormat.Av1 => _architecture >= 86, // generated images are decoded into software VideoFormat.GeneratedImage => false, - + _ => false }; @@ -75,7 +81,10 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities return FFmpegCapability.Software; } - public FFmpegCapability CanEncode(string videoFormat, Option videoProfile, Option maybePixelFormat) + public FFmpegCapability CanEncode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat) { int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); @@ -96,9 +105,6 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities return isHardware ? FFmpegCapability.Hardware : FFmpegCapability.Software; } - // this fails with some 1650 cards, so let's try greater than 75 - public bool HevcBFrames => _architecture > 75; - private FFmpegCapability CheckHardwareCodec(string codec, Func check) { if (check(codec)) diff --git a/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs index f7e7e8a6..a48ce92d 100644 --- a/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs @@ -6,8 +6,8 @@ namespace ErsatzTV.FFmpeg.Capabilities; public class VaapiHardwareCapabilities : IHardwareCapabilities { - private readonly List _profileEntrypoints; private readonly ILogger _logger; + private readonly List _profileEntrypoints; public VaapiHardwareCapabilities(List profileEntrypoints, ILogger logger) { @@ -15,7 +15,10 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities _logger = logger; } - public FFmpegCapability CanDecode(string videoFormat, Option videoProfile, Option maybePixelFormat) + public FFmpegCapability CanDecode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat) { int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); @@ -23,10 +26,10 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities { // no hardware decoding of 10-bit h264 (VideoFormat.H264, _) when bitDepth == 10 => false, - + // no hardware decoding of h264 baseline profile (VideoFormat.H264, "baseline" or "66") => false, - + (VideoFormat.H264, "main" or "77") => _profileEntrypoints.Contains( new VaapiProfileEntrypoint(VaapiProfile.H264Main, VaapiEntrypoint.Decode)), @@ -70,7 +73,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities (VideoFormat.Hevc, "main 10" or "2") => _profileEntrypoints.Contains( new VaapiProfileEntrypoint(VaapiProfile.HevcMain10, VaapiEntrypoint.Decode)), - + (VideoFormat.Vp9, "profile 0" or "0") => _profileEntrypoints.Contains( new VaapiProfileEntrypoint(VaapiProfile.Vp9Profile0, VaapiEntrypoint.Decode)), @@ -82,11 +85,11 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities (VideoFormat.Vp9, "profile 2" or "2") => _profileEntrypoints.Contains( new VaapiProfileEntrypoint(VaapiProfile.Vp9Profile2, VaapiEntrypoint.Decode)), - + (VideoFormat.Vp9, "profile 3" or "3") => _profileEntrypoints.Contains( new VaapiProfileEntrypoint(VaapiProfile.Vp9Profile3, VaapiEntrypoint.Decode)), - + (VideoFormat.Av1, "main" or "0") => _profileEntrypoints.Contains( new VaapiProfileEntrypoint(VaapiProfile.Av1Profile0, VaapiEntrypoint.Decode)), @@ -106,7 +109,10 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities return isHardware ? FFmpegCapability.Hardware : FFmpegCapability.Software; } - public FFmpegCapability CanEncode(string videoFormat, Option videoProfile, Option maybePixelFormat) + public FFmpegCapability CanEncode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat) { int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); @@ -114,7 +120,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities { // vaapi cannot encode 10-bit h264 VideoFormat.H264 when bitDepth == 10 => false, - + VideoFormat.H264 => _profileEntrypoints.Contains( new VaapiProfileEntrypoint(VaapiProfile.H264Main, VaapiEntrypoint.Encode)) || @@ -138,10 +144,10 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities new VaapiProfileEntrypoint(VaapiProfile.Mpeg2Main, VaapiEntrypoint.Encode)) || _profileEntrypoints.Contains( new VaapiProfileEntrypoint(VaapiProfile.Mpeg2Main, VaapiEntrypoint.EncodeLowPower)), - + _ => false }; - + if (!isHardware) { _logger.LogDebug( diff --git a/ErsatzTV.FFmpeg/CommandGenerator.cs b/ErsatzTV.FFmpeg/CommandGenerator.cs index 0af3a318..c3a1e913 100644 --- a/ErsatzTV.FFmpeg/CommandGenerator.cs +++ b/ErsatzTV.FFmpeg/CommandGenerator.cs @@ -42,7 +42,7 @@ public static class CommandGenerator { bool isVaapiOrQsv = pipelineSteps.Any(s => s is VaapiHardwareAccelerationOption or QsvHardwareAccelerationOption); - + if (!includedPaths.Contains(audioInputFile.Path) || isVaapiOrQsv) { includedPaths.Add(audioInputFile.Path); diff --git a/ErsatzTV.FFmpeg/Decoder/Cuvid/CuvidDecoder.cs b/ErsatzTV.FFmpeg/Decoder/Cuvid/CuvidDecoder.cs index 872e50b8..ba7f6030 100644 --- a/ErsatzTV.FFmpeg/Decoder/Cuvid/CuvidDecoder.cs +++ b/ErsatzTV.FFmpeg/Decoder/Cuvid/CuvidDecoder.cs @@ -2,10 +2,8 @@ namespace ErsatzTV.FFmpeg.Decoder.Cuvid; public abstract class CuvidDecoder : DecoderBase { - protected CuvidDecoder(HardwareAccelerationMode hardwareAccelerationMode) - { + protected CuvidDecoder(HardwareAccelerationMode hardwareAccelerationMode) => HardwareAccelerationMode = hardwareAccelerationMode; - } public HardwareAccelerationMode HardwareAccelerationMode { get; set; } @@ -13,7 +11,7 @@ public abstract class CuvidDecoder : DecoderBase HardwareAccelerationMode == HardwareAccelerationMode.None ? FrameDataLocation.Software : FrameDataLocation.Hardware; - + public override IList InputOptions(InputFile inputFile) { IList result = base.InputOptions(inputFile); diff --git a/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs b/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs index c86a907e..ea4d4f48 100644 --- a/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs +++ b/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs @@ -5,10 +5,8 @@ public class DecoderMpeg2Cuvid : CuvidDecoder private readonly bool _contentIsInterlaced; public DecoderMpeg2Cuvid(HardwareAccelerationMode hardwareAccelerationMode, bool contentIsInterlaced) - : base(hardwareAccelerationMode) - { + : base(hardwareAccelerationMode) => _contentIsInterlaced = contentIsInterlaced; - } public override string Name => "mpeg2_cuvid"; diff --git a/ErsatzTV.FFmpeg/Decoder/DecoderAv1.cs b/ErsatzTV.FFmpeg/Decoder/DecoderAv1.cs index d1b8aa2e..fb9675ce 100644 --- a/ErsatzTV.FFmpeg/Decoder/DecoderAv1.cs +++ b/ErsatzTV.FFmpeg/Decoder/DecoderAv1.cs @@ -5,14 +5,11 @@ public class DecoderAv1 : DecoderBase // ReSharper disable IdentifierTypo private const string Libdav1d = "libdav1d"; private const string Libaomav1 = "libaom-av1"; - + private readonly IReadOnlySet _ffmpegDecoders; - public DecoderAv1(IReadOnlySet ffmpegDecoders) - { - _ffmpegDecoders = ffmpegDecoders; - } - + public DecoderAv1(IReadOnlySet ffmpegDecoders) => _ffmpegDecoders = ffmpegDecoders; + public override string Name { get diff --git a/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderAv1Qsv.cs b/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderAv1Qsv.cs index a892a13b..7119e424 100644 --- a/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderAv1Qsv.cs +++ b/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderAv1Qsv.cs @@ -7,7 +7,7 @@ public class DecoderAv1Qsv : DecoderBase public override string Name => "av1_qsv"; protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware; - + public override FrameState NextState(FrameState currentState) { FrameState nextState = base.NextState(currentState); diff --git a/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderHevcQsv.cs b/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderHevcQsv.cs index 1c850ad6..05f59caa 100644 --- a/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderHevcQsv.cs +++ b/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderHevcQsv.cs @@ -7,7 +7,7 @@ public class DecoderHevcQsv : DecoderBase public override string Name => "hevc_qsv"; protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware; - + public override FrameState NextState(FrameState currentState) { FrameState nextState = base.NextState(currentState); diff --git a/ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs b/ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs index 9e10e4d1..4812d853 100644 --- a/ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs +++ b/ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs @@ -6,6 +6,7 @@ public class EncoderH264Nvenc : EncoderBase { public override string Name => "h264_nvenc"; public override StreamKind Kind => StreamKind.Video; + public override FrameState NextState(FrameState currentState) => currentState with { VideoFormat = VideoFormat.H264 diff --git a/ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs b/ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs index 0d88e74e..e1c5735d 100644 --- a/ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs +++ b/ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs @@ -6,7 +6,7 @@ namespace ErsatzTV.FFmpeg.Encoder.Nvenc; public class EncoderHevcNvenc : EncoderBase { private readonly bool _bFrames; - + public EncoderHevcNvenc(IHardwareCapabilities hardwareCapabilities) { if (hardwareCapabilities is NvidiaHardwareCapabilities nvidia) @@ -17,11 +17,12 @@ public class EncoderHevcNvenc : EncoderBase public override string Name => "hevc_nvenc"; public override StreamKind Kind => StreamKind.Video; + + public override IList OutputOptions => + new[] { "-c:v", "hevc_nvenc", "-b_ref_mode", _bFrames ? "1" : "0" }; + public override FrameState NextState(FrameState currentState) => currentState with { VideoFormat = VideoFormat.Hevc }; - - public override IList OutputOptions => - new[] { "-c:v", "hevc_nvenc", "-b_ref_mode", _bFrames ? "1" : "0" }; } diff --git a/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderH264Vaapi.cs b/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderH264Vaapi.cs index c22a0f04..42f5994f 100644 --- a/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderH264Vaapi.cs +++ b/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderH264Vaapi.cs @@ -6,6 +6,7 @@ public class EncoderH264Vaapi : EncoderBase { public override string Name => "h264_vaapi"; public override StreamKind Kind => StreamKind.Video; + public override FrameState NextState(FrameState currentState) => currentState with { VideoFormat = VideoFormat.H264 diff --git a/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderHevcVaapi.cs b/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderHevcVaapi.cs index 7d1a2c49..131c8853 100644 --- a/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderHevcVaapi.cs +++ b/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderHevcVaapi.cs @@ -6,6 +6,7 @@ public class EncoderHevcVaapi : EncoderBase { public override string Name => "hevc_vaapi"; public override StreamKind Kind => StreamKind.Video; + public override FrameState NextState(FrameState currentState) => currentState with { VideoFormat = VideoFormat.Hevc diff --git a/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderMpeg2Vaapi.cs b/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderMpeg2Vaapi.cs index 8fa25a4e..2389354d 100644 --- a/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderMpeg2Vaapi.cs +++ b/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderMpeg2Vaapi.cs @@ -6,6 +6,7 @@ public class EncoderMpeg2Vaapi : EncoderBase { public override string Name => "mpeg2_vaapi"; public override StreamKind Kind => StreamKind.Video; + public override FrameState NextState(FrameState currentState) => currentState with { VideoFormat = VideoFormat.Mpeg2Video diff --git a/ErsatzTV.FFmpeg/FFmpegState.cs b/ErsatzTV.FFmpeg/FFmpegState.cs index 2a8022b3..1ba5fab6 100644 --- a/ErsatzTV.FFmpeg/FFmpegState.cs +++ b/ErsatzTV.FFmpeg/FFmpegState.cs @@ -22,7 +22,7 @@ public record FFmpegState( Option MaybeQsvExtraHardwareFrames) { public int QsvExtraHardwareFrames => MaybeQsvExtraHardwareFrames.IfNone(64); - + public static FFmpegState Concat(bool saveReport, string channelName) => new( saveReport, diff --git a/ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs b/ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs index 8ef290f0..76325408 100644 --- a/ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs @@ -5,9 +5,9 @@ namespace ErsatzTV.FFmpeg.Filter; public class ColorspaceFilter : BaseFilter { private readonly FrameState _currentState; - private readonly VideoStream _videoStream; private readonly IPixelFormat _desiredPixelFormat; private readonly bool _forceInputOverrides; + private readonly VideoStream _videoStream; public ColorspaceFilter( FrameState currentState, @@ -21,22 +21,6 @@ public class ColorspaceFilter : BaseFilter _forceInputOverrides = forceInputOverrides; } - public override FrameState NextState(FrameState currentState) - { - FrameState nextState = currentState; - - if (!_videoStream.ColorParams.IsUnknown && _desiredPixelFormat.BitDepth is 10 or 8) - { - nextState = nextState with - { - FrameDataLocation = FrameDataLocation.Software, - PixelFormat = Some(_desiredPixelFormat) - }; - } - - return nextState; - } - public override string Filter { get @@ -48,7 +32,7 @@ public class ColorspaceFilter : BaseFilter foreach (IPixelFormat pixelFormat in _currentState.PixelFormat) { string name = pixelFormat.FFmpegName; - + // vaapi is not a target software format if (pixelFormat is PixelFormatVaapi vaapi) { @@ -57,7 +41,7 @@ public class ColorspaceFilter : BaseFilter name = pf.FFmpegName; } } - + if (!string.IsNullOrWhiteSpace(name)) { hwdownload = $"hwdownload,format={name},"; @@ -83,15 +67,24 @@ public class ColorspaceFilter : BaseFilter { string range = string.IsNullOrWhiteSpace(cp.ColorRange) ? "tv" : cp.ColorRange; - string transfer = string.IsNullOrWhiteSpace(cp.ColorTransfer) || string.Equals(cp.ColorTransfer, "reserved", StringComparison.OrdinalIgnoreCase) + string transfer = string.IsNullOrWhiteSpace(cp.ColorTransfer) || string.Equals( + cp.ColorTransfer, + "reserved", + StringComparison.OrdinalIgnoreCase) ? "bt709" : cp.ColorTransfer; - string primaries = string.IsNullOrWhiteSpace(cp.ColorPrimaries) || string.Equals(cp.ColorPrimaries, "reserved", StringComparison.OrdinalIgnoreCase) + string primaries = string.IsNullOrWhiteSpace(cp.ColorPrimaries) || string.Equals( + cp.ColorPrimaries, + "reserved", + StringComparison.OrdinalIgnoreCase) ? "bt709" : cp.ColorPrimaries; - string space = string.IsNullOrWhiteSpace(cp.ColorSpace) || string.Equals(cp.ColorSpace, "reserved", StringComparison.OrdinalIgnoreCase) + string space = string.IsNullOrWhiteSpace(cp.ColorSpace) || string.Equals( + cp.ColorSpace, + "reserved", + StringComparison.OrdinalIgnoreCase) ? "bt709" : cp.ColorSpace; @@ -112,4 +105,20 @@ public class ColorspaceFilter : BaseFilter return colorspace; } } + + public override FrameState NextState(FrameState currentState) + { + FrameState nextState = currentState; + + if (!_videoStream.ColorParams.IsUnknown && _desiredPixelFormat.BitDepth is 10 or 8) + { + nextState = nextState with + { + FrameDataLocation = FrameDataLocation.Software, + PixelFormat = Some(_desiredPixelFormat) + }; + } + + return nextState; + } } diff --git a/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs b/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs index d2c19804..82389ac2 100644 --- a/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs @@ -7,12 +7,10 @@ public class ComplexFilter : IPipelineStep { private readonly Option _maybeAudioInputFile; private readonly Option _maybeSubtitleInputFile; - private readonly PipelineContext _pipelineContext; - private readonly FilterChain _filterChain; private readonly Option _maybeVideoInputFile; private readonly Option _maybeWatermarkInputFile; private readonly List _outputOptions; - private readonly IList _arguments; + private readonly PipelineContext _pipelineContext; public ComplexFilter( Option maybeVideoInputFile, @@ -27,24 +25,25 @@ public class ComplexFilter : IPipelineStep _maybeWatermarkInputFile = maybeWatermarkInputFile; _maybeSubtitleInputFile = maybeSubtitleInputFile; _pipelineContext = pipelineContext; - _filterChain = filterChain; + FilterChain = filterChain; _outputOptions = new List(); - _arguments = Arguments(); + FilterOptions = Arguments(); } + // for testing + public FilterChain FilterChain { get; } + public IList EnvironmentVariables => Array.Empty(); public IList GlobalOptions => Array.Empty(); public IList InputOptions(InputFile inputFile) => Array.Empty(); - public IList FilterOptions => _arguments; + public IList FilterOptions { get; } + public IList OutputOptions => _outputOptions; public FrameState NextState(FrameState currentState) => currentState; - // for testing - public FilterChain FilterChain => _filterChain; - private List Arguments() { var audioLabel = "0:a"; @@ -104,12 +103,12 @@ public class ComplexFilter : IPipelineStep foreach ((int index, _, _) in videoInputFile.Streams) { videoLabel = $"{inputIndex}:{index}"; - if (_filterChain.VideoFilterSteps.Any(f => !string.IsNullOrWhiteSpace(f.Filter))) + if (FilterChain.VideoFilterSteps.Any(f => !string.IsNullOrWhiteSpace(f.Filter))) { videoFilterComplex += $"[{inputIndex}:{index}]"; videoFilterComplex += string.Join( ",", - _filterChain.VideoFilterSteps.Select(f => f.Filter).Filter(s => !string.IsNullOrWhiteSpace(s))); + FilterChain.VideoFilterSteps.Select(f => f.Filter).Filter(s => !string.IsNullOrWhiteSpace(s))); videoLabel = "[v]"; videoFilterComplex += videoLabel; } @@ -122,12 +121,12 @@ public class ComplexFilter : IPipelineStep foreach ((int index, _, _) in watermarkInputFile.Streams) { watermarkLabel = $"{inputIndex}:{index}"; - if (_filterChain.WatermarkFilterSteps.Any(f => !string.IsNullOrWhiteSpace(f.Filter))) + if (FilterChain.WatermarkFilterSteps.Any(f => !string.IsNullOrWhiteSpace(f.Filter))) { watermarkFilterComplex += $"[{inputIndex}:{index}]"; watermarkFilterComplex += string.Join( ",", - _filterChain.WatermarkFilterSteps.Select(f => f.Filter) + FilterChain.WatermarkFilterSteps.Select(f => f.Filter) .Filter(s => !string.IsNullOrWhiteSpace(s))); watermarkLabel = "[wm]"; watermarkFilterComplex += watermarkLabel; @@ -145,12 +144,13 @@ public class ComplexFilter : IPipelineStep foreach ((int index, _, _) in subtitleInputFile.Streams) { subtitleLabel = $"{inputIndex}:{index}"; - if (_filterChain.SubtitleFilterSteps.Any(f => !string.IsNullOrWhiteSpace(f.Filter))) + if (FilterChain.SubtitleFilterSteps.Any(f => !string.IsNullOrWhiteSpace(f.Filter))) { subtitleFilterComplex += $"[{inputIndex}:{index}]"; subtitleFilterComplex += string.Join( ",", - _filterChain.SubtitleFilterSteps.Select(f => f.Filter).Filter(s => !string.IsNullOrWhiteSpace(s))); + FilterChain.SubtitleFilterSteps.Select(f => f.Filter) + .Filter(s => !string.IsNullOrWhiteSpace(s))); subtitleLabel = "[st]"; subtitleFilterComplex += subtitleLabel; } @@ -160,38 +160,38 @@ public class ComplexFilter : IPipelineStep } } } - + // overlay subtitle - if (!string.IsNullOrWhiteSpace(subtitleLabel) && _filterChain.SubtitleOverlayFilterSteps.Any()) + if (!string.IsNullOrWhiteSpace(subtitleLabel) && FilterChain.SubtitleOverlayFilterSteps.Any()) { subtitleOverlayFilterComplex += $"{ProperLabel(videoLabel)}{ProperLabel(subtitleLabel)}"; subtitleOverlayFilterComplex += string.Join( ",", - _filterChain.SubtitleOverlayFilterSteps.Select(f => f.Filter) + FilterChain.SubtitleOverlayFilterSteps.Select(f => f.Filter) .Filter(s => !string.IsNullOrWhiteSpace(s))); videoLabel = "[vst]"; subtitleOverlayFilterComplex += videoLabel; } // overlay watermark - if (!string.IsNullOrWhiteSpace(watermarkLabel) && _filterChain.WatermarkOverlayFilterSteps.Any()) + if (!string.IsNullOrWhiteSpace(watermarkLabel) && FilterChain.WatermarkOverlayFilterSteps.Any()) { watermarkOverlayFilterComplex += $"{ProperLabel(videoLabel)}{ProperLabel(watermarkLabel)}"; watermarkOverlayFilterComplex += string.Join( ",", - _filterChain.WatermarkOverlayFilterSteps.Select(f => f.Filter) + FilterChain.WatermarkOverlayFilterSteps.Select(f => f.Filter) .Filter(s => !string.IsNullOrWhiteSpace(s))); videoLabel = "[vwm]"; watermarkOverlayFilterComplex += videoLabel; } - + // pixel format - if (_filterChain.PixelFormatFilterSteps.Any()) + if (FilterChain.PixelFormatFilterSteps.Any()) { pixelFormatFilterComplex += $"{ProperLabel(videoLabel)}"; pixelFormatFilterComplex += string.Join( ",", - _filterChain.PixelFormatFilterSteps.Select(f => f.Filter) + FilterChain.PixelFormatFilterSteps.Select(f => f.Filter) .Filter(s => !string.IsNullOrWhiteSpace(s))); videoLabel = "[vpf]"; pixelFormatFilterComplex += videoLabel; diff --git a/ErsatzTV.FFmpeg/Filter/Cuda/CudaFormatFilter.cs b/ErsatzTV.FFmpeg/Filter/Cuda/CudaFormatFilter.cs index 97f89024..dbabaf7c 100644 --- a/ErsatzTV.FFmpeg/Filter/Cuda/CudaFormatFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Cuda/CudaFormatFilter.cs @@ -8,7 +8,8 @@ public class CudaFormatFilter : BaseFilter public CudaFormatFilter(IPixelFormat pixelFormat) => _pixelFormat = pixelFormat; - public override FrameState NextState(FrameState currentState) => currentState with { PixelFormat = Some(_pixelFormat) }; - public override string Filter => $"scale_cuda=format={_pixelFormat.FFmpegName}"; + + public override FrameState NextState(FrameState currentState) => + currentState with { PixelFormat = Some(_pixelFormat) }; } diff --git a/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs b/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs index 7c499841..7abc132d 100644 --- a/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs @@ -5,8 +5,8 @@ namespace ErsatzTV.FFmpeg.Filter.Cuda; public class ScaleCudaFilter : BaseFilter { private readonly FrameState _currentState; - private readonly FrameSize _paddedSize; private readonly bool _isAnamorphicEdgeCase; + private readonly FrameSize _paddedSize; private readonly FrameSize _scaledSize; public ScaleCudaFilter( @@ -43,7 +43,7 @@ public class ScaleCudaFilter : BaseFilter } string squareScale = string.Empty; - string targetSize = $"{_paddedSize.Width}:{_paddedSize.Height}"; + var targetSize = $"{_paddedSize.Width}:{_paddedSize.Height}"; string format = string.Empty; foreach (IPixelFormat pixelFormat in _currentState.PixelFormat) { @@ -87,7 +87,7 @@ public class ScaleCudaFilter : BaseFilter FrameDataLocation = FrameDataLocation.Hardware, IsAnamorphic = false // this filter always outputs square pixels }; - + foreach (IPixelFormat pixelFormat in _currentState.PixelFormat) { result = result with { PixelFormat = Some(pixelFormat) }; diff --git a/ErsatzTV.FFmpeg/Filter/Cuda/SubtitleScaleNppFilter.cs b/ErsatzTV.FFmpeg/Filter/Cuda/SubtitleScaleNppFilter.cs index e66423b0..1bb9acc7 100644 --- a/ErsatzTV.FFmpeg/Filter/Cuda/SubtitleScaleNppFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Cuda/SubtitleScaleNppFilter.cs @@ -22,7 +22,7 @@ public class SubtitleScaleNppFilter : BaseFilter string scale = string.Empty; if (_currentState.ScaledSize != _scaledSize) { - string targetSize = $"{_paddedSize.Width}:{_paddedSize.Height}"; + var targetSize = $"{_paddedSize.Width}:{_paddedSize.Height}"; string format = string.Empty; foreach (IPixelFormat pixelFormat in _currentState.PixelFormat) { diff --git a/ErsatzTV.FFmpeg/Filter/FrameRateFilter.cs b/ErsatzTV.FFmpeg/Filter/FrameRateFilter.cs index c33fea86..1ee72aac 100644 --- a/ErsatzTV.FFmpeg/Filter/FrameRateFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/FrameRateFilter.cs @@ -15,7 +15,7 @@ public class FrameRateFilter : BaseFilter { get { - string frameRate = $"framerate=fps={_frameRate}:flags=-scd"; + var frameRate = $"framerate=fps={_frameRate}:flags=-scd"; string pixelFormat = _currentState.PixelFormat.Match(pf => pf.FFmpegName, () => string.Empty); if (_currentState.FrameDataLocation == FrameDataLocation.Hardware) diff --git a/ErsatzTV.FFmpeg/Filter/HardwareDownloadFilter.cs b/ErsatzTV.FFmpeg/Filter/HardwareDownloadFilter.cs index 59091b58..844b4aaf 100644 --- a/ErsatzTV.FFmpeg/Filter/HardwareDownloadFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/HardwareDownloadFilter.cs @@ -25,7 +25,7 @@ public class HardwareDownloadFilter : BaseFilter return $"hwdownload,format=vaapi|{pf.FFmpegName}"; } } - + if (!string.IsNullOrWhiteSpace(pixelFormat.FFmpegName)) { hwdownload = $"hwdownload,format={pixelFormat.FFmpegName}"; diff --git a/ErsatzTV.FFmpeg/Filter/HardwareUploadCudaFilter.cs b/ErsatzTV.FFmpeg/Filter/HardwareUploadCudaFilter.cs index 390dc284..b15a3146 100644 --- a/ErsatzTV.FFmpeg/Filter/HardwareUploadCudaFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/HardwareUploadCudaFilter.cs @@ -4,10 +4,7 @@ public class HardwareUploadCudaFilter : BaseFilter { private readonly FrameState _currentState; - public HardwareUploadCudaFilter(FrameState currentState) - { - _currentState = currentState; - } + public HardwareUploadCudaFilter(FrameState currentState) => _currentState = currentState; public override string Filter => _currentState.FrameDataLocation switch { diff --git a/ErsatzTV.FFmpeg/Filter/HardwareUploadFilter.cs b/ErsatzTV.FFmpeg/Filter/HardwareUploadFilter.cs index c0127523..ddd3f54e 100644 --- a/ErsatzTV.FFmpeg/Filter/HardwareUploadFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/HardwareUploadFilter.cs @@ -4,10 +4,7 @@ public class HardwareUploadFilter : BaseFilter { private readonly FFmpegState _ffmpegState; - public HardwareUploadFilter(FFmpegState ffmpegState) - { - _ffmpegState = ffmpegState; - } + public HardwareUploadFilter(FFmpegState ffmpegState) => _ffmpegState = ffmpegState; public override string Filter => _ffmpegState.EncoderHardwareAccelerationMode switch { diff --git a/ErsatzTV.FFmpeg/Filter/OverlaySubtitleFilter.cs b/ErsatzTV.FFmpeg/Filter/OverlaySubtitleFilter.cs index 682babf9..f9f3aafd 100644 --- a/ErsatzTV.FFmpeg/Filter/OverlaySubtitleFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/OverlaySubtitleFilter.cs @@ -8,6 +8,8 @@ public class OverlaySubtitleFilter : BaseFilter public OverlaySubtitleFilter(IPixelFormat outputPixelFormat) => _outputPixelFormat = outputPixelFormat; - public override string Filter => $"overlay=x=(W-w)/2:y=(H-h)/2:format={(_outputPixelFormat.BitDepth == 10 ? '1' : '0')}"; + public override string Filter => + $"overlay=x=(W-w)/2:y=(H-h)/2:format={(_outputPixelFormat.BitDepth == 10 ? '1' : '0')}"; + public override FrameState NextState(FrameState currentState) => currentState; } diff --git a/ErsatzTV.FFmpeg/Filter/OverlayWatermarkFilter.cs b/ErsatzTV.FFmpeg/Filter/OverlayWatermarkFilter.cs index 07e481d1..df6b4402 100644 --- a/ErsatzTV.FFmpeg/Filter/OverlayWatermarkFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/OverlayWatermarkFilter.cs @@ -6,10 +6,10 @@ namespace ErsatzTV.FFmpeg.Filter; public class OverlayWatermarkFilter : BaseFilter { + private readonly ILogger _logger; + private readonly IPixelFormat _outputPixelFormat; private readonly FrameSize _resolution; private readonly FrameSize _squarePixelFrameSize; - private readonly IPixelFormat _outputPixelFormat; - private readonly ILogger _logger; private readonly WatermarkState _watermarkState; public OverlayWatermarkFilter( @@ -68,7 +68,7 @@ public class OverlayWatermarkFilter : BaseFilter int verticalPadding = _resolution.Height - _squarePixelFrameSize.Height; _logger.LogDebug("Resolution: {Width}x{Height}", _resolution.Width, _resolution.Height); - _logger.LogDebug("Square Pix: {Width}x{Height}", _squarePixelFrameSize.Width, _squarePixelFrameSize.Height); + _logger.LogDebug("Square Pix: {Width}x{Height}", _squarePixelFrameSize.Width, _squarePixelFrameSize.Height); double horizontalMargin = Math.Round( _watermarkState.HorizontalMarginPercent / 100.0 * _squarePixelFrameSize.Width diff --git a/ErsatzTV.FFmpeg/Filter/PadFilter.cs b/ErsatzTV.FFmpeg/Filter/PadFilter.cs index 372e7e4b..3804118a 100644 --- a/ErsatzTV.FFmpeg/Filter/PadFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/PadFilter.cs @@ -17,7 +17,7 @@ public class PadFilter : BaseFilter { get { - string pad = $"pad={_paddedSize.Width}:{_paddedSize.Height}:-1:-1:color=black"; + var pad = $"pad={_paddedSize.Width}:{_paddedSize.Height}:-1:-1:color=black"; if (_currentState.FrameDataLocation == FrameDataLocation.Hardware) { diff --git a/ErsatzTV.FFmpeg/Filter/Qsv/HardwareUploadQsvFilter.cs b/ErsatzTV.FFmpeg/Filter/Qsv/HardwareUploadQsvFilter.cs index 7f5647a9..9efa0e20 100644 --- a/ErsatzTV.FFmpeg/Filter/Qsv/HardwareUploadQsvFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Qsv/HardwareUploadQsvFilter.cs @@ -14,7 +14,7 @@ public class HardwareUploadQsvFilter : BaseFilter public override string Filter => _currentState.FrameDataLocation switch { FrameDataLocation.Hardware => string.Empty, - _ => $"hwupload=extra_hw_frames={_ffmpegState.QsvExtraHardwareFrames}", + _ => $"hwupload=extra_hw_frames={_ffmpegState.QsvExtraHardwareFrames}" }; public override FrameState NextState(FrameState currentState) => diff --git a/ErsatzTV.FFmpeg/Filter/Qsv/OverlaySubtitleQsvFilter.cs b/ErsatzTV.FFmpeg/Filter/Qsv/OverlaySubtitleQsvFilter.cs index a3eed125..ee8f864b 100644 --- a/ErsatzTV.FFmpeg/Filter/Qsv/OverlaySubtitleQsvFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Qsv/OverlaySubtitleQsvFilter.cs @@ -3,7 +3,7 @@ public class OverlaySubtitleQsvFilter : BaseFilter { public override string Filter => - $"overlay_qsv=eof_action=endall:shortest=1:repeatlast=0:x=(W-w)/2:y=(H-h)/2"; + "overlay_qsv=eof_action=endall:shortest=1:repeatlast=0:x=(W-w)/2:y=(H-h)/2"; public override FrameState NextState(FrameState currentState) => currentState; } diff --git a/ErsatzTV.FFmpeg/Filter/Qsv/QsvFormatFilter.cs b/ErsatzTV.FFmpeg/Filter/Qsv/QsvFormatFilter.cs index df61bb6a..8bef6c6f 100644 --- a/ErsatzTV.FFmpeg/Filter/Qsv/QsvFormatFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Qsv/QsvFormatFilter.cs @@ -8,7 +8,8 @@ public class QsvFormatFilter : BaseFilter public QsvFormatFilter(IPixelFormat pixelFormat) => _pixelFormat = pixelFormat; - public override FrameState NextState(FrameState currentState) => currentState with { PixelFormat = Some(_pixelFormat) }; - public override string Filter => $"vpp_qsv=format={_pixelFormat.FFmpegName}"; + + public override FrameState NextState(FrameState currentState) => + currentState with { PixelFormat = Some(_pixelFormat) }; } diff --git a/ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs b/ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs index 7678c91b..9779efdd 100644 --- a/ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs @@ -5,10 +5,10 @@ namespace ErsatzTV.FFmpeg.Filter.Qsv; public class ScaleQsvFilter : BaseFilter { private readonly FrameState _currentState; - private readonly FrameSize _scaledSize; private readonly int _extraHardwareFrames; private readonly bool _isAnamorphicEdgeCase; private readonly string _sampleAspectRatio; + private readonly FrameSize _scaledSize; public ScaleQsvFilter( FrameState currentState, @@ -43,7 +43,7 @@ public class ScaleQsvFilter : BaseFilter else { string squareScale = string.Empty; - string targetSize = $"w={_scaledSize.Width}:h={_scaledSize.Height}"; + var targetSize = $"w={_scaledSize.Width}:h={_scaledSize.Height}"; string format = string.Empty; foreach (IPixelFormat pixelFormat in _currentState.PixelFormat) { diff --git a/ErsatzTV.FFmpeg/Filter/Qsv/SubtitleScaleQsvFilter.cs b/ErsatzTV.FFmpeg/Filter/Qsv/SubtitleScaleQsvFilter.cs index e849155f..f2cff379 100644 --- a/ErsatzTV.FFmpeg/Filter/Qsv/SubtitleScaleQsvFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Qsv/SubtitleScaleQsvFilter.cs @@ -5,9 +5,9 @@ namespace ErsatzTV.FFmpeg.Filter.Qsv; public class SubtitleScaleQsvFilter : BaseFilter { private readonly FrameState _currentState; - private readonly FrameSize _scaledSize; - private readonly FrameSize _paddedSize; private readonly int _extraHardwareFrames; + private readonly FrameSize _paddedSize; + private readonly FrameSize _scaledSize; public SubtitleScaleQsvFilter( FrameState currentState, @@ -28,8 +28,8 @@ public class SubtitleScaleQsvFilter : BaseFilter string scale = string.Empty; if (_currentState.ScaledSize != _scaledSize) { - string targetSize = $"w={_paddedSize.Width}:h={_paddedSize.Height}"; - + var targetSize = $"w={_paddedSize.Width}:h={_paddedSize.Height}"; + // use software scaling scale = $"scale={targetSize}:force_original_aspect_ratio=decrease"; } diff --git a/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs b/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs index 3c3fcebd..64ca2e7e 100644 --- a/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs @@ -5,8 +5,8 @@ namespace ErsatzTV.FFmpeg.Filter; public class ScaleFilter : BaseFilter { private readonly FrameState _currentState; - private readonly FrameSize _paddedSize; private readonly bool _isAnamorphicEdgeCase; + private readonly FrameSize _paddedSize; private readonly FrameSize _scaledSize; public ScaleFilter(FrameState currentState, FrameSize scaledSize, FrameSize paddedSize, bool isAnamorphicEdgeCase) @@ -35,11 +35,13 @@ public class ScaleFilter : BaseFilter string scale; if (_isAnamorphicEdgeCase) { - scale = $"scale=iw:sar*ih,setsar=1,scale={_paddedSize.Width}:{_paddedSize.Height}:flags=fast_bilinear{aspectRatio}"; + scale = + $"scale=iw:sar*ih,setsar=1,scale={_paddedSize.Width}:{_paddedSize.Height}:flags=fast_bilinear{aspectRatio}"; } else if (_currentState.IsAnamorphic) { - scale = $"scale=iw*sar:ih,setsar=1,scale={_paddedSize.Width}:{_paddedSize.Height}:flags=fast_bilinear{aspectRatio}"; + scale = + $"scale=iw*sar:ih,setsar=1,scale={_paddedSize.Width}:{_paddedSize.Height}:flags=fast_bilinear{aspectRatio}"; } else { diff --git a/ErsatzTV.FFmpeg/Filter/ScaleImageFilter.cs b/ErsatzTV.FFmpeg/Filter/ScaleImageFilter.cs index 2428a8c8..9cd2d360 100644 --- a/ErsatzTV.FFmpeg/Filter/ScaleImageFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/ScaleImageFilter.cs @@ -6,7 +6,8 @@ public class ScaleImageFilter : BaseFilter public ScaleImageFilter(FrameSize scaledSize) => _scaledSize = scaledSize; - public override string Filter => $"scale={_scaledSize.Width}:{_scaledSize.Height}:force_original_aspect_ratio=decrease"; + public override string Filter => + $"scale={_scaledSize.Width}:{_scaledSize.Height}:force_original_aspect_ratio=decrease"; public override FrameState NextState(FrameState currentState) => currentState with { diff --git a/ErsatzTV.FFmpeg/Filter/SubtitlePixelFormatFilter.cs b/ErsatzTV.FFmpeg/Filter/SubtitlePixelFormatFilter.cs index 6f849c2d..fa404e0d 100644 --- a/ErsatzTV.FFmpeg/Filter/SubtitlePixelFormatFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/SubtitlePixelFormatFilter.cs @@ -13,13 +13,13 @@ public class SubtitlePixelFormatFilter : BaseFilter public override string Filter => MaybeFormat.Match(f => $"format={f}", () => string.Empty); - public Option MaybeFormat =>_ffmpegState.EncoderHardwareAccelerationMode switch + public Option MaybeFormat => _ffmpegState.EncoderHardwareAccelerationMode switch { HardwareAccelerationMode.Nvenc when _is10BitOutput => "nv12", HardwareAccelerationMode.Nvenc => "yuva420p", HardwareAccelerationMode.Qsv => "yuva420p", _ => None - }; + }; public override FrameState NextState(FrameState currentState) => currentState; } diff --git a/ErsatzTV.FFmpeg/Filter/TonemapFilter.cs b/ErsatzTV.FFmpeg/Filter/TonemapFilter.cs index 2f44399d..74583261 100644 --- a/ErsatzTV.FFmpeg/Filter/TonemapFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/TonemapFilter.cs @@ -13,13 +13,6 @@ public class TonemapFilter : BaseFilter _desiredPixelFormat = desiredPixelFormat; } - public override FrameState NextState(FrameState currentState) => - currentState with - { - PixelFormat = Some(_desiredPixelFormat), - FrameDataLocation = FrameDataLocation.Software - }; - public override string Filter { get @@ -28,7 +21,7 @@ public class TonemapFilter : BaseFilter var tonemap = $"setparams=colorspace=bt2020c,zscale=transfer=linear,tonemap=hable,zscale=transfer=bt709,format={_desiredPixelFormat.FFmpegName}"; - + if (_currentState.FrameDataLocation == FrameDataLocation.Hardware) { if (!string.IsNullOrWhiteSpace(pixelFormat)) @@ -42,4 +35,11 @@ public class TonemapFilter : BaseFilter return tonemap; } } + + public override FrameState NextState(FrameState currentState) => + currentState with + { + PixelFormat = Some(_desiredPixelFormat), + FrameDataLocation = FrameDataLocation.Software + }; } diff --git a/ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs b/ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs index f0ab99c8..afb52526 100644 --- a/ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs @@ -5,8 +5,8 @@ namespace ErsatzTV.FFmpeg.Filter.Vaapi; public class ScaleVaapiFilter : BaseFilter { private readonly FrameState _currentState; - private readonly FrameSize _paddedSize; private readonly bool _isAnamorphicEdgeCase; + private readonly FrameSize _paddedSize; private readonly FrameSize _scaledSize; public ScaleVaapiFilter( @@ -44,7 +44,7 @@ public class ScaleVaapiFilter : BaseFilter } string squareScale = string.Empty; - string targetSize = $"{_paddedSize.Width}:{_paddedSize.Height}"; + var targetSize = $"{_paddedSize.Width}:{_paddedSize.Height}"; string format = string.Empty; foreach (IPixelFormat pixelFormat in _currentState.PixelFormat) { diff --git a/ErsatzTV.FFmpeg/Filter/Vaapi/SubtitleScaleVaapiFilter.cs b/ErsatzTV.FFmpeg/Filter/Vaapi/SubtitleScaleVaapiFilter.cs index 11fb50f3..7812c079 100644 --- a/ErsatzTV.FFmpeg/Filter/Vaapi/SubtitleScaleVaapiFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Vaapi/SubtitleScaleVaapiFilter.cs @@ -4,10 +4,7 @@ public class SubtitleScaleVaapiFilter : BaseFilter { private readonly FrameSize _paddedSize; - public SubtitleScaleVaapiFilter(FrameSize paddedSize) - { - _paddedSize = paddedSize; - } + public SubtitleScaleVaapiFilter(FrameSize paddedSize) => _paddedSize = paddedSize; public override string Filter => $"scale_vaapi={_paddedSize.Width}:{_paddedSize.Height}:force_original_aspect_ratio=decrease"; diff --git a/ErsatzTV.FFmpeg/Filter/Vaapi/VaapiFormatFilter.cs b/ErsatzTV.FFmpeg/Filter/Vaapi/VaapiFormatFilter.cs index 3e234e42..919217f5 100644 --- a/ErsatzTV.FFmpeg/Filter/Vaapi/VaapiFormatFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Vaapi/VaapiFormatFilter.cs @@ -8,7 +8,8 @@ public class VaapiFormatFilter : BaseFilter public VaapiFormatFilter(IPixelFormat pixelFormat) => _pixelFormat = pixelFormat; - public override FrameState NextState(FrameState currentState) => currentState with { PixelFormat = Some(_pixelFormat) }; - public override string Filter => $"scale_vaapi=format={_pixelFormat.FFmpegName}"; + + public override FrameState NextState(FrameState currentState) => + currentState with { PixelFormat = Some(_pixelFormat) }; } diff --git a/ErsatzTV.FFmpeg/Filter/WatermarkPixelFormatFilter.cs b/ErsatzTV.FFmpeg/Filter/WatermarkPixelFormatFilter.cs index 59d6f326..9c884356 100644 --- a/ErsatzTV.FFmpeg/Filter/WatermarkPixelFormatFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/WatermarkPixelFormatFilter.cs @@ -5,8 +5,8 @@ namespace ErsatzTV.FFmpeg.Filter; public class WatermarkPixelFormatFilter : BaseFilter { private readonly FFmpegState _ffmpegState; - private readonly WatermarkState _watermarkState; private readonly bool _is10BitOutput; + private readonly WatermarkState _watermarkState; public WatermarkPixelFormatFilter(FFmpegState ffmpegState, WatermarkState watermarkState, bool is10BitOutput) { diff --git a/ErsatzTV.FFmpeg/Format/PixelFormatUnknown.cs b/ErsatzTV.FFmpeg/Format/PixelFormatUnknown.cs index 19f76774..6e8b0aa0 100644 --- a/ErsatzTV.FFmpeg/Format/PixelFormatUnknown.cs +++ b/ErsatzTV.FFmpeg/Format/PixelFormatUnknown.cs @@ -2,10 +2,7 @@ public class PixelFormatUnknown : IPixelFormat { - public PixelFormatUnknown(int bitDepth = 8) - { - BitDepth = bitDepth; - } + public PixelFormatUnknown(int bitDepth = 8) => BitDepth = bitDepth; public string Name => "unknown"; public string FFmpegName => "unknown"; diff --git a/ErsatzTV.FFmpeg/Format/PixelFormatVaapi.cs b/ErsatzTV.FFmpeg/Format/PixelFormatVaapi.cs index 57d84fe4..c9f7f74a 100644 --- a/ErsatzTV.FFmpeg/Format/PixelFormatVaapi.cs +++ b/ErsatzTV.FFmpeg/Format/PixelFormatVaapi.cs @@ -7,6 +7,6 @@ public class PixelFormatVaapi : IPixelFormat public string Name { get; } public string FFmpegName => "vaapi"; - + public int BitDepth => 8; } diff --git a/ErsatzTV.FFmpeg/InputFile.cs b/ErsatzTV.FFmpeg/InputFile.cs index 29fc9be6..fd10613e 100644 --- a/ErsatzTV.FFmpeg/InputFile.cs +++ b/ErsatzTV.FFmpeg/InputFile.cs @@ -80,5 +80,6 @@ public record SubtitleInputFile(string Path, IList SubtitleStreams, Path, SubtitleStreams) { - public bool IsImageBased = SubtitleStreams.All(s => s.Codec is "hdmv_pgs_subtitle" or "dvd_subtitle" or "dvdsub" or "vobsub" or "pgssub" or "pgs"); + public bool IsImageBased = SubtitleStreams.All( + s => s.Codec is "hdmv_pgs_subtitle" or "dvd_subtitle" or "dvdsub" or "vobsub" or "pgssub" or "pgs"); } diff --git a/ErsatzTV.FFmpeg/MediaStream.cs b/ErsatzTV.FFmpeg/MediaStream.cs index 5811ec09..2aabc650 100644 --- a/ErsatzTV.FFmpeg/MediaStream.cs +++ b/ErsatzTV.FFmpeg/MediaStream.cs @@ -46,7 +46,7 @@ public record VideoStream( double res = FrameSize.Width / (double)FrameSize.Height; return $"{dar}:{res}"; } - else + { string[] split = MaybeSampleAspectRatio.Split(':'); var num = double.Parse(split[0]); @@ -134,7 +134,7 @@ public record VideoStream( double res = FrameSize.Width / (double)FrameSize.Height; return dar / res; } - else + { string[] split = MaybeSampleAspectRatio.Split(':'); var num = double.Parse(split[0]); diff --git a/ErsatzTV.FFmpeg/Option/HardwareAcceleration/QsvHardwareAccelerationOption.cs b/ErsatzTV.FFmpeg/Option/HardwareAcceleration/QsvHardwareAccelerationOption.cs index 6b70ac16..7bd06ebb 100644 --- a/ErsatzTV.FFmpeg/Option/HardwareAcceleration/QsvHardwareAccelerationOption.cs +++ b/ErsatzTV.FFmpeg/Option/HardwareAcceleration/QsvHardwareAccelerationOption.cs @@ -6,11 +6,6 @@ public class QsvHardwareAccelerationOption : GlobalOption { private readonly Option _qsvDevice; - public QsvHardwareAccelerationOption(Option qsvDevice) - { - _qsvDevice = qsvDevice; - } - // TODO: read this from ffmpeg output private readonly List _supportedFFmpegFormats = new() { @@ -18,6 +13,8 @@ public class QsvHardwareAccelerationOption : GlobalOption FFmpegFormat.P010LE }; + public QsvHardwareAccelerationOption(Option qsvDevice) => _qsvDevice = qsvDevice; + public override IList GlobalOptions { get diff --git a/ErsatzTV.FFmpeg/Option/HardwareAcceleration/VaapiHardwareAccelerationOption.cs b/ErsatzTV.FFmpeg/Option/HardwareAcceleration/VaapiHardwareAccelerationOption.cs index a65734d0..3a830df9 100644 --- a/ErsatzTV.FFmpeg/Option/HardwareAcceleration/VaapiHardwareAccelerationOption.cs +++ b/ErsatzTV.FFmpeg/Option/HardwareAcceleration/VaapiHardwareAccelerationOption.cs @@ -4,8 +4,8 @@ namespace ErsatzTV.FFmpeg.Option.HardwareAcceleration; public class VaapiHardwareAccelerationOption : GlobalOption { - private readonly string _vaapiDevice; private readonly FFmpegCapability _decodeCapability; + private readonly string _vaapiDevice; public VaapiHardwareAccelerationOption(string vaapiDevice, FFmpegCapability decodeCapability) { diff --git a/ErsatzTV.FFmpeg/OutputFormat/OutputFormatMpegTs.cs b/ErsatzTV.FFmpeg/OutputFormat/OutputFormatMpegTs.cs index 8b22a30e..dc8eee06 100644 --- a/ErsatzTV.FFmpeg/OutputFormat/OutputFormatMpegTs.cs +++ b/ErsatzTV.FFmpeg/OutputFormat/OutputFormatMpegTs.cs @@ -6,10 +6,7 @@ public class OutputFormatMpegTs : IPipelineStep { private readonly bool _initialDiscontinuity; - public OutputFormatMpegTs(bool initialDiscontinuity = true) - { - _initialDiscontinuity = initialDiscontinuity; - } + public OutputFormatMpegTs(bool initialDiscontinuity = true) => _initialDiscontinuity = initialDiscontinuity; public IList EnvironmentVariables => Array.Empty(); public IList GlobalOptions => Array.Empty(); diff --git a/ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs index dc66856a..c6ae679f 100644 --- a/ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs @@ -69,9 +69,11 @@ public class AmfPipelineBuilder : SoftwarePipelineBuilder }; } - protected override Option GetEncoder(FFmpegState ffmpegState, FrameState currentState, FrameState desiredState) - { - return (ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch + protected override Option GetEncoder( + FFmpegState ffmpegState, + FrameState currentState, + FrameState desiredState) => + (ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch { (HardwareAccelerationMode.Amf, VideoFormat.Hevc) => new EncoderHevcAmf(), @@ -80,8 +82,7 @@ public class AmfPipelineBuilder : SoftwarePipelineBuilder _ => GetSoftwareEncoder(currentState, desiredState) }; - } - + protected override List SetPixelFormat( VideoStream videoStream, Option desiredPixelFormat, @@ -110,7 +111,7 @@ public class AmfPipelineBuilder : SoftwarePipelineBuilder pipelineSteps.Add(new PixelFormatOutputOption(pixelFormat)); } - + return result; } } diff --git a/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs index 093c4e57..fe9a5478 100644 --- a/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs @@ -133,14 +133,15 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder { ScaledSize = videoStream.FrameSize, PaddedSize = videoStream.FrameSize, - - PixelFormat = ffmpegState.DecoderHardwareAccelerationMode == HardwareAccelerationMode.Nvenc && videoStream.BitDepth == 8 + + PixelFormat = ffmpegState.DecoderHardwareAccelerationMode == HardwareAccelerationMode.Nvenc && + videoStream.BitDepth == 8 ? videoStream.PixelFormat.Map(pf => (IPixelFormat)new PixelFormatNv12(pf.Name)) : videoStream.PixelFormat, - - IsAnamorphic = videoStream.IsAnamorphic, + + IsAnamorphic = videoStream.IsAnamorphic }; - + foreach (IDecoder decoder in maybeDecoder) { currentState = decoder.NextState(currentState); @@ -207,7 +208,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder desiredState, fontsFolder, subtitleOverlayFilterSteps); - + currentState = SetWatermark( videoStream, watermarkInputFile, @@ -235,7 +236,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder videoInputFile.FilterSteps.Add(encoder); } } - + List pixelFormatFilterSteps = SetPixelFormat( videoStream, desiredState.PixelFormat, @@ -252,7 +253,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder subtitleOverlayFilterSteps, pixelFormatFilterSteps); } - + private List SetPixelFormat( VideoStream videoStream, Option desiredPixelFormat, @@ -285,16 +286,16 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder if (!videoStream.ColorParams.IsBt709) { // _logger.LogDebug("Adding colorspace filter"); - var colorspace = new ColorspaceFilter(currentState, videoStream, format, false); + var colorspace = new ColorspaceFilter(currentState, videoStream, format); currentState = colorspace.NextState(currentState); result.Add(colorspace); } - + if (ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None) { _logger.LogDebug("Using software encoder"); - + if ((context.HasSubtitleOverlay || context.HasWatermark) && currentState.FrameDataLocation == FrameDataLocation.Hardware) { @@ -306,7 +307,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder result.Add(hardwareDownload); } } - + if (currentState.FrameDataLocation == FrameDataLocation.Hardware && ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None) { @@ -334,8 +335,8 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder bool hardwareEncoder = ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Nvenc; - if (softwareDecoder || (noPipelineFilters && hasColorspace) || - (hardwareDecoder && hardwareEncoder && noPipelineFilters)) + if (softwareDecoder || noPipelineFilters && hasColorspace || + hardwareDecoder && hardwareEncoder && noPipelineFilters) { result.Add(new CudaFormatFilter(format)); } @@ -488,7 +489,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder currentState = cudaDownload.NextState(currentState); videoInputFile.FilterSteps.Add(cudaDownload); } - + // only scale if scaling or padding was used for main video stream if (videoInputFile.FilterSteps.Any(s => s is ScaleFilter or ScaleCudaFilter or PadFilter)) { @@ -524,7 +525,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder currentState = padStep.NextState(currentState); videoInputFile.FilterSteps.Add(padStep); } - + return currentState; } @@ -549,7 +550,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder bool noHardwareFilters = context is { HasWatermark: false, HasSubtitleOverlay: false, ShouldDeinterlace: false }; bool needsToPad = currentState.PaddedSize != desiredState.PaddedSize; - + if (decodedToSoftware && (needsToPad || noHardwareFilters && softwareEncoder)) { scaleStep = new ScaleFilter( @@ -564,15 +565,17 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder currentState with { PixelFormat = !context.Is10BitOutput && (context.HasWatermark || - context.HasSubtitleOverlay || - context.ShouldDeinterlace || - (desiredState.ScaledSize != desiredState.PaddedSize) || - context.HasSubtitleText || - ffmpegState is - { - DecoderHardwareAccelerationMode: HardwareAccelerationMode.Nvenc, - EncoderHardwareAccelerationMode: HardwareAccelerationMode.None - }) + context.HasSubtitleOverlay || + context.ShouldDeinterlace || + desiredState.ScaledSize != desiredState.PaddedSize || + context.HasSubtitleText || + ffmpegState is + { + DecoderHardwareAccelerationMode: + HardwareAccelerationMode.Nvenc, + EncoderHardwareAccelerationMode: + HardwareAccelerationMode.None + }) ? desiredState.PixelFormat.Map(pf => (IPixelFormat)new PixelFormatNv12(pf.Name)) : Option.None }, @@ -590,7 +593,10 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder return currentState; } - private static FrameState SetDeinterlace(VideoInputFile videoInputFile, PipelineContext context, FrameState currentState) + private static FrameState SetDeinterlace( + VideoInputFile videoInputFile, + PipelineContext context, + FrameState currentState) { if (context.ShouldDeinterlace) { diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs index 135261c6..f381ec41 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs @@ -15,15 +15,15 @@ namespace ErsatzTV.FFmpeg.Pipeline; public abstract class PipelineBuilderBase : IPipelineBuilder { + private readonly Option _audioInputFile; private readonly IFFmpegCapabilities _ffmpegCapabilities; + private readonly string _fontsFolder; private readonly HardwareAccelerationMode _hardwareAccelerationMode; + private readonly ILogger _logger; + private readonly string _reportsFolder; + private readonly Option _subtitleInputFile; private readonly Option _videoInputFile; - private readonly Option _audioInputFile; private readonly Option _watermarkInputFile; - private readonly Option _subtitleInputFile; - private readonly string _reportsFolder; - private readonly string _fontsFolder; - private readonly ILogger _logger; protected PipelineBuilderBase( IFFmpegCapabilities ffmpegCapabilities, @@ -80,7 +80,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder new FastStartOutputOption(), new ClosedGopOutputOption() }; - + concatInputFile.AddOption(new ConcatInputFormat()); concatInputFile.AddOption(new RealtimeInputOption()); concatInputFile.AddOption(new InfiniteLoopInputOption(HardwareAccelerationMode.None)); @@ -133,7 +133,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder SetMetadataServiceProvider(ffmpegState, pipelineSteps); SetMetadataServiceName(ffmpegState, pipelineSteps); - pipelineSteps.Add(new OutputFormatMpegTs(initialDiscontinuity: false)); + pipelineSteps.Add(new OutputFormatMpegTs(false)); pipelineSteps.Add(new PipeProtocol()); return new FFmpegPipeline(pipelineSteps); @@ -161,12 +161,12 @@ public abstract class PipelineBuilderBase : IPipelineBuilder VideoStream videoStream = allVideoStreams.Head(); var context = new PipelineContext( - HardwareAccelerationMode: _hardwareAccelerationMode, - HasWatermark: _watermarkInputFile.IsSome, - HasSubtitleOverlay: _subtitleInputFile.Map(s => s is { IsImageBased: true, Copy: false }).IfNone(false), - HasSubtitleText: _subtitleInputFile.Map(s => s is { IsImageBased: false }).IfNone(false), - ShouldDeinterlace: desiredState.Deinterlaced, - Is10BitOutput: desiredState.PixelFormat.Map(pf => pf.BitDepth).IfNone(8) == 10); + _hardwareAccelerationMode, + _watermarkInputFile.IsSome, + _subtitleInputFile.Map(s => s is { IsImageBased: true, Copy: false }).IfNone(false), + _subtitleInputFile.Map(s => s is { IsImageBased: false }).IfNone(false), + desiredState.Deinterlaced, + desiredState.PixelFormat.Map(pf => pf.BitDepth).IfNone(8) == 10); SetThreadCount(ffmpegState, desiredState, pipelineSteps); SetSceneDetect(videoStream, ffmpegState, desiredState, pipelineSteps); @@ -193,7 +193,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder BuildAudioPipeline(audioInputFile, pipelineSteps); } } - + SetDoNotMapMetadata(ffmpegState, pipelineSteps); SetMetadataServiceProvider(ffmpegState, pipelineSteps); SetMetadataServiceName(ffmpegState, pipelineSteps); @@ -216,14 +216,12 @@ public abstract class PipelineBuilderBase : IPipelineBuilder private void LogUnknownDecoder( HardwareAccelerationMode hardwareAccelerationMode, string videoFormat, - string pixelFormat) - { + string pixelFormat) => _logger.LogWarning( "Unable to determine decoder for {AccelMode} - {VideoFormat} - {PixelFormat}; may have playback issues", hardwareAccelerationMode, videoFormat, pixelFormat); - } private Option LogUnknownEncoder(HardwareAccelerationMode hardwareAccelerationMode, string videoFormat) { @@ -303,7 +301,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder { pipelineSteps.Add(step); } - + SetAudioChannels(audioInputFile, pipelineSteps); SetAudioBitrate(audioInputFile, pipelineSteps); SetAudioBufferSize(audioInputFile, pipelineSteps); @@ -401,7 +399,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder SetVideoTrackTimescaleOutput(desiredState, pipelineSteps); SetVideoBitrateOutput(desiredState, pipelineSteps); SetVideoBufferSizeOutput(desiredState, pipelineSteps); - + FilterChain filterChain = SetVideoFilters( videoInputFile, videoStream, @@ -424,7 +422,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder VideoStream videoStream, FFmpegState ffmpegState, PipelineContext context); - + protected Option GetSoftwareDecoder(VideoStream videoStream) { Option maybeDecoder = _ffmpegCapabilities.SoftwareDecoderForVideoFormat(videoStream.Codec); @@ -465,7 +463,10 @@ public abstract class PipelineBuilderBase : IPipelineBuilder string fontsFolder, ICollection pipelineSteps); - private static void SetOutputTsOffset(FFmpegState ffmpegState, FrameState desiredState, ICollection pipelineSteps) + private static void SetOutputTsOffset( + FFmpegState ffmpegState, + FrameState desiredState, + ICollection pipelineSteps) { if (desiredState.VideoFormat == VideoFormat.Copy) { @@ -612,7 +613,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder pipelineSteps.Add(new NoSceneDetectOutputOption(0)); } } - + private void SetFFReport(FFmpegState ffmpegState, ICollection pipelineSteps) { if (ffmpegState.SaveReport) @@ -620,7 +621,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder pipelineSteps.Add(new FFReportVariable(_reportsFolder, None)); } } - + private void SetStreamSeek( FFmpegState ffmpegState, VideoInputFile videoInputFile, @@ -640,9 +641,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder } } } - - private static void SetTimeLimit(FFmpegState ffmpegState, List pipelineSteps) - { + + private static void SetTimeLimit(FFmpegState ffmpegState, List pipelineSteps) => pipelineSteps.AddRange(ffmpegState.Finish.Map(finish => new TimeLimitOutputOption(finish))); - } } diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs index 80cd7411..ecda9c1e 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs @@ -6,9 +6,9 @@ namespace ErsatzTV.FFmpeg.Pipeline; public class PipelineBuilderFactory : IPipelineBuilderFactory { - private readonly IRuntimeInfo _runtimeInfo; private readonly IHardwareCapabilitiesFactory _hardwareCapabilitiesFactory; private readonly ILogger _logger; + private readonly IRuntimeInfo _runtimeInfo; public PipelineBuilderFactory( IRuntimeInfo runtimeInfo, @@ -33,7 +33,7 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory string ffmpegPath) { IFFmpegCapabilities ffmpegCapabilities = await _hardwareCapabilitiesFactory.GetFFmpegCapabilities(ffmpegPath); - + IHardwareCapabilities capabilities = await _hardwareCapabilitiesFactory.GetHardwareCapabilities( ffmpegCapabilities, ffmpegPath, diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineContext.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineContext.cs index 24650741..9a34940b 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineContext.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineContext.cs @@ -7,4 +7,3 @@ public record PipelineContext( bool HasSubtitleText, bool ShouldDeinterlace, bool Is10BitOutput); - diff --git a/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs index 6915a944..57e25559 100644 --- a/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs @@ -63,7 +63,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder bool isHevcOrH264 = videoStream.Codec is VideoFormat.Hevc or VideoFormat.H264; bool is10Bit = videoStream.PixelFormat.Map(pf => pf.BitDepth).IfNone(8) == 10; - + // 10-bit hevc/h264 qsv decoders have issues, so use software if (decodeCapability == FFmpegCapability.Hardware && isHevcOrH264 && is10Bit) { @@ -128,15 +128,15 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder { ScaledSize = videoStream.FrameSize, PaddedSize = videoStream.FrameSize, - + // consider 8-bit hardware frames to be wrapped in nv12 PixelFormat = ffmpegState.DecoderHardwareAccelerationMode == HardwareAccelerationMode.Qsv ? videoStream.PixelFormat.Map(pf => pf.BitDepth == 8 ? new PixelFormatNv12(pf.Name) : pf) : videoStream.PixelFormat, - + IsAnamorphic = videoStream.IsAnamorphic }; - + foreach (IDecoder decoder in maybeDecoder) { currentState = decoder.NextState(currentState); @@ -169,7 +169,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder currentState = hardwareDownload.NextState(currentState); videoInputFile.FilterSteps.Add(hardwareDownload); } - + currentState = SetSubtitle( videoInputFile, subtitleInputFile, @@ -179,7 +179,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder desiredState, fontsFolder, subtitleOverlayFilterSteps); - + currentState = SetWatermark( videoStream, watermarkInputFile, @@ -208,7 +208,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder videoInputFile.FilterSteps.Add(encoder); } } - + List pixelFormatFilterSteps = SetPixelFormat( videoInputFile, videoStream, @@ -226,7 +226,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder subtitleOverlayFilterSteps, pixelFormatFilterSteps); } - + private List SetPixelFormat( VideoInputFile videoInputFile, VideoStream videoStream, @@ -261,8 +261,8 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder { foreach (IPixelFormat currentPixelFormat in currentState.PixelFormat) { - bool requiresConversion = false; - + var requiresConversion = false; + if (currentPixelFormat is PixelFormatNv12 nv) { foreach (IPixelFormat pf in AvailablePixelFormats.ForPixelFormat(nv.Name, null)) @@ -287,7 +287,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder var filter = new QsvFormatFilter(currentPixelFormat); result.Add(filter); currentState = filter.NextState(currentState); - + // if we need to convert 8-bit to 10-bit, do it here if (currentPixelFormat.BitDepth == 8 && context.Is10BitOutput) { @@ -329,7 +329,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder currentState, videoStream, format, - forceInputOverrides: usesVppQsv); + usesVppQsv); currentState = colorspace.NextState(currentState); result.Add(colorspace); @@ -338,7 +338,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder if (ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None) { _logger.LogDebug("Using software encoder"); - + if (currentState.FrameDataLocation == FrameDataLocation.Hardware) { _logger.LogDebug("FrameDataLocation == FrameDataLocation.Hardware"); @@ -484,12 +484,14 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder IPixelFormat pf = desiredPixelFormat; if (desiredPixelFormat is PixelFormatNv12 nv12) { - foreach (IPixelFormat availablePixelFormat in AvailablePixelFormats.ForPixelFormat(nv12.Name, null)) + foreach (IPixelFormat availablePixelFormat in AvailablePixelFormats.ForPixelFormat( + nv12.Name, + null)) { pf = availablePixelFormat; } } - + var subtitlesFilter = new OverlaySubtitleFilter(pf); subtitleOverlayFilterSteps.Add(subtitlesFilter); @@ -519,7 +521,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder currentState = padStep.NextState(currentState); videoInputFile.FilterSteps.Add(padStep); } - + return currentState; } @@ -532,7 +534,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder FrameState currentState) { IPipelineFilterStep scaleStep; - + if (currentState.ScaledSize != desiredState.ScaledSize && ffmpegState is { DecoderHardwareAccelerationMode: HardwareAccelerationMode.None, @@ -551,10 +553,10 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder currentState with { PixelFormat = ffmpegState is - { - DecoderHardwareAccelerationMode: HardwareAccelerationMode.Nvenc, - EncoderHardwareAccelerationMode: HardwareAccelerationMode.None - } + { + DecoderHardwareAccelerationMode: HardwareAccelerationMode.Nvenc, + EncoderHardwareAccelerationMode: HardwareAccelerationMode.None + } ? desiredState.PixelFormat.Map(pf => (IPixelFormat)new PixelFormatNv12(pf.Name)) : Option.None }, diff --git a/ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs index 5cee203a..efe62b94 100644 --- a/ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs @@ -31,10 +31,8 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase subtitleInputFile, reportsFolder, fontsFolder, - logger) - { + logger) => _logger = logger; - } protected override FFmpegState SetAccelState( VideoStream videoStream, @@ -65,10 +63,8 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase protected virtual Option GetEncoder( FFmpegState ffmpegState, FrameState currentState, - FrameState desiredState) - { - return GetSoftwareEncoder(currentState, desiredState); - } + FrameState desiredState) => + GetSoftwareEncoder(currentState, desiredState); protected override FilterChain SetVideoFilters( VideoInputFile videoInputFile, @@ -84,7 +80,7 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase { var watermarkOverlayFilterSteps = new List(); var subtitleOverlayFilterSteps = new List(); - + FrameState currentState = desiredState with { PixelFormat = videoStream.PixelFormat, @@ -131,7 +127,7 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase videoInputFile.FilterSteps.Add(encoder); } } - + // after decoder/encoder, return hls direct if (desiredState.VideoFormat == VideoFormat.Copy) { @@ -283,7 +279,9 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase IPixelFormat pf = desiredPixelFormat; if (desiredPixelFormat is PixelFormatNv12 nv12) { - foreach (IPixelFormat availablePixelFormat in AvailablePixelFormats.ForPixelFormat(nv12.Name, null)) + foreach (IPixelFormat availablePixelFormat in AvailablePixelFormats.ForPixelFormat( + nv12.Name, + null)) { pf = availablePixelFormat; } diff --git a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs index afa147fa..02497775 100644 --- a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs @@ -128,9 +128,9 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder ScaledSize = videoStream.FrameSize, PaddedSize = videoStream.FrameSize, PixelFormat = videoStream.PixelFormat, - IsAnamorphic = videoStream.IsAnamorphic, + IsAnamorphic = videoStream.IsAnamorphic }; - + foreach (IDecoder decoder in maybeDecoder) { currentState = decoder.NextState(currentState); @@ -166,9 +166,9 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder currentState = hardwareUpload.NextState(currentState); videoInputFile.FilterSteps.Add(hardwareUpload); } - else if(currentState.FrameDataLocation == FrameDataLocation.Hardware && - (!context.HasSubtitleOverlay || forceSoftwareOverlay) && - context.HasWatermark) + else if (currentState.FrameDataLocation == FrameDataLocation.Hardware && + (!context.HasSubtitleOverlay || forceSoftwareOverlay) && + context.HasWatermark) { // download for watermark (or forced software subtitle) var hardwareDownload = new HardwareDownloadFilter(currentState); @@ -185,7 +185,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder desiredState, fontsFolder, subtitleOverlayFilterSteps); - + currentState = SetWatermark( videoStream, watermarkInputFile, @@ -213,7 +213,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder videoInputFile.FilterSteps.Add(encoder); } } - + List pixelFormatFilterSteps = SetPixelFormat( videoStream, desiredState.PixelFormat, @@ -228,7 +228,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder subtitleOverlayFilterSteps, pixelFormatFilterSteps); } - + private List SetPixelFormat( VideoStream videoStream, Option desiredPixelFormat, @@ -255,8 +255,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder var colorspace = new ColorspaceFilter( currentState, videoStream, - format, - false); + format); currentState = colorspace.NextState(currentState); result.Add(colorspace); } @@ -264,7 +263,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder if (ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None) { _logger.LogDebug("Using software encoder"); - + if (currentState.FrameDataLocation == FrameDataLocation.Hardware) { _logger.LogDebug("FrameDataLocation == FrameDataLocation.Hardware"); @@ -403,9 +402,9 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder // } // else // { - var downloadFilter = new HardwareDownloadFilter(currentState); - currentState = downloadFilter.NextState(currentState); - videoInputFile.FilterSteps.Add(downloadFilter); + var downloadFilter = new HardwareDownloadFilter(currentState); + currentState = downloadFilter.NextState(currentState); + videoInputFile.FilterSteps.Add(downloadFilter); // } var subtitlesFilter = new SubtitlesFilter(fontsFolder, subtitle); @@ -429,7 +428,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder pf = format; } } - + var subtitlesFilter = new OverlaySubtitleFilter(pf); subtitleOverlayFilterSteps.Add(subtitlesFilter); } @@ -474,7 +473,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder currentState = padStep.NextState(currentState); videoInputFile.FilterSteps.Add(padStep); } - + return currentState; } @@ -487,12 +486,12 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder FrameState currentState) { IPipelineFilterStep scaleStep; - - if ((currentState.ScaledSize != desiredState.ScaledSize && ffmpegState is + + if (currentState.ScaledSize != desiredState.ScaledSize && ffmpegState is { DecoderHardwareAccelerationMode: HardwareAccelerationMode.None, EncoderHardwareAccelerationMode: HardwareAccelerationMode.None - } && context is { HasWatermark: false, HasSubtitleOverlay: false, ShouldDeinterlace: false }) || + } && context is { HasWatermark: false, HasSubtitleOverlay: false, ShouldDeinterlace: false } || ffmpegState.DecoderHardwareAccelerationMode != HardwareAccelerationMode.Vaapi) { scaleStep = new ScaleFilter( @@ -507,14 +506,14 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder currentState with { PixelFormat = //context.HasWatermark || - //context.HasSubtitleOverlay || - // (desiredState.ScaledSize != desiredState.PaddedSize) || - // context.HasSubtitleText || - ffmpegState is - { - DecoderHardwareAccelerationMode: HardwareAccelerationMode.Nvenc, - EncoderHardwareAccelerationMode: HardwareAccelerationMode.None - } + //context.HasSubtitleOverlay || + // (desiredState.ScaledSize != desiredState.PaddedSize) || + // context.HasSubtitleText || + ffmpegState is + { + DecoderHardwareAccelerationMode: HardwareAccelerationMode.Nvenc, + EncoderHardwareAccelerationMode: HardwareAccelerationMode.None + } ? desiredState.PixelFormat.Map(pf => (IPixelFormat)new PixelFormatNv12(pf.Name)) : Option.None }, diff --git a/ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs index 098a28e3..32592c76 100644 --- a/ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs @@ -92,9 +92,11 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder return None; } - protected override Option GetEncoder(FFmpegState ffmpegState, FrameState currentState, FrameState desiredState) - { - return (ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch + protected override Option GetEncoder( + FFmpegState ffmpegState, + FrameState currentState, + FrameState desiredState) => + (ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch { (HardwareAccelerationMode.VideoToolbox, VideoFormat.Hevc) => new EncoderHevcVideoToolbox(desiredState.BitDepth), @@ -103,8 +105,7 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder _ => GetSoftwareEncoder(currentState, desiredState) }; - } - + protected override List SetPixelFormat( VideoStream videoStream, Option desiredPixelFormat, diff --git a/ErsatzTV.Infrastructure/Data/Configurations/Metadata/ArtistMetadataConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/ArtistMetadataConfiguration.cs index 99295e1f..29e612ff 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/Metadata/ArtistMetadataConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/ArtistMetadataConfiguration.cs @@ -37,7 +37,7 @@ public class ArtistMetadataConfiguration : IEntityTypeConfiguration mm.Subtitles) .WithOne() .OnDelete(DeleteBehavior.Cascade); - + builder.HasMany(sm => sm.Styles) .WithOne() .OnDelete(DeleteBehavior.Cascade); diff --git a/ErsatzTV.Infrastructure/Data/Configurations/Metadata/EpisodeMetadataConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/EpisodeMetadataConfiguration.cs index e80d33d2..e31c5f69 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/Metadata/EpisodeMetadataConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/EpisodeMetadataConfiguration.cs @@ -37,7 +37,7 @@ public class EpisodeMetadataConfiguration : IEntityTypeConfiguration mm.Subtitles) .WithOne() .OnDelete(DeleteBehavior.Cascade); - + builder.HasMany(mm => mm.Directors) .WithOne() .OnDelete(DeleteBehavior.Cascade); diff --git a/ErsatzTV.Infrastructure/Data/Configurations/Metadata/MovieMetadataConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/MovieMetadataConfiguration.cs index c4441e20..4157f9f5 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/Metadata/MovieMetadataConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/MovieMetadataConfiguration.cs @@ -37,7 +37,7 @@ public class MovieMetadataConfiguration : IEntityTypeConfiguration mm.Subtitles) .WithOne() .OnDelete(DeleteBehavior.Cascade); - + builder.HasMany(mm => mm.Directors) .WithOne() .OnDelete(DeleteBehavior.Cascade); diff --git a/ErsatzTV.Infrastructure/Data/Configurations/Metadata/OtherVideoMetadataConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/OtherVideoMetadataConfiguration.cs index 0d9dfb59..4a555f9d 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/Metadata/OtherVideoMetadataConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/OtherVideoMetadataConfiguration.cs @@ -37,7 +37,7 @@ public class OtherVideoMetadataConfiguration : IEntityTypeConfiguration mm.Subtitles) .WithOne() .OnDelete(DeleteBehavior.Cascade); - + builder.HasMany(ovm => ovm.Directors) .WithOne() .OnDelete(DeleteBehavior.Cascade); diff --git a/ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs index 003096fe..ddb955b3 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs @@ -93,7 +93,8 @@ public class EmbyMovieRepository : IEmbyMovieRepository new { Id = id }).Map(count => count > 0 ? Some(id) : None); } - return None; } + return None; + } public async Task> FlagFileNotFound(EmbyLibrary library, List movieItemIds) { @@ -374,7 +375,7 @@ public class EmbyMovieRepository : IEmbyMovieRepository fanArt.DateAdded = incomingFanArt.DateAdded; fanArt.DateUpdated = incomingFanArt.DateUpdated; } - + // version MediaVersion version = existing.MediaVersions.Head(); MediaVersion incomingVersion = incoming.MediaVersions.Head(); diff --git a/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs index a350a4a3..706f87f1 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs @@ -705,7 +705,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository { metadata.Genres.Add(genre); } - + // tags foreach (Tag tag in metadata.Tags .Filter(g => incomingMetadata.Tags.All(g2 => g2.Name != g.Name)) @@ -728,7 +728,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository { metadata.Artwork.Remove(artworkToRemove); } - + // version MediaVersion version = existing.MediaVersions.Head(); MediaVersion incomingVersion = incoming.MediaVersions.Head(); diff --git a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs index a138caaa..c95dffcb 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs @@ -95,7 +95,8 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository new { Id = id }).Map(count => count > 0 ? Some(id) : None); } - return None; } + return None; + } public async Task> FlagFileNotFound(JellyfinLibrary library, List movieItemIds) { diff --git a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs index 2801c852..a089d08c 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs @@ -372,7 +372,8 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository new { Id = id }).Map(count => count > 0 ? Some(id) : None); } - return None; } + return None; + } private async Task UpdateShow(TvContext dbContext, JellyfinShow existing, JellyfinShow incoming) { @@ -693,7 +694,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository { metadata.Guids.Add(guid); } - + // genres foreach (Genre genre in metadata.Genres .Filter(g => incomingMetadata.Genres.All(g2 => g2.Name != g.Name)) @@ -708,7 +709,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository { metadata.Genres.Add(genre); } - + // tags foreach (Tag tag in metadata.Tags .Filter(g => incomingMetadata.Tags.All(g2 => g2.Name != g.Name)) @@ -731,7 +732,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository { metadata.Artwork.Remove(artworkToRemove); } - + // version MediaVersion version = existing.MediaVersions.Head(); MediaVersion incomingVersion = incoming.MediaVersions.Head(); diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs index d2b66e48..579c7dd7 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs @@ -13,8 +13,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories; public class MediaCollectionRepository : IMediaCollectionRepository { - private readonly IDbContextFactory _dbContextFactory; private readonly IClient _client; + private readonly IDbContextFactory _dbContextFactory; private readonly ISearchIndex _searchIndex; public MediaCollectionRepository( diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs index b6310d52..e7c75d9c 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs @@ -105,7 +105,6 @@ public class MediaItemRepository : IMediaItemRepository mediaItem.State = MediaItemState.Normal; return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", new { mediaItem.Id }).ToUnit(); } @@ -124,13 +123,21 @@ public class MediaItemRepository : IMediaItemRepository return Unit.Default; } - public static async Task MediaFileAlreadyExists(MediaItem incoming, int libraryPathId, TvContext dbContext, ILogger logger) + public static async Task MediaFileAlreadyExists( + MediaItem incoming, + int libraryPathId, + TvContext dbContext, + ILogger logger) { string path = incoming.GetHeadVersion().MediaFiles.Head().Path; return await MediaFileAlreadyExists(path, libraryPathId, dbContext, logger); } - public static async Task MediaFileAlreadyExists(string path, int libraryPathId, TvContext dbContext, ILogger logger) + public static async Task MediaFileAlreadyExists( + string path, + int libraryPathId, + TvContext dbContext, + ILogger logger) { Option maybeMediaItemId = await dbContext.Connection .QuerySingleOrDefaultAsync( @@ -165,7 +172,7 @@ public class MediaItemRepository : IMediaItemRepository JellyfinLibrary => "Jellyfin Library", _ => "Local Library" }; - + string existingLibraryType = mediaItem.LibraryPath.Library switch { PlexLibrary => "Plex Library", diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs index e7434c9a..6a52f244 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs @@ -5,7 +5,6 @@ using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using Serilog; namespace ErsatzTV.Infrastructure.Data.Repositories; @@ -494,13 +493,56 @@ public class MetadataRepository : IMetadataRepository return await UpdateSubtitles(dbContext, metadata, subtitles); } + public async Task RemoveGenre(Genre genre) + { + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); + return await dbContext.Connection.ExecuteAsync( + "DELETE FROM Genre WHERE Id = @GenreId", + new { GenreId = genre.Id }) + .Map(result => result > 0); + } + + public async Task RemoveTag(Tag tag) + { + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); + return await dbContext.Connection.ExecuteAsync( + "DELETE FROM Tag WHERE Id = @TagId AND ExternalCollectionId = @ExternalCollectionId", + new { TagId = tag.Id, tag.ExternalCollectionId }) + .Map(result => result > 0); + } + + public async Task RemoveStudio(Studio studio) + { + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); + return await dbContext.Connection.ExecuteAsync( + "DELETE FROM Studio WHERE Id = @StudioId", + new { StudioId = studio.Id }) + .Map(result => result > 0); + } + + public async Task RemoveStyle(Style style) + { + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); + return await dbContext.Connection.ExecuteAsync( + "DELETE FROM Style WHERE Id = @StyleId", + new { StyleId = style.Id }) + .Map(result => result > 0); + } + + public async Task RemoveMood(Mood mood) + { + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); + return await dbContext.Connection.ExecuteAsync("DELETE FROM Mood WHERE Id = @MoodId", new { MoodId = mood.Id }) + .Map(result => result > 0); + } + private static async Task UpdateSubtitles(TvContext dbContext, Metadata metadata, List subtitles) { // _logger.LogDebug( // "Updating {Count} subtitles; metadata is {Metadata}", // subtitles.Count, // metadata.GetType().Name); - + int metadataId = metadata.Id; Option maybeMetadata = metadata switch @@ -540,7 +582,7 @@ public class MetadataRepository : IMetadataRepository // toAdd.Count, // toRemove.Count, // toUpdate.Count); - + if (toAdd.Any() || toRemove.Any() || toUpdate.Any()) { // add @@ -584,47 +626,4 @@ public class MetadataRepository : IMetadataRepository // _logger.LogDebug("Subtitle update failure due to missing metadata"); return false; } - - public async Task RemoveGenre(Genre genre) - { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); - return await dbContext.Connection.ExecuteAsync( - "DELETE FROM Genre WHERE Id = @GenreId", - new { GenreId = genre.Id }) - .Map(result => result > 0); - } - - public async Task RemoveTag(Tag tag) - { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); - return await dbContext.Connection.ExecuteAsync( - "DELETE FROM Tag WHERE Id = @TagId AND ExternalCollectionId = @ExternalCollectionId", - new { TagId = tag.Id, tag.ExternalCollectionId }) - .Map(result => result > 0); - } - - public async Task RemoveStudio(Studio studio) - { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); - return await dbContext.Connection.ExecuteAsync( - "DELETE FROM Studio WHERE Id = @StudioId", - new { StudioId = studio.Id }) - .Map(result => result > 0); - } - - public async Task RemoveStyle(Style style) - { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); - return await dbContext.Connection.ExecuteAsync( - "DELETE FROM Style WHERE Id = @StyleId", - new { StyleId = style.Id }) - .Map(result => result > 0); - } - - public async Task RemoveMood(Mood mood) - { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); - return await dbContext.Connection.ExecuteAsync("DELETE FROM Mood WHERE Id = @MoodId", new { MoodId = mood.Id }) - .Map(result => result > 0); - } } diff --git a/ErsatzTV.Infrastructure/Data/Repositories/PlexMovieRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/PlexMovieRepository.cs index 7badd336..02c5a0a3 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/PlexMovieRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/PlexMovieRepository.cs @@ -95,6 +95,7 @@ public class PlexMovieRepository : IPlexMovieRepository return None; } + public async Task> FlagFileNotFound(PlexLibrary library, List plexMovieKeys) { if (plexMovieKeys.Count == 0) @@ -211,7 +212,7 @@ public class PlexMovieRepository : IPlexMovieRepository return BaseError.New(ex.ToString()); } } - + private static async Task UpdateMoviePath(TvContext dbContext, PlexMovie existing, PlexMovie incoming) { // library path is used for search indexing later diff --git a/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs index 1c6badef..d6860ea0 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs @@ -251,7 +251,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository foreach (PlexEpisode plexEpisode in maybeExisting) { var result = new MediaItemScanResult(plexEpisode) { IsAdded = false }; - + // deepScan isn't needed here since we create our own plex etags if (plexEpisode.Etag != item.Etag) { @@ -502,7 +502,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository await dbContext.Connection.ExecuteAsync( @"UPDATE MediaVersion SET Name = @Name, DateAdded = @DateAdded WHERE Id = @Id", new { version.Name, version.DateAdded, version.Id }); - + await dbContext.Connection.ExecuteAsync( @"UPDATE MediaFile SET Path = @Path WHERE Id = @Id", new { file.Path, file.Id }); diff --git a/ErsatzTV.Infrastructure/Data/Repositories/StreamSelectorRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/StreamSelectorRepository.cs index 12001bcc..c41faade 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/StreamSelectorRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/StreamSelectorRepository.cs @@ -9,10 +9,8 @@ public class StreamSelectorRepository : IStreamSelectorRepository { private readonly IDbContextFactory _dbContextFactory; - public StreamSelectorRepository(IDbContextFactory dbContextFactory) - { + public StreamSelectorRepository(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; - } public async Task GetEpisodeData(int episodeId) { @@ -38,7 +36,7 @@ public class StreamSelectorRepository : IStreamSelectorRepository new { Id = (int)episodeData.ShowMetadataId }) .MapT(FormatGuid) .Map(result => result.ToArray()); - + string[] episodeGuids = await dbContext.Connection .QueryAsync( @"SELECT Guid FROM MetadataGuid WHERE EpisodeMetadataId = @Id", diff --git a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs index 80b5568a..8ca1572f 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs @@ -552,7 +552,8 @@ public class TelevisionRepository : ITelevisionRepository await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); return await dbContext.Connection.ExecuteAsync( "INSERT INTO Genre (Name, EpisodeMetadataId) VALUES (@Name, @MetadataId)", - new { genre.Name, MetadataId = metadata.Id }).Map(result => result > 0); } + new { genre.Name, MetadataId = metadata.Id }).Map(result => result > 0); + } public async Task AddTag(Metadata metadata, Tag tag) { diff --git a/ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs b/ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs index 28a42e4b..17a48ca0 100644 --- a/ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs +++ b/ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs @@ -385,7 +385,7 @@ public class EmbyApiClient : IEmbyApiClient return None; } } - + private static List ProjectToModel( IEnumerable embyChapters, TimeSpan duration) @@ -800,8 +800,8 @@ public class EmbyApiClient : IEmbyApiClient return result; } - - private Option ProjectToMediaVersion(EmbyPlaybackInfoResponse response) + + private Option ProjectToMediaVersion(EmbyPlaybackInfoResponse response) { if (response.MediaSources is null || response.MediaSources.Count == 0) { @@ -818,7 +818,7 @@ public class EmbyApiClient : IEmbyApiClient { int width = videoStream.Width ?? 1; int height = videoStream.Height ?? 1; - + var isAnamorphic = false; if (videoStream.IsAnamorphic.HasValue) { @@ -828,7 +828,7 @@ public class EmbyApiClient : IEmbyApiClient { // if width/height != aspect ratio, is anamorphic double resolutionRatio = width / (double)height; - + string[] split = videoStream.AspectRatio.Split(":"); var num = double.Parse(split[0]); var den = double.Parse(split[1]); diff --git a/ErsatzTV.Infrastructure/Emby/IEmbyApi.cs b/ErsatzTV.Infrastructure/Emby/IEmbyApi.cs index 9e37ad90..5f03855f 100644 --- a/ErsatzTV.Infrastructure/Emby/IEmbyApi.cs +++ b/ErsatzTV.Infrastructure/Emby/IEmbyApi.cs @@ -130,8 +130,8 @@ public interface IEmbyApi int startIndex = 0, [Query] int limit = 0); - - + + [Get("/Items/{itemId}/PlaybackInfo")] public Task GetPlaybackInfo( [Header("X-Emby-Token")] diff --git a/ErsatzTV.Infrastructure/FFmpeg/MusicVideoCreditsGenerator.cs b/ErsatzTV.Infrastructure/FFmpeg/MusicVideoCreditsGenerator.cs index a0687c1b..f3b4e452 100644 --- a/ErsatzTV.Infrastructure/FFmpeg/MusicVideoCreditsGenerator.cs +++ b/ErsatzTV.Infrastructure/FFmpeg/MusicVideoCreditsGenerator.cs @@ -10,8 +10,8 @@ namespace ErsatzTV.Infrastructure.FFmpeg; public class MusicVideoCreditsGenerator : IMusicVideoCreditsGenerator { - private readonly ITempFilePool _tempFilePool; private readonly ILogger _logger; + private readonly ITempFilePool _tempFilePool; public MusicVideoCreditsGenerator(ITempFilePool tempFilePool, ILogger logger) { @@ -108,7 +108,7 @@ public class MusicVideoCreditsGenerator : IMusicVideoCreditsGenerator { artist = artistMetadata.Title; } - + string result = await template.RenderAsync( new { diff --git a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs index 3be92c2b..335f45ca 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs @@ -14,8 +14,8 @@ namespace ErsatzTV.Infrastructure.Health.Checks; public class HardwareAccelerationHealthCheck : BaseHealthCheck, IHardwareAccelerationHealthCheck { private readonly IConfigElementRepository _configElementRepository; - private readonly IRuntimeInfo _runtimeInfo; private readonly IDbContextFactory _dbContextFactory; + private readonly IRuntimeInfo _runtimeInfo; public HardwareAccelerationHealthCheck( IDbContextFactory dbContextFactory, diff --git a/ErsatzTV.Infrastructure/Images/ImageCache.cs b/ErsatzTV.Infrastructure/Images/ImageCache.cs index 5242f4bc..228a7f1f 100644 --- a/ErsatzTV.Infrastructure/Images/ImageCache.cs +++ b/ErsatzTV.Infrastructure/Images/ImageCache.cs @@ -73,7 +73,7 @@ public class ImageCache : IImageCache { try { - string filenameKey = $"{path}:{_localFileSystem.GetLastWriteTime(path).ToFileTimeUtc()}"; + var filenameKey = $"{path}:{_localFileSystem.GetLastWriteTime(path).ToFileTimeUtc()}"; byte[] hash = Crypto.ComputeHash(Encoding.UTF8.GetBytes(filenameKey)); string hex = BitConverter.ToString(hash).Replace("-", string.Empty); string subfolder = hex[..2]; diff --git a/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs b/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs index 6e108703..f205c3a5 100644 --- a/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs +++ b/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs @@ -887,7 +887,7 @@ public class JellyfinApiClient : IJellyfinApiClient return result; } - + private Option ProjectToMediaVersion(JellyfinPlaybackInfoResponse response) { if (response.MediaSources is null || response.MediaSources.Count == 0) @@ -897,7 +897,7 @@ public class JellyfinApiClient : IJellyfinApiClient } JellyfinMediaSourceResponse mediaSource = response.MediaSources.Head(); - + // jellyfin includes external streams first, obscuring real stream indexes // from the source file int streamIndexOffset = mediaSource.MediaStreams @@ -905,7 +905,7 @@ public class JellyfinApiClient : IJellyfinApiClient .Map(s => s.Index + 1) .OrderByDescending(i => i) .FirstOrDefault(); - + IList streams = mediaSource.MediaStreams; Option maybeVideoStream = @@ -915,7 +915,7 @@ public class JellyfinApiClient : IJellyfinApiClient { int width = videoStream.Width ?? 1; int height = videoStream.Height ?? 1; - + var isAnamorphic = false; if (videoStream.IsAnamorphic.HasValue) { @@ -925,7 +925,7 @@ public class JellyfinApiClient : IJellyfinApiClient { // if width/height != aspect ratio, is anamorphic double resolutionRatio = width / (double)height; - + string[] split = videoStream.AspectRatio.Split(":"); var num = double.Parse(split[0]); var den = double.Parse(split[1]); diff --git a/ErsatzTV.Infrastructure/Locking/EntityLocker.cs b/ErsatzTV.Infrastructure/Locking/EntityLocker.cs index 9698256e..1e11c5d5 100644 --- a/ErsatzTV.Infrastructure/Locking/EntityLocker.cs +++ b/ErsatzTV.Infrastructure/Locking/EntityLocker.cs @@ -7,9 +7,9 @@ public class EntityLocker : IEntityLocker { private readonly ConcurrentDictionary _lockedLibraries; private readonly ConcurrentDictionary _lockedRemoteMediaSourceTypes; + private bool _embyCollections; private bool _plex; private bool _trakt; - private bool _embyCollections; public EntityLocker() { @@ -129,7 +129,7 @@ public class EntityLocker : IEntityLocker } public bool IsTraktLocked() => _trakt; - + public bool LockEmbyCollections() { if (!_embyCollections) diff --git a/ErsatzTV.Infrastructure/Plex/Models/PlexStreamResponse.cs b/ErsatzTV.Infrastructure/Plex/Models/PlexStreamResponse.cs index e4437e0b..ea1ab688 100644 --- a/ErsatzTV.Infrastructure/Plex/Models/PlexStreamResponse.cs +++ b/ErsatzTV.Infrastructure/Plex/Models/PlexStreamResponse.cs @@ -76,10 +76,10 @@ public class PlexStreamResponse [XmlAttribute("colorTrc")] public string ColorTrc { get; set; } - + [XmlAttribute("colorPrimaries")] public string ColorPrimaries { get; set; } - + [XmlAttribute("displayTitle")] public string DisplayTitle { get; set; } diff --git a/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs b/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs index 7f811e14..030ade66 100644 --- a/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs +++ b/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs @@ -78,7 +78,9 @@ public class PlexServerApiClient : IPlexServerApiClient { return jsonService .GetLibrarySectionContents(library.Key, skip, pageSize, token.AuthToken) - .Map(r => r.MediaContainer.Metadata.Filter(m => m.Media.Count > 0 && m.Media.Any(media => media.Part.Count > 0))) + .Map( + r => r.MediaContainer.Metadata.Filter( + m => m.Media.Count > 0 && m.Media.Any(media => media.Part.Count > 0))) .Map(list => list.Map(metadata => ProjectToMovie(metadata, library.MediaSourceId))); } @@ -218,13 +220,17 @@ public class PlexServerApiClient : IPlexServerApiClient Option maybeResponse = await service .GetVideoMetadata(key, token.AuthToken) .Map(Optional) - .Map(r => r.Filter(m => m.Metadata.Media.Count > 0 && m.Metadata.Media.Any(media => media.Part.Count > 0))); + .Map( + r => r.Filter( + m => m.Metadata.Media.Count > 0 && m.Metadata.Media.Any(media => media.Part.Count > 0))); return maybeResponse.Match( response => { Option maybeVersion = ProjectToMediaVersion(response.Metadata); return maybeVersion.Match>>( - version => Tuple(ProjectToMovieMetadata(version, response.Metadata, library.MediaSourceId), version), + version => Tuple( + ProjectToMovieMetadata(version, response.Metadata, library.MediaSourceId), + version), () => BaseError.New("Unable to locate metadata")); }, () => BaseError.New("Unable to locate metadata")); @@ -247,7 +253,9 @@ public class PlexServerApiClient : IPlexServerApiClient Option maybeResponse = await service .GetVideoMetadata(key, token.AuthToken) .Map(Optional) - .Map(r => r.Filter(m => m.Metadata.Media.Count > 0 && m.Metadata.Media.Any(media => media.Part.Count > 0))); + .Map( + r => r.Filter( + m => m.Metadata.Media.Count > 0 && m.Metadata.Media.Any(media => media.Part.Count > 0))); return maybeResponse.Match( response => { @@ -383,7 +391,7 @@ public class PlexServerApiClient : IPlexServerApiClient PlexMediaResponse media = response.Media .Filter(media => media.Part.Any()) .MaxBy(media => media.Id); - + PlexPartResponse part = media.Part.Head(); DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime; DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime; @@ -408,7 +416,7 @@ public class PlexServerApiClient : IPlexServerApiClient }, Streams = new List() }; - + MovieMetadata metadata = ProjectToMovieMetadata(version, response, mediaSourceId); var movie = new PlexMovie @@ -448,7 +456,7 @@ public class PlexServerApiClient : IPlexServerApiClient Writers = Optional(response.Writer).Flatten().Map(w => new Writer { Name = w.Tag }).ToList(), Subtitles = new List() }; - + var subtitleStreams = version.Streams .Filter(s => s.MediaStreamKind is MediaStreamKind.Subtitle or MediaStreamKind.ExternalSubtitle) .ToList(); @@ -497,7 +505,7 @@ public class PlexServerApiClient : IPlexServerApiClient if (!string.IsNullOrWhiteSpace(response.Thumb)) { - string path = $"plex/{mediaSourceId}{response.Thumb}"; + var path = $"plex/{mediaSourceId}{response.Thumb}"; var artwork = new Artwork { ArtworkKind = ArtworkKind.Poster, @@ -512,7 +520,7 @@ public class PlexServerApiClient : IPlexServerApiClient if (!string.IsNullOrWhiteSpace(response.Art)) { - string path = $"plex/{mediaSourceId}{response.Art}"; + var path = $"plex/{mediaSourceId}{response.Art}"; var artwork = new Artwork { ArtworkKind = ArtworkKind.FanArt, @@ -543,7 +551,8 @@ public class PlexServerApiClient : IPlexServerApiClient var version = new MediaVersion { Duration = TimeSpan.FromMilliseconds(media.Duration), - SampleAspectRatio = string.IsNullOrWhiteSpace(videoStream.PixelAspectRatio) ? "1:1" + SampleAspectRatio = string.IsNullOrWhiteSpace(videoStream.PixelAspectRatio) + ? "1:1" : videoStream.PixelAspectRatio, VideoScanKind = videoStream.ScanType switch { @@ -619,7 +628,8 @@ public class PlexServerApiClient : IPlexServerApiClient // also include external subtitles foreach (PlexStreamResponse subtitleStream in - streams.Filter(s => s.StreamType == 3 && !s.Index.HasValue && !string.IsNullOrWhiteSpace(s.Key))) + streams.Filter( + s => s.StreamType == 3 && !s.Index.HasValue && !string.IsNullOrWhiteSpace(s.Key))) { var stream = new MediaStream { @@ -723,7 +733,7 @@ public class PlexServerApiClient : IPlexServerApiClient if (!string.IsNullOrWhiteSpace(response.Thumb)) { - string path = $"plex/{mediaSourceId}{response.Thumb}"; + var path = $"plex/{mediaSourceId}{response.Thumb}"; var artwork = new Artwork { ArtworkKind = ArtworkKind.Poster, @@ -738,7 +748,7 @@ public class PlexServerApiClient : IPlexServerApiClient if (!string.IsNullOrWhiteSpace(response.Art)) { - string path = $"plex/{mediaSourceId}{response.Art}"; + var path = $"plex/{mediaSourceId}{response.Art}"; var artwork = new Artwork { ArtworkKind = ArtworkKind.FanArt, @@ -790,7 +800,7 @@ public class PlexServerApiClient : IPlexServerApiClient if (!string.IsNullOrWhiteSpace(response.Thumb)) { - string path = $"plex/{mediaSourceId}{response.Thumb}"; + var path = $"plex/{mediaSourceId}{response.Thumb}"; var artwork = new Artwork { ArtworkKind = ArtworkKind.Poster, @@ -805,7 +815,7 @@ public class PlexServerApiClient : IPlexServerApiClient if (!string.IsNullOrWhiteSpace(response.Art)) { - string path = $"plex/{mediaSourceId}{response.Art}"; + var path = $"plex/{mediaSourceId}{response.Art}"; var artwork = new Artwork { ArtworkKind = ArtworkKind.FanArt, @@ -835,7 +845,7 @@ public class PlexServerApiClient : IPlexServerApiClient PlexMediaResponse media = response.Media .Filter(media => media.Part.Any()) .MaxBy(media => media.Id); - + PlexXmlPartResponse part = media.Part.Head(); DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime; DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime; @@ -860,7 +870,7 @@ public class PlexServerApiClient : IPlexServerApiClient // specifically omit stream details Streams = new List() }; - + EpisodeMetadata metadata = ProjectToEpisodeMetadata(version, response, mediaSourceId); var episode = new PlexEpisode @@ -875,7 +885,10 @@ public class PlexServerApiClient : IPlexServerApiClient return episode; } - private EpisodeMetadata ProjectToEpisodeMetadata(MediaVersion version, PlexMetadataResponse response, int mediaSourceId) + private EpisodeMetadata ProjectToEpisodeMetadata( + MediaVersion version, + PlexMetadataResponse response, + int mediaSourceId) { DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime; DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime; @@ -904,7 +917,7 @@ public class PlexServerApiClient : IPlexServerApiClient .ToList(); metadata.Subtitles.AddRange(subtitleStreams.Map(Subtitle.FromMediaStream)); - + if (response is PlexXmlMetadataResponse xml) { metadata.Guids = Optional(xml.Guid).Flatten().Map(g => new MetadataGuid { Guid = g.Id }).ToList(); @@ -937,7 +950,7 @@ public class PlexServerApiClient : IPlexServerApiClient if (!string.IsNullOrWhiteSpace(response.Thumb)) { - string path = $"plex/{mediaSourceId}{response.Thumb}"; + var path = $"plex/{mediaSourceId}{response.Thumb}"; var artwork = new Artwork { ArtworkKind = ArtworkKind.Thumbnail, diff --git a/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs b/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs index b3496412..6853e258 100644 --- a/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs +++ b/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs @@ -8,8 +8,8 @@ namespace ErsatzTV.Infrastructure.Scheduling; public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerator { - private readonly ILogger _logger; private readonly CancellationToken _cancellationToken; + private readonly ILogger _logger; private readonly int _mediaItemCount; private readonly Dictionary> _mediaItemGroups; private readonly List _ungrouped; @@ -30,7 +30,7 @@ public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerato scriptEngine.Load(scriptFile); var numParts = (int)(double)scriptEngine.GetValue("numParts"); - + _mediaItemGroups = new Dictionary>(); for (var i = 1; i <= numParts; i++) { @@ -39,7 +39,7 @@ public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerato _ungrouped = new List(); _mediaItemCount = mediaItems.Count; - + IList validEpisodes = mediaItems .OfType() .Filter(e => e.Season is not null && e.EpisodeMetadata is not null && e.EpisodeMetadata.Count == 1) @@ -49,7 +49,7 @@ public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerato // prep script params int seasonNumber = episode.Season.SeasonNumber; int episodeNumber = episode.EpisodeMetadata[0].EpisodeNumber; - + // call the script function, and if we get a part (group) number back, use it if (scriptEngine.Invoke("partNumberForEpisode", seasonNumber, episodeNumber) is double result) { @@ -182,7 +182,7 @@ public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerato // flatten return GroupedMediaItem.FlattenGroups(copy, _mediaItemCount); } - + private static IList Shuffle(IEnumerable mediaItems, CloneableRandom random) { MediaItem[] copy = mediaItems.ToArray(); diff --git a/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumeratorFactory.cs b/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumeratorFactory.cs index 66a14023..c8aa575f 100644 --- a/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumeratorFactory.cs +++ b/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumeratorFactory.cs @@ -8,8 +8,8 @@ namespace ErsatzTV.Infrastructure.Scheduling; public class MultiEpisodeShuffleCollectionEnumeratorFactory : IMultiEpisodeShuffleCollectionEnumeratorFactory { - private readonly IScriptEngine _scriptEngine; private readonly ILogger _logger; + private readonly IScriptEngine _scriptEngine; public MultiEpisodeShuffleCollectionEnumeratorFactory( IScriptEngine scriptEngine, diff --git a/ErsatzTV.Infrastructure/Scripting/ScriptEngine.cs b/ErsatzTV.Infrastructure/Scripting/ScriptEngine.cs index 3230fd63..8756312e 100644 --- a/ErsatzTV.Infrastructure/Scripting/ScriptEngine.cs +++ b/ErsatzTV.Infrastructure/Scripting/ScriptEngine.cs @@ -8,8 +8,7 @@ public class ScriptEngine : IScriptEngine { private Engine _engine; - public ScriptEngine(ILogger logger) - { + public ScriptEngine(ILogger logger) => _engine = new Engine( options => { @@ -19,7 +18,6 @@ public class ScriptEngine : IScriptEngine options.MaxStatements(1000); }) .SetValue("log", new Action(s => logger.LogDebug("JS Script: {Message}", s))); - } public void Load(string jsScriptPath) { @@ -32,7 +30,7 @@ public class ScriptEngine : IScriptEngine string contents = await File.ReadAllTextAsync(jsScriptPath); _engine.Execute(contents); } - + public object GetValue(string propertyName) => _engine.GetValue(propertyName).ToObject(); public object Invoke(string functionName, params object[] args) => _engine.Invoke(functionName, args).ToObject(); diff --git a/ErsatzTV.Infrastructure/Search/SearchIndex.cs b/ErsatzTV.Infrastructure/Search/SearchIndex.cs index 7846b340..c8bc9391 100644 --- a/ErsatzTV.Infrastructure/Search/SearchIndex.cs +++ b/ErsatzTV.Infrastructure/Search/SearchIndex.cs @@ -80,7 +80,7 @@ public sealed class SearchIndex : ISearchIndex public const string EpisodeType = "episode"; public const string OtherVideoType = "other_video"; public const string SongType = "song"; - + private readonly List _cultureInfos; private readonly ILogger _logger; @@ -462,7 +462,7 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new StringField(TraktListField, item.TraktList.TraktId.ToString(), Field.Store.NO)); } - + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, movie.Id.ToString()), doc); @@ -584,7 +584,7 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new StringField(TraktListField, item.TraktList.TraktId.ToString(), Field.Store.NO)); } - + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, show.Id.ToString()), doc); @@ -608,7 +608,7 @@ public sealed class SearchIndex : ISearchIndex try { - string seasonTitle = $"{showMetadata.Title} - S{season.SeasonNumber}"; + var seasonTitle = $"{showMetadata.Title} - S{season.SeasonNumber}"; string sortTitle = $"{showMetadata.SortTitle}_{season.SeasonNumber:0000}" .ToLowerInvariant(); string titleAndYear = $"{showMetadata.Title}_{showMetadata.Year}_{season.SeasonNumber}" @@ -672,7 +672,7 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new TextField(TagField, tag.Name, Field.Store.NO)); } - + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, season.Id.ToString()), doc); @@ -726,7 +726,7 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new TextField(MoodField, mood.Name, Field.Store.NO)); } - + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, artist.Id.ToString()), doc); @@ -821,7 +821,7 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new TextField(ArtistField, artist, Field.Store.NO)); } - + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, musicVideo.Id.ToString()), doc); @@ -1042,7 +1042,7 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new TextField(WriterField, writer.Name, Field.Store.NO)); } - + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, otherVideo.Id.ToString()), doc); @@ -1108,7 +1108,7 @@ public sealed class SearchIndex : ISearchIndex { doc.Add(new TextField(GenreField, genre.Name, Field.Store.NO)); } - + AddMetadataGuids(metadata, doc); _writer.UpdateDocument(new Term(IdField, song.Id.ToString()), doc); @@ -1139,7 +1139,7 @@ public sealed class SearchIndex : ISearchIndex return query; } - + private void AddStatistics(Document doc, List mediaVersions) { foreach (MediaVersion version in mediaVersions) @@ -1198,7 +1198,8 @@ public sealed class SearchIndex : ISearchIndex EpisodeMetadata em => $"{Title(em)}_{em.Episode.Season.Show.ShowMetadata.Head().Title}_{em.Year}_{em.Episode.Season.SeasonNumber}_{em.EpisodeNumber}_{em.Episode.State}" .ToLowerInvariant(), - OtherVideoMetadata ovm => $"{OtherVideoTitle(ovm).Replace(' ', '_')}_{ovm.Year}_{ovm.OtherVideo.State}".ToLowerInvariant(), + OtherVideoMetadata ovm => $"{OtherVideoTitle(ovm).Replace(' ', '_')}_{ovm.Year}_{ovm.OtherVideo.State}" + .ToLowerInvariant(), SongMetadata sm => $"{Title(sm)}_{sm.Year}_{sm.Song.State}".ToLowerInvariant(), MovieMetadata mm => $"{Title(mm)}_{mm.Year}_{mm.Movie.State}".ToLowerInvariant(), ArtistMetadata am => $"{Title(am)}_{am.Year}_{am.Artist.State}".ToLowerInvariant(), diff --git a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs index 48da5db3..1eaefaac 100644 --- a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs @@ -112,7 +112,7 @@ public class TranscodingTests Watermark.None, Watermark.PermanentOpaqueScaled, // Watermark.PermanentOpaqueActualSize, - Watermark.PermanentTransparentScaled, + Watermark.PermanentTransparentScaled // Watermark.PermanentTransparentActualSize }; @@ -183,7 +183,7 @@ public class TranscodingTests public static FFmpegProfileVideoFormat[] VideoFormats = { FFmpegProfileVideoFormat.H264, - FFmpegProfileVideoFormat.Hevc, + FFmpegProfileVideoFormat.Hevc // FFmpegProfileVideoFormat.Mpeg2Video }; @@ -192,7 +192,7 @@ public class TranscodingTests // HardwareAccelerationKind.None, // HardwareAccelerationKind.Nvenc, HardwareAccelerationKind.Vaapi, - HardwareAccelerationKind.Qsv, + HardwareAccelerationKind.Qsv // HardwareAccelerationKind.VideoToolbox, // HardwareAccelerationKind.Amf }; @@ -214,9 +214,11 @@ public class TranscodingTests [ValueSource(typeof(TestData), nameof(TestData.TestAccelerations))] HardwareAccelerationKind profileAcceleration) { - var localFileSystem = new LocalFileSystem(new Mock().Object, LoggerFactory.CreateLogger()); + var localFileSystem = new LocalFileSystem( + new Mock().Object, + LoggerFactory.CreateLogger()); var tempFilePool = new TempFilePool(); - + var mockImageCache = new Mock(localFileSystem, tempFilePool); // always return the static watermark resource @@ -226,7 +228,7 @@ public class TranscodingTests It.Is(x => x == ArtworkKind.Watermark), It.IsAny>())) .Returns(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "ErsatzTV.png")); - + var oldService = new FFmpegProcessService( new FFmpegPlaybackSettingsCalculator(), new FakeStreamSelector(), @@ -249,7 +251,7 @@ public class TranscodingTests LoggerFactory.CreateLogger()), LoggerFactory.CreateLogger()), LoggerFactory.CreateLogger()); - + var songVideoGenerator = new SongVideoGenerator(tempFilePool, mockImageCache.Object, service); var channel = new Channel(Guid.NewGuid()) @@ -266,7 +268,7 @@ public class TranscodingTests StreamingMode = StreamingMode.TransportStream, SubtitleMode = ChannelSubtitleMode.None }; - + string file = Path.Combine(TestContext.CurrentContext.TestDirectory, Path.Combine("Resources", "song.mp3")); var songVersion = new MediaVersion { @@ -291,7 +293,7 @@ public class TranscodingTests }, MediaVersions = new List { songVersion } }; - + (string videoPath, MediaVersion videoVersion) = await songVideoGenerator.GenerateSongVideo( song, channel, @@ -300,7 +302,7 @@ public class TranscodingTests ExecutableName("ffmpeg"), ExecutableName("ffprobe"), CancellationToken.None); - + var metadataRepository = new Mock(); metadataRepository .Setup(r => r.UpdateStatistics(It.IsAny(), It.IsAny(), It.IsAny())) @@ -324,11 +326,11 @@ public class TranscodingTests new LocalFileSystem(new Mock().Object, LoggerFactory.CreateLogger()), new Mock().Object, LoggerFactory.CreateLogger()); - + await localStatisticsProvider.RefreshStatistics(ExecutableName("ffmpeg"), ExecutableName("ffprobe"), song); - + DateTimeOffset now = DateTimeOffset.Now; - + Command process = await service.ForPlayoutItem( ExecutableName("ffmpeg"), ExecutableName("ffprobe"), @@ -371,40 +373,43 @@ public class TranscodingTests localStatisticsProvider, () => videoVersion); } - + [Test] [Combinatorial] public async Task Transcode( - [ValueSource(typeof(TestData), nameof(TestData.FilesToTest))] - string fileToTest, - [ValueSource(typeof(TestData), nameof(TestData.InputFormats))] - InputFormat inputFormat, - [ValueSource(typeof(TestData), nameof(TestData.Resolutions))] - Resolution profileResolution, - [ValueSource(typeof(TestData), nameof(TestData.BitDepths))] - FFmpegProfileBitDepth profileBitDepth, - [ValueSource(typeof(TestData), nameof(TestData.Paddings))] - Padding padding, - [ValueSource(typeof(TestData), nameof(TestData.VideoScanKinds))] - VideoScanKind videoScanKind, - [ValueSource(typeof(TestData), nameof(TestData.Watermarks))] - Watermark watermark, - [ValueSource(typeof(TestData), nameof(TestData.Subtitles))] - Subtitle subtitle, - [ValueSource(typeof(TestData), nameof(TestData.VideoFormats))] - FFmpegProfileVideoFormat profileVideoFormat, - [ValueSource(typeof(TestData), nameof(TestData.TestAccelerations))] HardwareAccelerationKind profileAcceleration) + [ValueSource(typeof(TestData), nameof(TestData.FilesToTest))] + string fileToTest, + [ValueSource(typeof(TestData), nameof(TestData.InputFormats))] + InputFormat inputFormat, + [ValueSource(typeof(TestData), nameof(TestData.Resolutions))] + Resolution profileResolution, + [ValueSource(typeof(TestData), nameof(TestData.BitDepths))] + FFmpegProfileBitDepth profileBitDepth, + [ValueSource(typeof(TestData), nameof(TestData.Paddings))] + Padding padding, + [ValueSource(typeof(TestData), nameof(TestData.VideoScanKinds))] + VideoScanKind videoScanKind, + [ValueSource(typeof(TestData), nameof(TestData.Watermarks))] + Watermark watermark, + [ValueSource(typeof(TestData), nameof(TestData.Subtitles))] + Subtitle subtitle, + [ValueSource(typeof(TestData), nameof(TestData.VideoFormats))] + FFmpegProfileVideoFormat profileVideoFormat, + [ValueSource(typeof(TestData), nameof(TestData.TestAccelerations))] + HardwareAccelerationKind profileAcceleration) { string file = fileToTest; if (string.IsNullOrWhiteSpace(file)) { // some formats don't support interlaced content (mpeg1video, msmpeg4v2, msmpeg4v3) // others (libx265, any 10-bit) are unlikely to have interlaced content, so don't bother testing - if (inputFormat.Encoder is "mpeg1video" or "msmpeg4v2" or "msmpeg4v3" or "libx265" || inputFormat.PixelFormat.Contains("10")) + if (inputFormat.Encoder is "mpeg1video" or "msmpeg4v2" or "msmpeg4v3" or "libx265" || + inputFormat.PixelFormat.Contains("10")) { if (videoScanKind == VideoScanKind.Interlaced) { - Assert.Inconclusive($"{inputFormat.Encoder}/{inputFormat.PixelFormat} does not support interlaced content"); + Assert.Inconclusive( + $"{inputFormat.Encoder}/{inputFormat.PixelFormat} does not support interlaced content"); return; } } @@ -527,7 +532,8 @@ public class TranscodingTests { var videoFilters = string.Join(",", filterChain.VideoFilterSteps.Map(f => f.Filter)); var pixelFormatFilters = string.Join(",", filterChain.PixelFormatFilterSteps.Map(f => f.Filter)); - if (videoFilters.Contains("nv12") || (pixelFormatFilters.Contains("nv12") && !pixelFormatFilters.EndsWith("format=nv12,format=p010le"))) + if (videoFilters.Contains("nv12") || pixelFormatFilters.Contains("nv12") && + !pixelFormatFilters.EndsWith("format=nv12,format=p010le")) { // Assert.Fail("10-bit shouldn't use NV12!"); } @@ -567,7 +573,7 @@ public class TranscodingTests or OverlaySubtitleCudaFilter or OverlaySubtitleQsvFilter or OverlaySubtitleVaapiFilter); - + hasSubtitleFilters.Should().Be(subtitle != Subtitle.None); bool hasWatermarkFilters = filterChain.WatermarkOverlayFilterSteps.Any( @@ -729,7 +735,7 @@ public class TranscodingTests ? $" -color_primaries {inputFormat.ColorPrimaries}" : string.Empty; - string args = + var args = $"-y -f lavfi -i anoisesrc=color=brown -f lavfi -i testsrc=duration=1:size={resolution}:rate=30 {videoFilter} -c:a aac -c:v {inputFormat.Encoder}{colorRange}{colorSpace}{colorTransfer}{colorPrimaries} -shortest -pix_fmt {inputFormat.PixelFormat} -strict -2 {flags} {file}"; var p1 = new Process { @@ -763,7 +769,7 @@ public class TranscodingTests $"-o {tempFileName} {sourceFile} --field-order 0:{(videoScanKind == VideoScanKind.Interlaced ? '1' : '0')} {subPath}") .WithValidation(CommandResultValidation.None) .ExecuteBufferedAsync(); - + if (p2.ExitCode != 0) { if (File.Exists(sourceFile)) @@ -793,7 +799,7 @@ public class TranscodingTests StartInfo = new ProcessStartInfo { FileName = ExecutableName("mkvpropedit"), - Arguments = $"{tempFileName} --edit track:v1 --set interlaced={(interlaced ? '1' : '0')}", + Arguments = $"{tempFileName} --edit track:v1 --set interlaced={(interlaced ? '1' : '0')}" } }; @@ -943,7 +949,7 @@ public class TranscodingTests Console.WriteLine(process.Arguments); } } - + // additional checks on resulting file await localStatisticsProvider.RefreshStatistics( ExecutableName("ffmpeg"), @@ -963,10 +969,10 @@ public class TranscodingTests }); MediaVersion v = getFinalMediaVersion(); - + // verify de-interlace v.VideoScanKind.Should().NotBe(VideoScanKind.Interlaced); - + // verify resolution v.Height.Should().Be(profileResolution.Height); v.Width.Should().Be(profileResolution.Width); @@ -976,7 +982,7 @@ public class TranscodingTests // verify pixel format videoStream.PixelFormat.Should().Be( profileBitDepth == FFmpegProfileBitDepth.TenBit ? PixelFormat.YUV420P10LE : PixelFormat.YUV420P); - + // verify colors var colorParams = new ColorParams( videoStream.ColorRange, @@ -1023,7 +1029,7 @@ public class TranscodingTests ChannelSubtitleMode subtitleMode) => subtitles.HeadOrNone().AsTask(); } - + private static string ExecutableName(string baseName) => OperatingSystem.IsWindows() ? $"{baseName}.exe" : baseName; } diff --git a/ErsatzTV.Scanner.Tests/Core/Metadata/MovieFolderScannerTests.cs b/ErsatzTV.Scanner.Tests/Core/Metadata/MovieFolderScannerTests.cs index f2797505..6bcb2fde 100644 --- a/ErsatzTV.Scanner.Tests/Core/Metadata/MovieFolderScannerTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/Metadata/MovieFolderScannerTests.cs @@ -4,13 +4,12 @@ using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.FFmpeg; using ErsatzTV.Core.Interfaces.Images; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Metadata; using ErsatzTV.Scanner.Core.Interfaces.FFmpeg; using ErsatzTV.Scanner.Core.Interfaces.Metadata; -using ErsatzTV.Scanner.Tests.Core.Fakes; using ErsatzTV.Scanner.Core.Metadata; +using ErsatzTV.Scanner.Tests.Core.Fakes; using FluentAssertions; using MediatR; using Microsoft.Extensions.Logging; @@ -121,7 +120,7 @@ public class MovieFolderScannerTests It.Is(i => i.MediaVersions.Head().MediaFiles.Head().Path == moviePath)), Times.Once); } - + [Test] public async Task NewMovie_Statistics_And_FallbackMetadata_MixedCase( [ValueSource(typeof(LocalFolderScanner), nameof(LocalFolderScanner.VideoFileExtensions))] @@ -130,7 +129,7 @@ public class MovieFolderScannerTests char[] mixedCaseExtension = videoExtension.ToLowerInvariant().ToArray(); mixedCaseExtension[2] = char.ToUpper(mixedCaseExtension[2]); videoExtension = new string(mixedCaseExtension); - + string moviePath = Path.Combine( FakeRoot, Path.Combine("Movie (2020)", $"Movie (2020){videoExtension}")); diff --git a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/EpisodeNfoReaderTests.cs b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/EpisodeNfoReaderTests.cs index 3545c504..55a24517 100644 --- a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/EpisodeNfoReaderTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/EpisodeNfoReaderTests.cs @@ -289,7 +289,7 @@ public class EpisodeNfoReaderTests .Should().Be(1); } } - + [Test] public async Task Genres() { @@ -315,7 +315,7 @@ public class EpisodeNfoReaderTests list.Count(nfo => nfo.Genres is ["Genre 2", "Genre 3"]).Should().Be(1); } } - + [Test] public async Task Tags() { diff --git a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MusicVideoNfoReaderTests.cs b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MusicVideoNfoReaderTests.cs index 55b63998..2bcfc9b5 100644 --- a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MusicVideoNfoReaderTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MusicVideoNfoReaderTests.cs @@ -182,7 +182,7 @@ Le groupe a également enregistré une version espagnole de ce titre, La reina d nfo.Studios.Should().BeEquivalentTo(new List { "Test Studio" }); } } - + [Test] public async Task MetadataNfo_With_Directors_Should_Return_Nfo() { diff --git a/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs b/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs index d4b35b37..ea6dff5e 100644 --- a/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs +++ b/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs @@ -8,10 +8,10 @@ namespace ErsatzTV.Scanner.Application.Emby; public class SynchronizeEmbyCollectionsHandler : IRequestHandler> { + private readonly IConfigElementRepository _configElementRepository; private readonly IEmbySecretStore _embySecretStore; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IEmbyCollectionScanner _scanner; - private readonly IConfigElementRepository _configElementRepository; public SynchronizeEmbyCollectionsHandler( IMediaSourceRepository mediaSourceRepository, @@ -84,7 +84,7 @@ public class SynchronizeEmbyCollectionsHandler : IRequestHandler 0 && nextScan < DateTimeOffset.Now)) + if (parameters.ForceScan || parameters.LibraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now) { Either result = await _scanner.ScanCollections( parameters.ConnectionParameters.ActiveConnection.Address, diff --git a/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyLibraryByIdHandler.cs b/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyLibraryByIdHandler.cs index 5870e106..a5b7d249 100644 --- a/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyLibraryByIdHandler.cs +++ b/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyLibraryByIdHandler.cs @@ -17,9 +17,9 @@ public class SynchronizeEmbyLibraryByIdHandler : IRequestHandler _logger; + private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMediator _mediator; - private readonly IMediaSourceRepository _mediaSourceRepository; public SynchronizeEmbyLibraryByIdHandler( IMediator mediator, @@ -56,7 +56,7 @@ public class SynchronizeEmbyLibraryByIdHandler : IRequestHandler 0 && nextScan < DateTimeOffset.Now)) + if (parameters.ForceScan || parameters.LibraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now) { Either result = parameters.Library.MediaKind switch { @@ -82,7 +82,7 @@ public class SynchronizeEmbyLibraryByIdHandler : IRequestHandler> { private readonly IConfigElementRepository _configElementRepository; + + private readonly IJellyfinApiClient _jellyfinApiClient; private readonly IJellyfinMovieLibraryScanner _jellyfinMovieLibraryScanner; private readonly IJellyfinSecretStore _jellyfinSecretStore; private readonly IJellyfinTelevisionLibraryScanner _jellyfinTelevisionLibraryScanner; private readonly ILibraryRepository _libraryRepository; private readonly ILogger _logger; - - private readonly IJellyfinApiClient _jellyfinApiClient; - private readonly IMediator _mediator; private readonly IMediaSourceRepository _mediaSourceRepository; + private readonly IMediator _mediator; public SynchronizeJellyfinLibraryByIdHandler( IJellyfinApiClient jellyfinApiClient, @@ -60,7 +60,7 @@ public class { var lastScan = new DateTimeOffset(parameters.Library.LastScan ?? SystemTime.MinValueUtc, TimeSpan.Zero); DateTimeOffset nextScan = lastScan + TimeSpan.FromHours(parameters.LibraryRefreshInterval); - if (parameters.ForceScan || (parameters.LibraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now)) + if (parameters.ForceScan || parameters.LibraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now) { // need the jellyfin admin user id for now Either syncAdminResult = await _mediator.Send( diff --git a/ErsatzTV.Scanner/Application/MediaSources/Commands/ScanLocalLibraryHandler.cs b/ErsatzTV.Scanner/Application/MediaSources/Commands/ScanLocalLibraryHandler.cs index 26ba2612..58992212 100644 --- a/ErsatzTV.Scanner/Application/MediaSources/Commands/ScanLocalLibraryHandler.cs +++ b/ErsatzTV.Scanner/Application/MediaSources/Commands/ScanLocalLibraryHandler.cs @@ -67,7 +67,7 @@ public class ScanLocalLibraryHandler : IRequestHandler 0 && nextScan < DateTimeOffset.Now)) + if (forceScan || libraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now) { scanned = true; diff --git a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs index cf59a825..d6d30860 100644 --- a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs +++ b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs @@ -13,8 +13,8 @@ public class SynchronizePlexLibraryByIdHandler : IRequestHandler _logger; - private readonly IMediator _mediator; private readonly IMediaSourceRepository _mediaSourceRepository; + private readonly IMediator _mediator; private readonly IPlexMovieLibraryScanner _plexMovieLibraryScanner; private readonly IPlexSecretStore _plexSecretStore; private readonly IPlexTelevisionLibraryScanner _plexTelevisionLibraryScanner; @@ -55,7 +55,7 @@ public class SynchronizePlexLibraryByIdHandler : IRequestHandler 0 && nextScan < DateTimeOffset.Now)) + if (parameters.ForceScan || parameters.LibraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now) { Either result = parameters.Library.MediaKind switch { @@ -93,7 +93,7 @@ public class SynchronizePlexLibraryByIdHandler : IRequestHandler _logger; + private readonly IMediator _mediator; public EmbyCollectionScanner( IMediator mediator, @@ -32,7 +32,7 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner { // need to call get libraries to find library that contains collections (box sets) await _embyApiClient.GetLibraries(address, apiKey); - + var incomingItemIds = new List(); // get all collections from db (item id, etag) diff --git a/ErsatzTV.Scanner/Core/Emby/EmbyMovieLibraryScanner.cs b/ErsatzTV.Scanner/Core/Emby/EmbyMovieLibraryScanner.cs index 9998f2d1..5d070d75 100644 --- a/ErsatzTV.Scanner/Core/Emby/EmbyMovieLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Emby/EmbyMovieLibraryScanner.cs @@ -17,9 +17,9 @@ public class EmbyMovieLibraryScanner : { private readonly IEmbyApiClient _embyApiClient; private readonly IEmbyMovieRepository _embyMovieRepository; + private readonly ILogger _logger; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IEmbyPathReplacementService _pathReplacementService; - private readonly ILogger _logger; public EmbyMovieLibraryScanner( IEmbyApiClient embyApiClient, @@ -42,7 +42,7 @@ public class EmbyMovieLibraryScanner : _pathReplacementService = pathReplacementService; _logger = logger; } - + protected override bool ServerSupportsRemoteStreaming => true; public async Task> ScanLibrary( @@ -105,7 +105,7 @@ public class EmbyMovieLibraryScanner : EmbyLibrary library, MediaItemScanResult result, EmbyMovie incoming) => Task.FromResult(Option>.None); - + protected override async Task> GetMediaServerStatistics( EmbyConnectionParameters connectionParameters, EmbyLibrary library, diff --git a/ErsatzTV.Scanner/Core/Emby/EmbyTelevisionLibraryScanner.cs b/ErsatzTV.Scanner/Core/Emby/EmbyTelevisionLibraryScanner.cs index efc73bd9..993fec15 100644 --- a/ErsatzTV.Scanner/Core/Emby/EmbyTelevisionLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Emby/EmbyTelevisionLibraryScanner.cs @@ -16,9 +16,9 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner< EmbyItemEtag>, IEmbyTelevisionLibraryScanner { private readonly IEmbyApiClient _embyApiClient; + private readonly ILogger _logger; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IEmbyPathReplacementService _pathReplacementService; - private readonly ILogger _logger; private readonly IEmbyTelevisionRepository _televisionRepository; public EmbyTelevisionLibraryScanner( @@ -165,7 +165,7 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner< EmbyLibrary library, MediaItemScanResult result, EmbyEpisode incoming) => Task.FromResult(Option>.None); - + protected override async Task> GetMediaServerStatistics( EmbyConnectionParameters connectionParameters, EmbyLibrary library, @@ -196,7 +196,6 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner< } - protected override Task>> UpdateMetadata( MediaItemScanResult result, ShowMetadata fullMetadata) => diff --git a/ErsatzTV.Scanner/Core/FFmpeg/FFmpegPngService.cs b/ErsatzTV.Scanner/Core/FFmpeg/FFmpegPngService.cs index ce01d072..178a44df 100644 --- a/ErsatzTV.Scanner/Core/FFmpeg/FFmpegPngService.cs +++ b/ErsatzTV.Scanner/Core/FFmpeg/FFmpegPngService.cs @@ -7,7 +7,8 @@ public class FFmpegPngService : IFFmpegPngService { public Command ConvertToPng(string ffmpegPath, string inputFile, string outputFile) { - string[] arguments = { + string[] arguments = + { "-threads", "1", "-nostdin", "-hide_banner", "-loglevel", "error", "-nostats", @@ -23,7 +24,8 @@ public class FFmpegPngService : IFFmpegPngService public Command ExtractAttachedPicAsPng(string ffmpegPath, string inputFile, int streamIndex, string outputFile) { - string[] arguments = { + string[] arguments = + { "-threads", "1", "-nostdin", "-hide_banner", "-loglevel", "error", "-nostats", diff --git a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs index f922045a..dcbbeee5 100644 --- a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs +++ b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs @@ -10,9 +10,9 @@ namespace ErsatzTV.Scanner.Core.Jellyfin; public class JellyfinCollectionScanner : IJellyfinCollectionScanner { private readonly IJellyfinApiClient _jellyfinApiClient; - private readonly IMediator _mediator; private readonly IJellyfinCollectionRepository _jellyfinCollectionRepository; private readonly ILogger _logger; + private readonly IMediator _mediator; public JellyfinCollectionScanner( IMediator mediator, diff --git a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinMovieLibraryScanner.cs b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinMovieLibraryScanner.cs index 06b07491..9f935561 100644 --- a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinMovieLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinMovieLibraryScanner.cs @@ -17,8 +17,8 @@ public class JellyfinMovieLibraryScanner : { private readonly IJellyfinApiClient _jellyfinApiClient; private readonly IJellyfinMovieRepository _jellyfinMovieRepository; - private readonly IMediaSourceRepository _mediaSourceRepository; private readonly ILogger _logger; + private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IJellyfinPathReplacementService _pathReplacementService; public JellyfinMovieLibraryScanner( @@ -108,7 +108,7 @@ public class JellyfinMovieLibraryScanner : JellyfinLibrary library, MediaItemScanResult result, JellyfinMovie incoming) => Task.FromResult(Option>.None); - + protected override async Task> GetMediaServerStatistics( JellyfinConnectionParameters connectionParameters, JellyfinLibrary library, diff --git a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs index 9a3097df..62e666be 100644 --- a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs @@ -17,9 +17,9 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan JellyfinItemEtag>, IJellyfinTelevisionLibraryScanner { private readonly IJellyfinApiClient _jellyfinApiClient; + private readonly ILogger _logger; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IJellyfinPathReplacementService _pathReplacementService; - private readonly ILogger _logger; private readonly IJellyfinTelevisionRepository _televisionRepository; public JellyfinTelevisionLibraryScanner( @@ -191,7 +191,7 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan { _logger.LogWarning("Failed to get episode statistics from Jellyfin: {Error}", error.ToString()); } - + // chapters are pulled with metadata, not with statistics, but we need to save them here foreach (MediaVersion version in maybeVersion.RightToSeq()) { diff --git a/ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs b/ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs index 9092bc12..4cc4321a 100644 --- a/ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs +++ b/ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs @@ -29,9 +29,9 @@ public class LocalMetadataProvider : ILocalMetadataProvider private readonly IMusicVideoRepository _musicVideoRepository; private readonly IOtherVideoNfoReader _otherVideoNfoReader; private readonly IOtherVideoRepository _otherVideoRepository; + private readonly IShowNfoReader _showNfoReader; private readonly ISongRepository _songRepository; private readonly ITelevisionRepository _televisionRepository; - private readonly IShowNfoReader _showNfoReader; public LocalMetadataProvider( IMetadataRepository metadataRepository, @@ -833,7 +833,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider updated = true; } } - + foreach (Director director in existing.Directors .Filter(d => metadata.Directors.All(d2 => d2.Name != d.Name)).ToList()) { @@ -1274,7 +1274,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider releaseDate = premiered; } - + return new OtherVideoMetadata { MetadataKind = MetadataKind.Sidecar, diff --git a/ErsatzTV.Scanner/Core/Metadata/LocalStatisticsProvider.cs b/ErsatzTV.Scanner/Core/Metadata/LocalStatisticsProvider.cs index dea86dda..a8f0c49e 100644 --- a/ErsatzTV.Scanner/Core/Metadata/LocalStatisticsProvider.cs +++ b/ErsatzTV.Scanner/Core/Metadata/LocalStatisticsProvider.cs @@ -335,7 +335,7 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider internal MediaVersion ProjectToMediaVersion(string path, FFprobe probeOutput) => Optional(probeOutput) - .Filter(json => json is { format: { }, streams: { } }) + .Filter(json => json is { format: not null, streams: not null }) .ToValidation("Unable to parse ffprobe output") .ToEither() .Match( diff --git a/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs b/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs index dda69201..d09ff38a 100644 --- a/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs @@ -19,9 +19,9 @@ public abstract class MediaServerMovieLibraryScanner false; protected virtual bool ServerReturnsStatisticsWithMetadata => false; @@ -115,7 +115,7 @@ public abstract class MediaServerMovieLibraryScanner> maybeMovie; if (ServerReturnsStatisticsWithMetadata) @@ -256,7 +256,7 @@ public abstract class MediaServerMovieLibraryScanner result, TMovie incoming) => Task.FromResult(Option.None); - + protected abstract Task>> GetFullMetadataAndStatistics( TConnectionParameters connectionParameters, TLibrary library, @@ -379,12 +379,12 @@ public abstract class MediaServerMovieLibraryScanner r in metadataResult.RightToSeq()) { result = r; @@ -393,7 +393,7 @@ public abstract class MediaServerMovieLibraryScanner>> UpdateMetadata( TConnectionParameters connectionParameters, TLibrary library, @@ -450,8 +450,8 @@ public abstract class MediaServerMovieLibraryScanner>> UpdateSubtitles( MediaItemScanResult existing) { diff --git a/ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs b/ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs index 9f16bbc1..5cd0a685 100644 --- a/ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs @@ -21,9 +21,9 @@ public abstract class MediaServerTelevisionLibraryScanner>> UpdateMetadata( MediaItemScanResult result, EpisodeMetadata fullMetadata); - + private async Task> ScanSeasons( IMediaServerTelevisionRepository televisionRepository, TLibrary library, @@ -579,7 +579,8 @@ public abstract class MediaServerTelevisionLibraryScanner r in metadataResult.RightToSeq()) { result = r; @@ -782,28 +783,28 @@ public abstract class MediaServerTelevisionLibraryScanner>> UpdateSubtitles( MediaItemScanResult existing) { diff --git a/ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs index 9908f815..7e3142da 100644 --- a/ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs @@ -20,11 +20,11 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner { private readonly IClient _client; private readonly ILibraryRepository _libraryRepository; - private readonly IMediaItemRepository _mediaItemRepository; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalSubtitlesProvider _localSubtitlesProvider; private readonly ILogger _logger; + private readonly IMediaItemRepository _mediaItemRepository; private readonly IMediator _mediator; private readonly IMovieRepository _movieRepository; @@ -76,7 +76,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner try { ImmutableHashSet allTrashedItems = await _mediaItemRepository.GetAllTrashedItems(libraryPath); - + decimal progressSpread = progressMax - progressMin; var foldersCompleted = 0; @@ -142,7 +142,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner continue; } - bool etagMatches = await knownFolder.Map(f => f.Etag ?? string.Empty).IfNoneAsync(string.Empty) == etag; + bool etagMatches = await knownFolder.Map(f => f.Etag ?? string.Empty).IfNoneAsync(string.Empty) == etag; if (etagMatches) { if (allFiles.Any(allTrashedItems.Contains)) diff --git a/ErsatzTV.Scanner/Core/Metadata/Nfo/MovieNfo.cs b/ErsatzTV.Scanner/Core/Metadata/Nfo/MovieNfo.cs index 6daa0d92..70947e99 100644 --- a/ErsatzTV.Scanner/Core/Metadata/Nfo/MovieNfo.cs +++ b/ErsatzTV.Scanner/Core/Metadata/Nfo/MovieNfo.cs @@ -19,7 +19,9 @@ public class MovieNfo public int Year { get; set; } public string? ContentRating { get; set; } public Option Premiered { get; set; } + public string? Plot { get; set; } + // public string? Tagline { get; set; } public List Genres { get; } public List Tags { get; } diff --git a/ErsatzTV.Scanner/Core/Metadata/Nfo/NfoReader.cs b/ErsatzTV.Scanner/Core/Metadata/Nfo/NfoReader.cs index 77971b23..69ae5de3 100644 --- a/ErsatzTV.Scanner/Core/Metadata/Nfo/NfoReader.cs +++ b/ErsatzTV.Scanner/Core/Metadata/Nfo/NfoReader.cs @@ -19,7 +19,13 @@ public abstract class NfoReader : NfoReaderBase protected async Task SanitizedStreamForFile(string fileName) { - await using var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, Buffer.Length, true); + await using var fs = new FileStream( + fileName, + FileMode.Open, + FileAccess.Read, + FileShare.Read, + Buffer.Length, + true); using var sr = new StreamReader(fs, Encoding.UTF8); string text = await sr.ReadToEndAsync(); // trim BOM and zero width space, replace controls with replacement character @@ -66,7 +72,9 @@ public abstract class NfoReader : NfoReaderBase { try { - if (nfo is not null && DateTime.TryParse(await reader.ReadElementContentAsStringAsync(), out DateTime result)) + if (nfo is not null && DateTime.TryParse( + await reader.ReadElementContentAsStringAsync(), + out DateTime result)) { action(nfo, result); } diff --git a/ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs b/ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs index b04f8720..a50e67b4 100644 --- a/ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs @@ -592,7 +592,7 @@ public class PlexTelevisionLibraryScanner : { result.IsUpdated = true; } - + if (await _metadataRepository.UpdateSubtitles(existingMetadata, fullMetadata.Subtitles)) { result.IsUpdated = true; diff --git a/ErsatzTV.Scanner/Program.cs b/ErsatzTV.Scanner/Program.cs index 9a121264..60aa7fee 100644 --- a/ErsatzTV.Scanner/Program.cs +++ b/ErsatzTV.Scanner/Program.cs @@ -55,7 +55,7 @@ public class Program .Enrich.FromLogContext() .WriteTo.Console(new CompactJsonFormatter(), standardErrorFromLevel: LogEventLevel.Debug) .CreateLogger(); - + try { await CreateHostBuilder(args).Build().RunAsync(); @@ -89,7 +89,7 @@ public class Program }), ServiceLifetime.Scoped, ServiceLifetime.Singleton); - + services.AddDbContextFactory( options => options.UseSqlite( connectionString, @@ -131,7 +131,7 @@ public class Program services.AddScoped(); services.AddScoped(); services.AddScoped(); - + services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -139,7 +139,7 @@ public class Program services.AddScoped(); services.AddScoped(); services.AddScoped(); - + services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -157,7 +157,7 @@ public class Program services.AddScoped(); services.AddScoped(); services.AddScoped(); - + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -169,24 +169,47 @@ public class Program services.AddMediatR(config => config.RegisterServicesFromAssemblyContaining()); services.AddMemoryCache(); - + services.AddHostedService(); }) .UseSerilog(); private class BugsnagNoopClient : IClient { - public void Notify(Exception exception) { } - public void Notify(Exception exception, Middleware callback) { } - public void Notify(Exception exception, Severity severity) { } - public void Notify(Exception exception, Severity severity, Middleware callback) { } - public void Notify(Exception exception, HandledState handledState) { } - public void Notify(Exception exception, HandledState handledState, Middleware callback) { } - public void Notify(Report report, Middleware callback) { } - public void BeforeNotify(Middleware middleware) { } + public void Notify(Exception exception) + { + } + + public void Notify(Exception exception, Middleware callback) + { + } + + public void Notify(Exception exception, Severity severity) + { + } + + public void Notify(Exception exception, Severity severity, Middleware callback) + { + } + + public void Notify(Exception exception, HandledState handledState) + { + } + + public void Notify(Exception exception, HandledState handledState, Middleware callback) + { + } + + public void Notify(Report report, Middleware callback) + { + } + + public void BeforeNotify(Middleware middleware) + { + } public IBreadcrumbs Breadcrumbs => new Breadcrumbs(Configuration); public ISessionTracker SessionTracking => new SessionTracker(Configuration); public IConfiguration Configuration => new Configuration(); } -} \ No newline at end of file +} diff --git a/ErsatzTV.Scanner/Worker.cs b/ErsatzTV.Scanner/Worker.cs index d05a2ecd..2811b23b 100644 --- a/ErsatzTV.Scanner/Worker.cs +++ b/ErsatzTV.Scanner/Worker.cs @@ -12,9 +12,9 @@ namespace ErsatzTV.Scanner; public class Worker : BackgroundService { - private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IHostApplicationLifetime _appLifetime; private readonly ILogger _logger; + private readonly IServiceScopeFactory _serviceScopeFactory; public Worker( IServiceScopeFactory serviceScopeFactory, @@ -25,11 +25,11 @@ public class Worker : BackgroundService _appLifetime = appLifetime; _logger = logger; } - + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { RootCommand rootCommand = ConfigureCommandLine(); - + // need to strip program name (head) from command line args string[] arguments = Environment.GetCommandLineArgs().Skip(1).ToArray(); @@ -41,7 +41,7 @@ public class Worker : BackgroundService private RootCommand ConfigureCommandLine() { var forceOption = new System.CommandLine.Option( - name: "--force", + "--force", description: "Force scanning", parseArgument: _ => true) { @@ -50,17 +50,17 @@ public class Worker : BackgroundService }; var deepOption = new System.CommandLine.Option( - name: "--deep", + "--deep", description: "Deep scan", parseArgument: _ => true) { AllowMultipleArgumentsPerToken = true, Arity = ArgumentArity.Zero }; - + var libraryIdArgument = new Argument("library-id", "The library id to scan"); var mediaSourceIdArgument = new Argument("media-source-id", "The media source id to scan"); - + var scanLocalCommand = new Command("scan-local", "Scan a local library"); scanLocalCommand.AddArgument(libraryIdArgument); scanLocalCommand.AddOption(forceOption); @@ -83,7 +83,7 @@ public class Worker : BackgroundService scanJellyfinCommand.AddArgument(libraryIdArgument); scanJellyfinCommand.AddOption(forceOption); scanJellyfinCommand.AddOption(deepOption); - + scanLocalCommand.SetHandler( async context => { @@ -91,7 +91,7 @@ public class Worker : BackgroundService { bool force = context.ParseResult.GetValueForOption(forceOption); SetProcessPriority(force); - + int libraryId = context.ParseResult.GetValueForArgument(libraryIdArgument); using IServiceScope scope = _serviceScopeFactory.CreateScope(); @@ -101,7 +101,7 @@ public class Worker : BackgroundService await mediator.Send(scan, context.GetCancellationToken()); } }); - + scanPlexCommand.SetHandler( async context => { @@ -120,7 +120,7 @@ public class Worker : BackgroundService await mediator.Send(scan, context.GetCancellationToken()); } }); - + scanEmbyCommand.SetHandler( async context => { @@ -139,7 +139,7 @@ public class Worker : BackgroundService await mediator.Send(scan, context.GetCancellationToken()); } }); - + scanEmbyCollectionsCommand.SetHandler( async context => { @@ -157,7 +157,7 @@ public class Worker : BackgroundService await mediator.Send(scan, context.GetCancellationToken()); } }); - + scanJellyfinCommand.SetHandler( async context => { diff --git a/ErsatzTV/Controllers/AccountController.cs b/ErsatzTV/Controllers/AccountController.cs index e1752cdd..cf718cc9 100644 --- a/ErsatzTV/Controllers/AccountController.cs +++ b/ErsatzTV/Controllers/AccountController.cs @@ -6,8 +6,5 @@ namespace ErsatzTV.Controllers; public class AccountController : ControllerBase { [HttpPost("account/logout")] - public IActionResult Logout() - { - return new SignOutResult(new[] { "oidc", "cookie" }); - } + public IActionResult Logout() => new SignOutResult(new[] { "oidc", "cookie" }); } diff --git a/ErsatzTV/Controllers/InternalController.cs b/ErsatzTV/Controllers/InternalController.cs index 8edd7226..719e3e01 100644 --- a/ErsatzTV/Controllers/InternalController.cs +++ b/ErsatzTV/Controllers/InternalController.cs @@ -8,9 +8,9 @@ using ErsatzTV.Application.Subtitles.Queries; using ErsatzTV.Core; using ErsatzTV.Core.FFmpeg; using ErsatzTV.Extensions; +using Flurl; using MediatR; using Microsoft.AspNetCore.Mvc; -using Flurl; namespace ErsatzTV.Controllers; @@ -99,7 +99,7 @@ public class InternalController : ControllerBase Left: _ => new NotFoundResult(), Right: r => { - Url fullPath = new Uri(r.Uri, path).SetQueryParam("X-Plex-Token", r.AuthToken); + Url fullPath = new Uri(r.Uri, path).SetQueryParam("X-Plex-Token", r.AuthToken); return new RedirectResult(fullPath.ToString()); }); } @@ -115,7 +115,7 @@ public class InternalController : ControllerBase Right: r => { Url fullPath; - + if (path.Contains("Subtitles")) { fullPath = Flurl.Url.Parse(r.Address) @@ -133,7 +133,7 @@ public class InternalController : ControllerBase return new RedirectResult(fullPath.ToString()); }); } - + [HttpGet("/media/emby/{*path}")] public async Task GetEmbyMedia(string path, CancellationToken cancellationToken) { @@ -145,7 +145,7 @@ public class InternalController : ControllerBase Right: r => { Url fullPath; - + if (path.Contains("Subtitles")) { fullPath = Flurl.Url.Parse(r.Address) @@ -178,12 +178,12 @@ public class InternalController : ControllerBase { return new RedirectResult(r); } - + string mimeType = Path.GetExtension(r).ToLowerInvariant() switch { "ass" or "ssa" => "text/x-ssa", "vtt" => "text/vtt", - _ => "application/x-subrip", + _ => "application/x-subrip" }; return new PhysicalFileResult(r, mimeType); diff --git a/ErsatzTV/Filters/ConditionalIptvAuthorizeFilter.cs b/ErsatzTV/Filters/ConditionalIptvAuthorizeFilter.cs index f5a1385c..32020695 100644 --- a/ErsatzTV/Filters/ConditionalIptvAuthorizeFilter.cs +++ b/ErsatzTV/Filters/ConditionalIptvAuthorizeFilter.cs @@ -22,4 +22,4 @@ public class ConditionalIptvAuthorizeFilter : AuthorizeFilter return Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/ErsatzTV/Filters/V2ApiActionFilter.cs b/ErsatzTV/Filters/V2ApiActionFilter.cs index cf035b4e..572cb732 100644 --- a/ErsatzTV/Filters/V2ApiActionFilter.cs +++ b/ErsatzTV/Filters/V2ApiActionFilter.cs @@ -6,8 +6,8 @@ namespace ErsatzTV.Filters; public class V2ApiActionFilter : ActionFilterAttribute { private static readonly Lazy UseV2Ui = - new(() => !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("ETV_V2_UI"))); - + new(() => !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("ETV_V2_UI"))); + public override void OnActionExecuting(ActionExecutingContext context) { if (!UseV2Ui.Value) diff --git a/ErsatzTV/JwtHelper.cs b/ErsatzTV/JwtHelper.cs index 345a0268..100bbf9e 100644 --- a/ErsatzTV/JwtHelper.cs +++ b/ErsatzTV/JwtHelper.cs @@ -1,10 +1,13 @@ -using Microsoft.IdentityModel.Tokens; using System.Text; +using Microsoft.IdentityModel.Tokens; namespace ErsatzTV; public static class JwtHelper { + public static SymmetricSecurityKey IssuerSigningKey { get; private set; } + public static bool IsEnabled { get; private set; } + public static void Init(IConfiguration configuration) { string issuerSigningKey = configuration["JWT:IssuerSigningKey"]; @@ -14,7 +17,4 @@ public static class JwtHelper IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(issuerSigningKey!)); } } - - public static SymmetricSecurityKey IssuerSigningKey { get; private set; } - public static bool IsEnabled { get; private set; } } diff --git a/ErsatzTV/OidcHelper.cs b/ErsatzTV/OidcHelper.cs index d4fa202e..e99f2687 100644 --- a/ErsatzTV/OidcHelper.cs +++ b/ErsatzTV/OidcHelper.cs @@ -2,6 +2,12 @@ namespace ErsatzTV; public static class OidcHelper { + public static string Authority { get; private set; } + public static string ClientId { get; private set; } + public static string ClientSecret { get; private set; } + public static string LogoutUri { get; private set; } + public static bool IsEnabled { get; private set; } + public static void Init(IConfiguration configuration) { Authority = configuration["OIDC:Authority"]; @@ -10,13 +16,7 @@ public static class OidcHelper LogoutUri = configuration["OIDC:LogoutUri"]; IsEnabled = !string.IsNullOrWhiteSpace(Authority) && - !string.IsNullOrWhiteSpace(ClientId) && - !string.IsNullOrWhiteSpace(ClientSecret); + !string.IsNullOrWhiteSpace(ClientId) && + !string.IsNullOrWhiteSpace(ClientSecret); } - - public static string Authority { get; private set; } - public static string ClientId { get; private set; } - public static string ClientSecret { get; private set; } - public static string LogoutUri { get; private set; } - public static bool IsEnabled { get; private set; } } diff --git a/ErsatzTV/Pages/ArtistList.razor b/ErsatzTV/Pages/ArtistList.razor index 81d76be9..50289c6d 100644 --- a/ErsatzTV/Pages/ArtistList.razor +++ b/ErsatzTV/Pages/ArtistList.razor @@ -1,7 +1,7 @@ @page "/media/music/artists" @page "/media/music/artists/page/{PageNumber:int}" -@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Extensions +@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Application.MediaCards @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.Search @@ -99,7 +99,7 @@ private void PrevPage() { - string uri = $"media/music/artists/page/{PageNumber - 1}"; + var uri = $"media/music/artists/page/{PageNumber - 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); @@ -110,7 +110,7 @@ private void NextPage() { - string uri = $"media/music/artists/page/{PageNumber + 1}"; + var uri = $"media/music/artists/page/{PageNumber + 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); diff --git a/ErsatzTV/Pages/ChannelEditor.razor b/ErsatzTV/Pages/ChannelEditor.razor index 29c2c53e..889237de 100644 --- a/ErsatzTV/Pages/ChannelEditor.razor +++ b/ErsatzTV/Pages/ChannelEditor.razor @@ -5,11 +5,11 @@ @using ErsatzTV.Application.Filler @using ErsatzTV.Application.Images @using ErsatzTV.Application.MediaItems +@using ErsatzTV.Application.Templates @using ErsatzTV.Application.Watermarks @using System.Globalization @using ErsatzTV.Core.Domain.Filler @using ErsatzTV.Application.Channels -@using ErsatzTV.Application.Templates @implements IDisposable @inject NavigationManager NavigationManager @inject ILogger Logger diff --git a/ErsatzTV/Pages/EmbyMediaSources.razor b/ErsatzTV/Pages/EmbyMediaSources.razor index 50dddfcb..d9b5cc08 100644 --- a/ErsatzTV/Pages/EmbyMediaSources.razor +++ b/ErsatzTV/Pages/EmbyMediaSources.razor @@ -1,6 +1,6 @@ @page "/media/sources/emby" -@using ErsatzTV.Core.Interfaces.Emby @using ErsatzTV.Application.Emby +@using ErsatzTV.Core.Interfaces.Emby @using ErsatzTV.Core.Emby @implements IDisposable @inject IEmbySecretStore _embySecretStore diff --git a/ErsatzTV/Pages/EpisodeList.razor b/ErsatzTV/Pages/EpisodeList.razor index 389f2487..b8f4aa7a 100644 --- a/ErsatzTV/Pages/EpisodeList.razor +++ b/ErsatzTV/Pages/EpisodeList.razor @@ -1,7 +1,7 @@ @page "/media/tv/episodes" @page "/media/tv/episodes/page/{PageNumber:int}" -@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Extensions +@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Application.MediaCards @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.Search @@ -99,7 +99,7 @@ private void PrevPage() { - string uri = $"media/tv/episodes/page/{PageNumber - 1}"; + var uri = $"media/tv/episodes/page/{PageNumber - 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); @@ -110,7 +110,7 @@ private void NextPage() { - string uri = $"media/tv/episodes/page/{PageNumber + 1}"; + var uri = $"media/tv/episodes/page/{PageNumber + 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); diff --git a/ErsatzTV/Pages/FFmpegEditor.razor b/ErsatzTV/Pages/FFmpegEditor.razor index 989b58f4..042304bd 100644 --- a/ErsatzTV/Pages/FFmpegEditor.razor +++ b/ErsatzTV/Pages/FFmpegEditor.razor @@ -2,9 +2,9 @@ @page "/ffmpeg/add" @using Microsoft.Extensions.Caching.Memory @using ErsatzTV.Application.Resolutions -@using ErsatzTV.Core.FFmpeg @using ErsatzTV.Application.FFmpegProfiles @using System.Runtime.InteropServices +@using ErsatzTV.Core.FFmpeg @implements IDisposable @inject NavigationManager _navigationManager @inject ILogger _logger diff --git a/ErsatzTV/Pages/FillerPresetEditor.razor b/ErsatzTV/Pages/FillerPresetEditor.razor index d6628f3f..13c2c1b0 100644 --- a/ErsatzTV/Pages/FillerPresetEditor.razor +++ b/ErsatzTV/Pages/FillerPresetEditor.razor @@ -3,9 +3,9 @@ @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.MediaItems @using ErsatzTV.Application.Television -@using ErsatzTV.Core.Domain.Filler @using ErsatzTV.Application.Artists @using ErsatzTV.Application.Filler +@using ErsatzTV.Core.Domain.Filler @implements IDisposable @inject NavigationManager _navigationManager @inject ILogger _logger diff --git a/ErsatzTV/Pages/Index.razor b/ErsatzTV/Pages/Index.razor index 228ee60e..ae3b4aa0 100644 --- a/ErsatzTV/Pages/Index.razor +++ b/ErsatzTV/Pages/Index.razor @@ -1,8 +1,8 @@ @page "/" @using Microsoft.Extensions.Caching.Memory @using System.Reflection -@using ErsatzTV.Core.Interfaces.GitHub @using ErsatzTV.Core.Health +@using ErsatzTV.Core.Interfaces.GitHub @using ErsatzTV.Application.Health @implements IDisposable @inject IGitHubApiClient _gitHubApiClient diff --git a/ErsatzTV/Pages/JellyfinMediaSources.razor b/ErsatzTV/Pages/JellyfinMediaSources.razor index 11b63c86..b9fc1a24 100644 --- a/ErsatzTV/Pages/JellyfinMediaSources.razor +++ b/ErsatzTV/Pages/JellyfinMediaSources.razor @@ -1,6 +1,6 @@ @page "/media/sources/jellyfin" -@using ErsatzTV.Core.Interfaces.Jellyfin @using ErsatzTV.Application.Jellyfin +@using ErsatzTV.Core.Interfaces.Jellyfin @using ErsatzTV.Core.Jellyfin @implements IDisposable @inject IJellyfinSecretStore _jellyfinSecretStore diff --git a/ErsatzTV/Pages/Libraries.razor b/ErsatzTV/Pages/Libraries.razor index 1941e222..a35aeb46 100644 --- a/ErsatzTV/Pages/Libraries.razor +++ b/ErsatzTV/Pages/Libraries.razor @@ -1,9 +1,9 @@ @page "/media/libraries" -@using MediatR.Courier @using ErsatzTV.Application.Libraries @using ErsatzTV.Application.MediaSources @using ErsatzTV.Application.Plex @using ErsatzTV.Core.Metadata +@using MediatR.Courier @using PlexLibraryViewModel = ErsatzTV.Application.Libraries.PlexLibraryViewModel @using ErsatzTV.Application.Jellyfin @using ErsatzTV.Application.Emby @@ -199,7 +199,7 @@ case "emby": return _locker.AreEmbyCollectionsLocked(); } - + return false; } diff --git a/ErsatzTV/Pages/Logs.razor b/ErsatzTV/Pages/Logs.razor index 77b5a7f1..699c9241 100644 --- a/ErsatzTV/Pages/Logs.razor +++ b/ErsatzTV/Pages/Logs.razor @@ -14,7 +14,8 @@ Logs + AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"> + @@ -99,7 +100,7 @@ return new TableData { TotalItems = data.TotalCount, Items = data.Page }; } - + private void OnSearch(string text) { _searchString = text; diff --git a/ErsatzTV/Pages/Movie.razor b/ErsatzTV/Pages/Movie.razor index 057d00ca..93585d16 100644 --- a/ErsatzTV/Pages/Movie.razor +++ b/ErsatzTV/Pages/Movie.razor @@ -2,9 +2,9 @@ @using ErsatzTV.Extensions @using ErsatzTV.Application.Movies @using System.Globalization -@using ErsatzTV.Application.MediaCards @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.MediaItems +@using ErsatzTV.Application.MediaCards @implements IDisposable @inject IMediator _mediator @inject IDialogService _dialog @@ -283,4 +283,5 @@ DialogResult _ = await dialog.Result; } } + } \ No newline at end of file diff --git a/ErsatzTV/Pages/MovieList.razor b/ErsatzTV/Pages/MovieList.razor index 0e81e7f9..981a7460 100644 --- a/ErsatzTV/Pages/MovieList.razor +++ b/ErsatzTV/Pages/MovieList.razor @@ -1,7 +1,7 @@ @page "/media/movies" @page "/media/movies/page/{PageNumber:int}" -@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Extensions +@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Application.MediaCards @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.Search @@ -98,7 +98,7 @@ private void PrevPage() { - string uri = $"media/movies/page/{PageNumber - 1}"; + var uri = $"media/movies/page/{PageNumber - 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); @@ -109,7 +109,7 @@ private void NextPage() { - string uri = $"media/movies/page/{PageNumber + 1}"; + var uri = $"media/movies/page/{PageNumber + 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); diff --git a/ErsatzTV/Pages/MultiSelectBase.cs b/ErsatzTV/Pages/MultiSelectBase.cs index 628e2bf9..6344ac65 100644 --- a/ErsatzTV/Pages/MultiSelectBase.cs +++ b/ErsatzTV/Pages/MultiSelectBase.cs @@ -112,7 +112,8 @@ public class MultiSelectBase : FragmentNavigationBase { { "EntityType", count.ToString() }, { "EntityName", entityName } }; var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; - IDialogReference dialog = await Dialog.ShowAsync("Add To Collection", parameters, options); + IDialogReference dialog = + await Dialog.ShowAsync("Add To Collection", parameters, options); DialogResult result = await dialog.Result; if (!result.Canceled && result.Data is MediaCollectionViewModel collection) { diff --git a/ErsatzTV/Pages/MusicVideoList.razor b/ErsatzTV/Pages/MusicVideoList.razor index 0f4302d4..d50a7cf3 100644 --- a/ErsatzTV/Pages/MusicVideoList.razor +++ b/ErsatzTV/Pages/MusicVideoList.razor @@ -1,7 +1,7 @@ @page "/media/music/videos" @page "/media/music/videos/page/{PageNumber:int}" -@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Extensions +@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Application.MediaCards @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.Search @@ -99,7 +99,7 @@ private void PrevPage() { - string uri = $"media/music/videos/page/{PageNumber - 1}"; + var uri = $"media/music/videos/page/{PageNumber - 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); @@ -110,7 +110,7 @@ private void NextPage() { - string uri = $"media/music/videos/page/{PageNumber + 1}"; + var uri = $"media/music/videos/page/{PageNumber + 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); diff --git a/ErsatzTV/Pages/OtherVideoList.razor b/ErsatzTV/Pages/OtherVideoList.razor index 2afd8be8..621e16f3 100644 --- a/ErsatzTV/Pages/OtherVideoList.razor +++ b/ErsatzTV/Pages/OtherVideoList.razor @@ -1,7 +1,7 @@ @page "/media/other/videos" @page "/media/other/videos/page/{PageNumber:int}" -@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Extensions +@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Application.MediaCards @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.Search @@ -99,7 +99,7 @@ private void PrevPage() { - string uri = $"media/other/videos/page/{PageNumber - 1}"; + var uri = $"media/other/videos/page/{PageNumber - 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); @@ -110,7 +110,7 @@ private void NextPage() { - string uri = $"media/other/videos/page/{PageNumber + 1}"; + var uri = $"media/other/videos/page/{PageNumber + 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); diff --git a/ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor b/ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor index 78943e65..7b6ae945 100644 --- a/ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor +++ b/ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor @@ -1,10 +1,9 @@ @page "/playouts/{Id:int}/alternate-schedules" +@using System.Globalization @using ErsatzTV.Application.ProgramSchedules @using ErsatzTV.Application.Playouts -@using Microsoft.AspNetCore.Components @using ErsatzTV.Application.Channels @using System.Text -@using System.Globalization @implements IDisposable @inject NavigationManager NavigationManager @inject ILogger Logger @@ -12,202 +11,202 @@ @inject IMediator Mediator - - - @_channelName Alternate Schedules - - In priority order from top to bottom - - - - - - - - - - - - Schedule - Days of the Week - Days of the Month - Months - - - - - - - - @context.ProgramSchedule.Name - - - - - @ToDaysOfWeekString(context.DaysOfWeek) - - - - - @ToDaysOfMonthString(context.DaysOfMonth) - - - - - @ToMonthsOfYearString(context.MonthsOfYear) - - - - - - - - - - - - - - - - - - Add Alternate Schedule - - - Save Changes - - -@if (_selectedItem is not null) -{ - - -
-
- - - - @foreach (ProgramScheduleViewModel schedule in _schedules) - { - @schedule.Name - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Weekdays - - - Weekends - - - All - - - None - - - -
-
- - - - @foreach (int day in Enumerable.Range(1, 31)) - { - - - - } - - - - - All - - - None - - - -
-
- - - - - - @foreach (int month in Enumerable.Range(2, 11)) - { - - - - } - - - - All - - - None - - - -
-
-
-} + + + @_channelName Alternate Schedules + + In priority order from top to bottom + + + + + + + + + + + + Schedule + Days of the Week + Days of the Month + Months + + + + + + + + @context.ProgramSchedule.Name + + + + + @ToDaysOfWeekString(context.DaysOfWeek) + + + + + @ToDaysOfMonthString(context.DaysOfMonth) + + + + + @ToMonthsOfYearString(context.MonthsOfYear) + + + + + + + + + + + + + + + + + + Add Alternate Schedule + + + Save Changes + + + @if (_selectedItem is not null) + { + + +
+
+ + + + @foreach (ProgramScheduleViewModel schedule in _schedules) + { + @schedule.Name + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Weekdays + + + Weekends + + + All + + + None + + + +
+
+ + + + @foreach (int day in Enumerable.Range(1, 31)) + { + + + + } + + + + + All + + + None + + + +
+
+ + + + + + @foreach (int month in Enumerable.Range(2, 11)) + { + + + + } + + + + All + + + None + + + +
+
+
+ }
@code { @@ -301,11 +300,8 @@ _selectedItem.DaysOfWeek.AddRange(ProgramScheduleAlternate.AllDaysOfWeek()); } - private void SelectNoDaysOfWeek() - { - _selectedItem.DaysOfWeek.Clear(); - } - + private void SelectNoDaysOfWeek() => _selectedItem.DaysOfWeek.Clear(); + private void DayOfMonthChanged(int dayOfMonth, bool isChecked) { if (isChecked && !_selectedItem.DaysOfMonth.Contains(dayOfMonth)) @@ -319,18 +315,15 @@ _selectedItem.DaysOfMonth.Remove(dayOfMonth); } } - + private void SelectAllDaysOfMonth() { _selectedItem.DaysOfMonth.Clear(); _selectedItem.DaysOfMonth.AddRange(ProgramScheduleAlternate.AllDaysOfMonth()); } - private void SelectNoDaysOfMonth() - { - _selectedItem.DaysOfMonth.Clear(); - } - + private void SelectNoDaysOfMonth() => _selectedItem.DaysOfMonth.Clear(); + private void MonthOfYearChanged(int monthOfYear, bool isChecked) { if (isChecked && !_selectedItem.MonthsOfYear.Contains(monthOfYear)) @@ -351,10 +344,7 @@ _selectedItem.MonthsOfYear.AddRange(ProgramScheduleAlternate.AllMonthsOfYear()); } - private void SelectNoMonthsOfYear() - { - _selectedItem.MonthsOfYear.Clear(); - } + private void SelectNoMonthsOfYear() => _selectedItem.MonthsOfYear.Clear(); private void AddAlternateSchedule() { @@ -379,14 +369,14 @@ private void MoveItemUp(PlayoutAlternateScheduleEditViewModel item) { - // swap with lower index + // swap with lower index PlayoutAlternateScheduleEditViewModel toSwap = _items.OrderByDescending(x => x.Index).First(x => x.Index < item.Index); (toSwap.Index, item.Index) = (item.Index, toSwap.Index); } private void MoveItemDown(PlayoutAlternateScheduleEditViewModel item) { - // swap with higher index + // swap with higher index PlayoutAlternateScheduleEditViewModel toSwap = _items.OrderBy(x => x.Index).First(x => x.Index > item.Index); (toSwap.Index, item.Index) = (item.Index, toSwap.Index); } @@ -424,7 +414,7 @@ return string.Join(", ", daysOfWeek.Map(_dtf.GetAbbreviatedDayName)); } - + private string ToDaysOfMonthString(List daysOfMonth) { if (daysOfMonth.Count is 0 or 31) @@ -434,7 +424,7 @@ return ToRangeString(daysOfMonth); } - + private string ToMonthsOfYearString(List monthsOfYear) { if (monthsOfYear.Count is 0 or 12) @@ -457,27 +447,26 @@ { int temp = list[i]; - //add a number + //add a number result.Append(list[i]); - //skip number(s) between a range + //skip number(s) between a range while (i < list.Count - 1 && list[i + 1] == list[i] + 1) { i++; } - //add the range + //add the range if (temp != list[i]) { result.Append("-").Append(list[i]); } - //add comma + //add comma if (i != list.Count - 1) { result.Append(", "); } - } return result.ToString(); diff --git a/ErsatzTV/Pages/Playouts.razor b/ErsatzTV/Pages/Playouts.razor index f4879fff..88a2b8eb 100644 --- a/ErsatzTV/Pages/Playouts.razor +++ b/ErsatzTV/Pages/Playouts.razor @@ -174,7 +174,7 @@ } } } - + private async Task ResetPlayout(PlayoutNameViewModel playout) { await WorkerChannel.WriteAsync(new BuildPlayout(playout.PlayoutId, PlayoutBuildMode.Reset), _cts.Token); diff --git a/ErsatzTV/Pages/PlexLibrariesEditor.razor b/ErsatzTV/Pages/PlexLibrariesEditor.razor index 494e2850..d44bba2b 100644 --- a/ErsatzTV/Pages/PlexLibrariesEditor.razor +++ b/ErsatzTV/Pages/PlexLibrariesEditor.razor @@ -50,4 +50,5 @@ await _scannerWorkerChannel.WriteAsync(new SynchronizePlexLibraryByIdIfNeeded(parameters.LibraryId), _cts.Token); return Unit.Default; } + } \ No newline at end of file diff --git a/ErsatzTV/Pages/PlexMediaSources.razor b/ErsatzTV/Pages/PlexMediaSources.razor index 557f4ca8..cdc0d4ec 100644 --- a/ErsatzTV/Pages/PlexMediaSources.razor +++ b/ErsatzTV/Pages/PlexMediaSources.razor @@ -1,6 +1,6 @@ @page "/media/sources/plex" -@using ErsatzTV.Core.Interfaces.Plex @using ErsatzTV.Application.Plex +@using ErsatzTV.Core.Interfaces.Plex @implements IDisposable @inject IDialogService _dialog @inject IMediator _mediator diff --git a/ErsatzTV/Pages/ScheduleItemsEditor.razor b/ErsatzTV/Pages/ScheduleItemsEditor.razor index 46df5a15..3df48db6 100644 --- a/ErsatzTV/Pages/ScheduleItemsEditor.razor +++ b/ErsatzTV/Pages/ScheduleItemsEditor.razor @@ -1,12 +1,12 @@ @page "/schedules/{Id:int}/items" +@using ErsatzTV.Application.Filler @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.MediaItems @using ErsatzTV.Application.ProgramSchedules +@using ErsatzTV.Application.Search @using ErsatzTV.Application.Watermarks -@using ErsatzTV.Application.Filler @using System.Globalization @using ErsatzTV.Core.Domain.Filler -@using ErsatzTV.Application.Search @implements IDisposable @inject NavigationManager NavigationManager @inject ILogger Logger @@ -114,7 +114,7 @@ { + ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."> Only the first 10 items are shown @@ -126,7 +126,7 @@ { + ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."> Only the first 10 items are shown @@ -138,7 +138,7 @@ { + ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."> Only the first 10 items are shown @@ -150,7 +150,7 @@ { + ToStringFunc="@(c => c?.Name)" Placeholder="Type to search..."> Only the first 10 items are shown @@ -162,7 +162,7 @@ { @@ -175,7 +175,7 @@ { @@ -391,7 +391,7 @@ } } } - + private async Task> SearchCollections(string value) { if (string.IsNullOrWhiteSpace(value)) @@ -421,7 +421,7 @@ return await Mediator.Send(new SearchSmartCollections(value), _cts.Token); } - + private async Task> SearchTelevisionShows(string value) { if (string.IsNullOrWhiteSpace(value)) @@ -431,7 +431,7 @@ return await Mediator.Send(new SearchTelevisionShows(value), _cts.Token); } - + private async Task> SearchTelevisionSeasons(string value) { if (string.IsNullOrWhiteSpace(value)) @@ -441,7 +441,7 @@ return await Mediator.Send(new SearchTelevisionSeasons(value), _cts.Token); } - + private async Task> SearchArtists(string value) { if (string.IsNullOrWhiteSpace(value)) diff --git a/ErsatzTV/Pages/Schedules.razor b/ErsatzTV/Pages/Schedules.razor index 4daf170c..486fd6d1 100644 --- a/ErsatzTV/Pages/Schedules.razor +++ b/ErsatzTV/Pages/Schedules.razor @@ -145,7 +145,7 @@ } } } - + private async Task CopySchedule(ProgramScheduleViewModel programSchedule) { var parameters = new DialogParameters { { "ProgramScheduleId", programSchedule.Id } }; diff --git a/ErsatzTV/Pages/Settings.razor b/ErsatzTV/Pages/Settings.razor index fd6ea722..7d3c60bc 100644 --- a/ErsatzTV/Pages/Settings.razor +++ b/ErsatzTV/Pages/Settings.razor @@ -6,8 +6,8 @@ @using ErsatzTV.Application.Watermarks @using System.Globalization @using ErsatzTV.Application.Configuration -@using Serilog.Events @using ErsatzTV.Core.Domain.Filler +@using Serilog.Events @implements IDisposable @inject IMediator _mediator @inject ISnackbar _snackbar diff --git a/ErsatzTV/Pages/SongList.razor b/ErsatzTV/Pages/SongList.razor index 47333058..68116855 100644 --- a/ErsatzTV/Pages/SongList.razor +++ b/ErsatzTV/Pages/SongList.razor @@ -1,7 +1,7 @@ @page "/media/music/songs" @page "/media/music/songs/page/{PageNumber:int}" -@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Extensions +@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Application.MediaCards @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.Search @@ -99,7 +99,7 @@ private void PrevPage() { - string uri = $"media/music/songs/page/{PageNumber - 1}"; + var uri = $"media/music/songs/page/{PageNumber - 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); @@ -110,7 +110,7 @@ private void NextPage() { - string uri = $"media/music/songs/page/{PageNumber + 1}"; + var uri = $"media/music/songs/page/{PageNumber + 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); diff --git a/ErsatzTV/Pages/TelevisionEpisodeList.razor b/ErsatzTV/Pages/TelevisionEpisodeList.razor index 4f086191..3107d708 100644 --- a/ErsatzTV/Pages/TelevisionEpisodeList.razor +++ b/ErsatzTV/Pages/TelevisionEpisodeList.razor @@ -266,7 +266,7 @@ Right: _ => _snackbar.Add($"Added {episode.Title} to collection {collection.Name}", Severity.Success)); } } - + private async Task ShowInfo(TelevisionEpisodeCardViewModel episode) { Either maybeInfo = await _mediator.Send(new GetMediaItemInfo(episode.EpisodeId)); diff --git a/ErsatzTV/Pages/TelevisionSeasonSearchResults.razor b/ErsatzTV/Pages/TelevisionSeasonSearchResults.razor index 806cbc28..ea816dbc 100644 --- a/ErsatzTV/Pages/TelevisionSeasonSearchResults.razor +++ b/ErsatzTV/Pages/TelevisionSeasonSearchResults.razor @@ -1,7 +1,7 @@ @page "/media/tv/seasons" @page "/media/tv/seasons/page/{PageNumber:int}" -@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Extensions +@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Application.MediaCards @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.Search @@ -98,7 +98,7 @@ private void PrevPage() { - string uri = $"media/tv/seasons/page/{PageNumber - 1}"; + var uri = $"media/tv/seasons/page/{PageNumber - 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); @@ -109,7 +109,7 @@ private void NextPage() { - string uri = $"media/tv/seasons/page/{PageNumber + 1}"; + var uri = $"media/tv/seasons/page/{PageNumber + 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); diff --git a/ErsatzTV/Pages/TelevisionShowList.razor b/ErsatzTV/Pages/TelevisionShowList.razor index 77f79cda..0bd9fecf 100644 --- a/ErsatzTV/Pages/TelevisionShowList.razor +++ b/ErsatzTV/Pages/TelevisionShowList.razor @@ -1,7 +1,7 @@ @page "/media/tv/shows" @page "/media/tv/shows/page/{PageNumber:int}" -@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Extensions +@using LanguageExt.UnsafeValueAccess @using ErsatzTV.Application.MediaCards @using ErsatzTV.Application.MediaCollections @using ErsatzTV.Application.Search @@ -98,7 +98,7 @@ private void PrevPage() { - string uri = $"media/tv/shows/page/{PageNumber - 1}"; + var uri = $"media/tv/shows/page/{PageNumber - 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); @@ -109,7 +109,7 @@ private void NextPage() { - string uri = $"media/tv/shows/page/{PageNumber + 1}"; + var uri = $"media/tv/shows/page/{PageNumber + 1}"; if (!string.IsNullOrWhiteSpace(_query)) { (string key, string value) = _query.EncodeQuery(); diff --git a/ErsatzTV/Pages/Troubleshooting.razor b/ErsatzTV/Pages/Troubleshooting.razor index 50540c4f..c35d83da 100644 --- a/ErsatzTV/Pages/Troubleshooting.razor +++ b/ErsatzTV/Pages/Troubleshooting.razor @@ -1,8 +1,8 @@ @page "/system/troubleshooting" +@using ErsatzTV.Application.Troubleshooting @using ErsatzTV.Application.Troubleshooting.Queries @using System.Text.Json @using System.Text.Json.Serialization -@using ErsatzTV.Application.Troubleshooting @implements IDisposable @inject IMediator Mediator @inject IJSRuntime JsRuntime @@ -15,7 +15,7 @@ @_troubleshootingInfo - + Copy @@ -25,7 +25,7 @@ @_nvidiaCapabilities - + Copy @@ -35,7 +35,7 @@ @_vaapiCapabilities - + Copy @@ -80,9 +80,7 @@ _troubleshootingInfo = ex.ToString(); } } - - private async Task CopyToClipboard(ElementReference view) - { - await JsRuntime.InvokeVoidAsync("clipboardCopy.copyText", view); - } + + private async Task CopyToClipboard(ElementReference view) => await JsRuntime.InvokeVoidAsync("clipboardCopy.copyText", view); + } \ No newline at end of file diff --git a/ErsatzTV/Pages/WatermarkEditor.razor b/ErsatzTV/Pages/WatermarkEditor.razor index 23a4b525..815d33ab 100644 --- a/ErsatzTV/Pages/WatermarkEditor.razor +++ b/ErsatzTV/Pages/WatermarkEditor.razor @@ -1,7 +1,7 @@ @page "/watermarks/{Id:int}" @page "/watermarks/add" -@using ErsatzTV.FFmpeg.State @using ErsatzTV.Application.Watermarks +@using ErsatzTV.FFmpeg.State @using ErsatzTV.Application.Images @implements IDisposable @inject NavigationManager NavigationManager @@ -68,7 +68,7 @@ + Disabled="@(_model.Mode == ChannelWatermarkMode.None)"/> _channel; - private readonly IServiceScopeFactory _serviceScopeFactory; private readonly ILogger _logger; + private readonly IServiceScopeFactory _serviceScopeFactory; public ScannerService( ChannelReader channel, @@ -127,7 +127,7 @@ public class ScannerService : BackgroundService entityLocker.UnlockLibrary(request.LibraryId); } } - + private async Task SynchronizePlexLibrary( ISynchronizePlexLibraryById request, CancellationToken cancellationToken) @@ -155,13 +155,13 @@ public class ScannerService : BackgroundService error.Value); } }); - + if (entityLocker.IsLibraryLocked(request.PlexLibraryId)) { entityLocker.UnlockLibrary(request.PlexLibraryId); } } - + private async Task SynchronizeAdminUserId( SynchronizeJellyfinAdminUserId request, CancellationToken cancellationToken) @@ -179,7 +179,7 @@ public class ScannerService : BackgroundService request.JellyfinMediaSourceId, error.Value)); } - + private async Task SynchronizeLibraries(SynchronizeJellyfinLibraries request, CancellationToken cancellationToken) { using IServiceScope scope = _serviceScopeFactory.CreateScope(); @@ -195,7 +195,7 @@ public class ScannerService : BackgroundService request.JellyfinMediaSourceId, error.Value)); } - + private async Task SynchronizeJellyfinLibrary( ISynchronizeJellyfinLibraryById request, CancellationToken cancellationToken) @@ -229,7 +229,7 @@ public class ScannerService : BackgroundService entityLocker.UnlockLibrary(request.JellyfinLibraryId); } } - + private async Task SynchronizeLibraries(SynchronizeEmbyLibraries request, CancellationToken cancellationToken) { using IServiceScope scope = _serviceScopeFactory.CreateScope(); @@ -245,7 +245,7 @@ public class ScannerService : BackgroundService request.EmbyMediaSourceId, error.Value)); } - + private async Task SynchronizeEmbyLibrary(ISynchronizeEmbyLibraryById request, CancellationToken cancellationToken) { using IServiceScope scope = _serviceScopeFactory.CreateScope(); @@ -271,7 +271,7 @@ public class ScannerService : BackgroundService error.Value); } }); - + if (entityLocker.IsLibraryLocked(request.EmbyLibraryId)) { entityLocker.UnlockLibrary(request.EmbyLibraryId); @@ -300,11 +300,10 @@ public class ScannerService : BackgroundService _logger.LogWarning("Unable to synchronize emby collections: {Error}", error.Value); } }); - + if (entityLocker.AreEmbyCollectionsLocked()) { entityLocker.UnlockEmbyCollections(); } } } - diff --git a/ErsatzTV/Services/SchedulerService.cs b/ErsatzTV/Services/SchedulerService.cs index 0f5a1552..d09cde54 100644 --- a/ErsatzTV/Services/SchedulerService.cs +++ b/ErsatzTV/Services/SchedulerService.cs @@ -19,9 +19,9 @@ namespace ErsatzTV.Services; public class SchedulerService : BackgroundService { - private readonly ChannelWriter _scannerWorkerChannel; private readonly IEntityLocker _entityLocker; private readonly ILogger _logger; + private readonly ChannelWriter _scannerWorkerChannel; private readonly IServiceScopeFactory _serviceScopeFactory; private readonly ChannelWriter _workerChannel; @@ -243,11 +243,11 @@ public class SchedulerService : BackgroundService TvContext dbContext = scope.ServiceProvider.GetRequiredService(); var mediaSourceIds = new System.Collections.Generic.HashSet(); - + foreach (EmbyLibrary library in dbContext.EmbyLibraries.Filter(l => l.ShouldSyncItems)) { mediaSourceIds.Add(library.MediaSourceId); - + if (_entityLocker.LockLibrary(library.Id)) { await _scannerWorkerChannel.WriteAsync( diff --git a/ErsatzTV/Shared/LetterBar.razor b/ErsatzTV/Shared/LetterBar.razor index a646f024..e0460bb2 100644 --- a/ErsatzTV/Shared/LetterBar.razor +++ b/ErsatzTV/Shared/LetterBar.razor @@ -45,7 +45,7 @@ private string GetLinkForLetter(char letter) { - string uri = $"{BaseUri}/page/{PageMap.PageMap[letter]}"; + var uri = $"{BaseUri}/page/{PageMap.PageMap[letter]}"; if (!string.IsNullOrWhiteSpace(Query)) { (string key, string value) = Query.EncodeQuery(); diff --git a/ErsatzTV/Shared/MediaItemInfoDialog.razor b/ErsatzTV/Shared/MediaItemInfoDialog.razor index 8725247c..88f2e913 100644 --- a/ErsatzTV/Shared/MediaItemInfoDialog.razor +++ b/ErsatzTV/Shared/MediaItemInfoDialog.razor @@ -13,7 +13,7 @@ - + Copy Close @@ -28,7 +28,7 @@ [Parameter] public MediaItemInfo MediaItemInfo { get; set; } - + private string _info; private ElementReference _infoView; @@ -53,10 +53,7 @@ return Task.CompletedTask; } - private async Task CopyToClipboard(ElementReference view) - { - await JsRuntime.InvokeVoidAsync("clipboardCopy.copyText", view); - } + private async Task CopyToClipboard(ElementReference view) => await JsRuntime.InvokeVoidAsync("clipboardCopy.copyText", view); private void Close() => MudDialog.Close(DialogResult.Ok(true)); } \ No newline at end of file diff --git a/ErsatzTV/Startup.cs b/ErsatzTV/Startup.cs index fdcca6cf..eb7659c5 100644 --- a/ErsatzTV/Startup.cs +++ b/ErsatzTV/Startup.cs @@ -480,7 +480,7 @@ public class Startup app.UseAuthentication(); app.UseAuthorization(); } - + app2.UseEndpoints(e => e.MapFallbackToFile("index.html")); app2.UseFileServer( new FileServerOptions @@ -582,7 +582,7 @@ public class Startup services.AddScoped(); services.AddScoped(); services.AddScoped(); + MultiEpisodeShuffleCollectionEnumeratorFactory>(); services.AddScoped(); services.AddScoped(); diff --git a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs index 000abc87..864a7220 100644 --- a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs +++ b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs @@ -90,7 +90,7 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator x.VideoFormat).Must(c => AmfFormats.Contains(c)) .WithMessage("Amf supports formats (h264, hevc)"); }); - + When( x => x.VideoFormat == FFmpegProfileVideoFormat.Mpeg2Video, () => RuleFor(x => x.BitDepth) diff --git a/ErsatzTV/Validators/PlayoutAlternateScheduleEditViewModelValidator.cs b/ErsatzTV/Validators/PlayoutAlternateScheduleEditViewModelValidator.cs index 17f9e7b7..49a73ca2 100644 --- a/ErsatzTV/Validators/PlayoutAlternateScheduleEditViewModelValidator.cs +++ b/ErsatzTV/Validators/PlayoutAlternateScheduleEditViewModelValidator.cs @@ -5,8 +5,5 @@ namespace ErsatzTV.Validators; public class PlayoutAlternateScheduleEditViewModelValidator : AbstractValidator { - public PlayoutAlternateScheduleEditViewModelValidator() - { - RuleFor(p => p.ProgramSchedule).NotNull(); - } + public PlayoutAlternateScheduleEditViewModelValidator() => RuleFor(p => p.ProgramSchedule).NotNull(); }