diff --git a/CHANGELOG.md b/CHANGELOG.md index f0ddd9cc..dc95c37c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Fixed - Align default docker image (no acceleration) with new images from [ErsatzTV-ffmpeg](https://github.com/jasongdove/ErsatzTV-ffmpeg) +- Fix some transcoding pipelines that use software decoders ### Changed -- Plex, Jellyfin and Emby libraries now retrieve all metadata and statistics from the media server; ffprobe is no longer used +- Plex, Jellyfin and Emby libraries now retrieve all metadata and statistics from the media server + - File systems will no longer be periodically scanned for libraries using these media sources - Plex, Jellyfin and Emby libraries now direct stream content when files are not found on ErsatzTV's file system - Content will still be normalized according to the Channel and FFmpeg Profile settings - Streaming from disk is preferred, so every playback attempt will first check the local file system diff --git a/ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKey.cs b/ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKey.cs index e8182e3e..ef618ad5 100644 --- a/ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKey.cs +++ b/ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKey.cs @@ -2,4 +2,4 @@ namespace ErsatzTV.Application.Configuration; -public record SaveConfigElementByKey(ConfigElementKey Key, string Value) : IRequest; +public record SaveConfigElementByKey(ConfigElementKey Key, string Value) : IRequest; diff --git a/ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKeyHandler.cs b/ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKeyHandler.cs index 3d73942a..530c15d8 100644 --- a/ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKeyHandler.cs +++ b/ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKeyHandler.cs @@ -2,16 +2,15 @@ namespace ErsatzTV.Application.Configuration; -public class SaveConfigElementByKeyHandler : IRequestHandler +public class SaveConfigElementByKeyHandler : IRequestHandler { private readonly IConfigElementRepository _configElementRepository; public SaveConfigElementByKeyHandler(IConfigElementRepository configElementRepository) => _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); - return Unit.Default; } } diff --git a/ErsatzTV.Application/ErsatzTV.Application.csproj b/ErsatzTV.Application/ErsatzTV.Application.csproj index 234ab919..377b9c18 100644 --- a/ErsatzTV.Application/ErsatzTV.Application.csproj +++ b/ErsatzTV.Application/ErsatzTV.Application.csproj @@ -10,7 +10,7 @@ - + all diff --git a/ErsatzTV.Application/Maintenance/Commands/ReleaseMemory.cs b/ErsatzTV.Application/Maintenance/Commands/ReleaseMemory.cs index 8fbaa028..288b3a7a 100644 --- a/ErsatzTV.Application/Maintenance/Commands/ReleaseMemory.cs +++ b/ErsatzTV.Application/Maintenance/Commands/ReleaseMemory.cs @@ -1,6 +1,6 @@ namespace ErsatzTV.Application.Maintenance; -public record ReleaseMemory(bool ForceAggressive) : IRequest, IBackgroundServiceRequest +public record ReleaseMemory(bool ForceAggressive) : IRequest, IBackgroundServiceRequest { public DateTimeOffset RequestTime = DateTimeOffset.Now; } diff --git a/ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs b/ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs index 6900fe83..c4b0be28 100644 --- a/ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs +++ b/ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.Logging; namespace ErsatzTV.Application.Maintenance; -public class ReleaseMemoryHandler : IRequestHandler +public class ReleaseMemoryHandler : IRequestHandler { private static long _lastRelease; @@ -19,12 +19,12 @@ public class ReleaseMemoryHandler : IRequestHandler _logger = logger; } - public Task Handle(ReleaseMemory request, CancellationToken cancellationToken) + public Task Handle(ReleaseMemory request, CancellationToken cancellationToken) { if (!request.ForceAggressive && _lastRelease > request.RequestTime.Ticks) { // we've already released since the request was created, so don't bother - return Task.FromResult(Unit.Default); + return Task.CompletedTask; } bool hasActiveWorkers = _ffmpegSegmenterService.SessionWorkers.Any() || FFmpegProcess.ProcessCount > 0; @@ -45,6 +45,6 @@ public class ReleaseMemoryHandler : IRequestHandler _logger.LogDebug("Completed garbage collection"); Interlocked.Exchange(ref _lastRelease, DateTimeOffset.Now.Ticks); - return Task.FromResult(Unit.Default); + return Task.CompletedTask; } } diff --git a/ErsatzTV.Application/Search/Commands/RebuildSearchIndex.cs b/ErsatzTV.Application/Search/Commands/RebuildSearchIndex.cs index 22bcef89..dc709272 100644 --- a/ErsatzTV.Application/Search/Commands/RebuildSearchIndex.cs +++ b/ErsatzTV.Application/Search/Commands/RebuildSearchIndex.cs @@ -1,3 +1,3 @@ namespace ErsatzTV.Application.Search; -public record RebuildSearchIndex : IRequest, IBackgroundServiceRequest; +public record RebuildSearchIndex : IRequest, IBackgroundServiceRequest; diff --git a/ErsatzTV.Application/Search/Commands/RebuildSearchIndexHandler.cs b/ErsatzTV.Application/Search/Commands/RebuildSearchIndexHandler.cs index 74abc4b4..6d8059f4 100644 --- a/ErsatzTV.Application/Search/Commands/RebuildSearchIndexHandler.cs +++ b/ErsatzTV.Application/Search/Commands/RebuildSearchIndexHandler.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; namespace ErsatzTV.Application.Search; -public class RebuildSearchIndexHandler : IRequestHandler +public class RebuildSearchIndexHandler : IRequestHandler { private readonly IConfigElementRepository _configElementRepository; private readonly IFallbackMetadataProvider _fallbackMetadataProvider; @@ -35,7 +35,7 @@ public class RebuildSearchIndexHandler : IRequestHandler Handle(RebuildSearchIndex request, CancellationToken cancellationToken) + public async Task Handle(RebuildSearchIndex request, CancellationToken cancellationToken) { _logger.LogInformation("Initializing search index"); @@ -63,7 +63,5 @@ public class RebuildSearchIndexHandler : IRequestHandler MediaItemIds) : IRequest, +public record ReindexMediaItems(IReadOnlyCollection MediaItemIds) : IRequest, ISearchIndexBackgroundServiceRequest; diff --git a/ErsatzTV.Application/Search/Commands/ReindexMediaItemsHandler.cs b/ErsatzTV.Application/Search/Commands/ReindexMediaItemsHandler.cs index 58bc2a3e..aa1a68e6 100644 --- a/ErsatzTV.Application/Search/Commands/ReindexMediaItemsHandler.cs +++ b/ErsatzTV.Application/Search/Commands/ReindexMediaItemsHandler.cs @@ -4,7 +4,7 @@ using ErsatzTV.Core.Interfaces.Search; namespace ErsatzTV.Application.Search; -public class ReindexMediaItemsHandler : IRequestHandler +public class ReindexMediaItemsHandler : IRequestHandler { private readonly ICachingSearchRepository _cachingSearchRepository; private readonly IFallbackMetadataProvider _fallbackMetadataProvider; @@ -20,10 +20,9 @@ public class ReindexMediaItemsHandler : IRequestHandler _searchIndex = searchIndex; } - public async Task Handle(ReindexMediaItems request, CancellationToken cancellationToken) + public async Task Handle(ReindexMediaItems request, CancellationToken cancellationToken) { await _searchIndex.RebuildItems(_cachingSearchRepository, _fallbackMetadataProvider, request.MediaItemIds); _searchIndex.Commit(); - return Unit.Default; } } diff --git a/ErsatzTV.Application/Search/Commands/RemoveMediaItems.cs b/ErsatzTV.Application/Search/Commands/RemoveMediaItems.cs index 340a58c3..fb29a46f 100644 --- a/ErsatzTV.Application/Search/Commands/RemoveMediaItems.cs +++ b/ErsatzTV.Application/Search/Commands/RemoveMediaItems.cs @@ -1,4 +1,4 @@ namespace ErsatzTV.Application.Search; -public record RemoveMediaItems(IReadOnlyCollection MediaItemIds) : IRequest, +public record RemoveMediaItems(IReadOnlyCollection MediaItemIds) : IRequest, ISearchIndexBackgroundServiceRequest; diff --git a/ErsatzTV.Application/Search/Commands/RemoveMediaItemsHandler.cs b/ErsatzTV.Application/Search/Commands/RemoveMediaItemsHandler.cs index 7fc26593..097a4a16 100644 --- a/ErsatzTV.Application/Search/Commands/RemoveMediaItemsHandler.cs +++ b/ErsatzTV.Application/Search/Commands/RemoveMediaItemsHandler.cs @@ -2,16 +2,15 @@ using ErsatzTV.Core.Interfaces.Search; namespace ErsatzTV.Application.Search; -public class RemoveMediaItemsHandler : IRequestHandler +public class RemoveMediaItemsHandler : IRequestHandler { private readonly ISearchIndex _searchIndex; public RemoveMediaItemsHandler(ISearchIndex searchIndex) => _searchIndex = searchIndex; - public async Task Handle(RemoveMediaItems request, CancellationToken cancellationToken) + public async Task Handle(RemoveMediaItems request, CancellationToken cancellationToken) { await _searchIndex.RemoveItems(request.MediaItemIds); _searchIndex.Commit(); - return Unit.Default; } } diff --git a/ErsatzTV.Core/ErsatzTV.Core.csproj b/ErsatzTV.Core/ErsatzTV.Core.csproj index 86cdf35c..492b67b2 100644 --- a/ErsatzTV.Core/ErsatzTV.Core.csproj +++ b/ErsatzTV.Core/ErsatzTV.Core.csproj @@ -12,7 +12,7 @@ - + diff --git a/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs index 66491f44..47adc046 100644 --- a/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs @@ -32,10 +32,16 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities // no hardware decoding of 10-bit h264 VideoFormat.H264 when bitDepth == 10 => false, + VideoFormat.Mpeg2Video => true, + + VideoFormat.Vc1 => true, + + VideoFormat.Mpeg4 => true, + // generated images are decoded into software VideoFormat.GeneratedImage => false, - - _ => true + + _ => false }; } diff --git a/ErsatzTV.FFmpeg/Filter/Cuda/CudaHardwareDownloadFilter.cs b/ErsatzTV.FFmpeg/Filter/Cuda/CudaHardwareDownloadFilter.cs index ce85c9a9..03104c64 100644 --- a/ErsatzTV.FFmpeg/Filter/Cuda/CudaHardwareDownloadFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Cuda/CudaHardwareDownloadFilter.cs @@ -4,16 +4,23 @@ namespace ErsatzTV.FFmpeg.Filter.Cuda; public class CudaHardwareDownloadFilter : BaseFilter { - private readonly Option _maybePixelFormat; + private readonly Option _maybeCurrentPixelFormat; + private readonly Option _maybeTargetPixelFormat; - public CudaHardwareDownloadFilter(Option maybePixelFormat) => _maybePixelFormat = maybePixelFormat; + public CudaHardwareDownloadFilter( + Option maybeCurrentPixelFormat, + Option maybeTargetPixelFormat) + { + _maybeCurrentPixelFormat = maybeCurrentPixelFormat; + _maybeTargetPixelFormat = maybeTargetPixelFormat; + } public override string Filter { get { var hwdownload = "hwdownload"; - foreach (IPixelFormat pixelFormat in _maybePixelFormat) + foreach (IPixelFormat pixelFormat in _maybeCurrentPixelFormat) { if (!string.IsNullOrWhiteSpace(pixelFormat.FFmpegName)) { @@ -21,9 +28,19 @@ public class CudaHardwareDownloadFilter : BaseFilter if (pixelFormat is PixelFormatNv12 nv12) { - foreach (IPixelFormat pf in AvailablePixelFormats.ForPixelFormat(nv12.Name, null)) + if (_maybeTargetPixelFormat.IsNone) + { + foreach (IPixelFormat pf in AvailablePixelFormats.ForPixelFormat(nv12.Name, null)) + { + hwdownload += $",format={pf.FFmpegName}"; + } + } + else { - hwdownload += $",format={pf.FFmpegName}"; + foreach (IPixelFormat pf in _maybeTargetPixelFormat) + { + hwdownload += $",format={pf.FFmpegName}"; + } } } } @@ -39,8 +56,14 @@ public class CudaHardwareDownloadFilter : BaseFilter { FrameDataLocation = FrameDataLocation.Software }; - - foreach (IPixelFormat pixelFormat in _maybePixelFormat) + + foreach (IPixelFormat pixelFormat in _maybeTargetPixelFormat) + { + result = result with { PixelFormat = Some(pixelFormat) }; + return result; + } + + foreach (IPixelFormat pixelFormat in _maybeCurrentPixelFormat) { if (pixelFormat is PixelFormatNv12 nv12) { diff --git a/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs b/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs index cba8db35..3c3fcebd 100644 --- a/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs @@ -21,6 +21,11 @@ public class ScaleFilter : BaseFilter { get { + if (_currentState.ScaledSize == _scaledSize) + { + return string.Empty; + } + string aspectRatio = string.Empty; if (_scaledSize != _paddedSize) { diff --git a/ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs index bed01b17..26a71053 100644 --- a/ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs @@ -92,7 +92,7 @@ public class AmfPipelineBuilder : SoftwarePipelineBuilder { if (!videoStream.ColorParams.IsBt709) { - _logger.LogDebug("Adding colorspace filter"); + // _logger.LogDebug("Adding colorspace filter"); var colorspace = new ColorspaceFilter(currentState, videoStream, pixelFormat); currentState = colorspace.NextState(currentState); result.Add(colorspace); diff --git a/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs index 37c15d8c..fdc5fe08 100644 --- a/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs @@ -80,12 +80,11 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder }; } - protected override void SetDecoder( + protected override Option SetDecoder( VideoInputFile videoInputFile, VideoStream videoStream, FFmpegState ffmpegState, - PipelineContext context, - ICollection pipelineSteps) + PipelineContext context) { Option maybeDecoder = (ffmpegState.DecoderHardwareAccelerationMode, videoStream.Codec) switch { @@ -105,7 +104,10 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder foreach (IDecoder decoder in maybeDecoder) { videoInputFile.AddOption(decoder); + return Some(decoder); } + + return None; } protected override FilterChain SetVideoFilters( @@ -114,6 +116,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder Option watermarkInputFile, Option subtitleInputFile, PipelineContext context, + Option maybeDecoder, FFmpegState ffmpegState, FrameState desiredState, string fontsFolder, @@ -132,10 +135,12 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder : videoStream.PixelFormat, IsAnamorphic = videoStream.IsAnamorphic, - FrameDataLocation = ffmpegState.DecoderHardwareAccelerationMode == HardwareAccelerationMode.Nvenc - ? FrameDataLocation.Hardware - : FrameDataLocation.Software }; + + foreach (IDecoder decoder in maybeDecoder) + { + currentState = decoder.NextState(currentState); + } // if (context.HasSubtitleOverlay || context.HasWatermark) // { @@ -275,7 +280,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder if (!videoStream.ColorParams.IsBt709) { - _logger.LogDebug("Adding colorspace filter"); + // _logger.LogDebug("Adding colorspace filter"); var colorspace = new ColorspaceFilter(currentState, videoStream, format, false); currentState = colorspace.NextState(currentState); @@ -292,11 +297,19 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder _logger.LogDebug( "HasSubtitleOverlay || HasWatermark && FrameDataLocation == FrameDataLocation.Hardware"); - var hardwareDownload = new CudaHardwareDownloadFilter(currentState.PixelFormat); + var hardwareDownload = new CudaHardwareDownloadFilter(currentState.PixelFormat, None); currentState = hardwareDownload.NextState(currentState); result.Add(hardwareDownload); } } + + if (currentState.FrameDataLocation == FrameDataLocation.Hardware && + ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None) + { + var hardwareDownload = new CudaHardwareDownloadFilter(currentState.PixelFormat, Some(format)); + currentState = hardwareDownload.NextState(currentState); + result.Add(hardwareDownload); + } if (currentState.PixelFormat.Map(f => f.FFmpegName) != format.FFmpegName) { @@ -332,14 +345,6 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder pipelineSteps.Add(new PixelFormatOutputOption(format)); } } - - if (currentState.FrameDataLocation == FrameDataLocation.Hardware && - ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None) - { - var hardwareDownload = new CudaHardwareDownloadFilter(Some(format)); - currentState = hardwareDownload.NextState(currentState); - result.Add(hardwareDownload); - } } return result; @@ -398,6 +403,8 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder videoStream.SquarePixelFrameSize(currentState.PaddedSize), _logger); watermarkOverlayFilterSteps.Add(watermarkFilter); + + currentState = watermarkFilter.NextState(currentState); } return currentState; @@ -473,7 +480,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder { if (currentState.FrameDataLocation == FrameDataLocation.Hardware) { - var cudaDownload = new CudaHardwareDownloadFilter(currentState.PixelFormat); + var cudaDownload = new CudaHardwareDownloadFilter(currentState.PixelFormat, None); currentState = cudaDownload.NextState(currentState); videoInputFile.FilterSteps.Add(cudaDownload); } @@ -526,12 +533,20 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder FrameState currentState) { IPipelineFilterStep scaleStep; + + bool needsToScale = currentState.ScaledSize != desiredState.ScaledSize; + if (!needsToScale) + { + return currentState; + } + + bool decodedToSoftware = ffmpegState.DecoderHardwareAccelerationMode == HardwareAccelerationMode.None; + bool softwareEncoder = ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None; + bool noHardwareFilters = context is + { HasWatermark: false, HasSubtitleOverlay: false, ShouldDeinterlace: false }; + bool needsToPad = currentState.PaddedSize != desiredState.PaddedSize; - if (currentState.ScaledSize != desiredState.ScaledSize && ffmpegState is - { - DecoderHardwareAccelerationMode: HardwareAccelerationMode.None, - EncoderHardwareAccelerationMode: HardwareAccelerationMode.None - } && context is { HasWatermark: false, HasSubtitleOverlay: false, ShouldDeinterlace: false }) + if (decodedToSoftware && (needsToPad || noHardwareFilters && softwareEncoder)) { scaleStep = new ScaleFilter( currentState, diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs index 7b3937de..1768f951 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs @@ -395,7 +395,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder ffmpegState = SetAccelState(videoStream, ffmpegState, desiredState, context, pipelineSteps); - SetDecoder(videoInputFile, videoStream, ffmpegState, context, pipelineSteps); + Option maybeDecoder = SetDecoder(videoInputFile, videoStream, ffmpegState, context); SetStillImageInfiniteLoop(videoInputFile, videoStream, ffmpegState); SetRealtimeInput(videoInputFile, desiredState); @@ -411,6 +411,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder _watermarkInputFile, _subtitleInputFile, context, + maybeDecoder, ffmpegState, desiredState, _fontsFolder, @@ -421,12 +422,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder return filterChain; } - protected abstract void SetDecoder( + protected abstract Option SetDecoder( VideoInputFile videoInputFile, VideoStream videoStream, FFmpegState ffmpegState, - PipelineContext context, - ICollection pipelineSteps); + PipelineContext context); protected Option GetSoftwareDecoder(VideoStream videoStream) => videoStream.Codec switch @@ -471,6 +471,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder Option watermarkInputFile, Option subtitleInputFile, PipelineContext context, + Option maybeDecoder, FFmpegState ffmpegState, FrameState desiredState, string fontsFolder, diff --git a/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs index 457198f7..6b697a87 100644 --- a/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs @@ -78,12 +78,11 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder }; } - protected override void SetDecoder( + protected override Option SetDecoder( VideoInputFile videoInputFile, VideoStream videoStream, FFmpegState ffmpegState, - PipelineContext context, - ICollection pipelineSteps) + PipelineContext context) { Option maybeDecoder = (ffmpegState.DecoderHardwareAccelerationMode, videoStream.Codec) switch { @@ -99,7 +98,10 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder foreach (IDecoder decoder in maybeDecoder) { videoInputFile.AddOption(decoder); + return Some(decoder); } + + return None; } protected override FilterChain SetVideoFilters( @@ -108,6 +110,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder Option watermarkInputFile, Option subtitleInputFile, PipelineContext context, + Option maybeDecoder, FFmpegState ffmpegState, FrameState desiredState, string fontsFolder, @@ -126,11 +129,13 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder ? videoStream.PixelFormat.Map(pf => pf.BitDepth == 8 ? new PixelFormatNv12(pf.Name) : pf) : videoStream.PixelFormat, - IsAnamorphic = videoStream.IsAnamorphic, - FrameDataLocation = ffmpegState.DecoderHardwareAccelerationMode == HardwareAccelerationMode.Qsv - ? FrameDataLocation.Hardware - : FrameDataLocation.Software + IsAnamorphic = videoStream.IsAnamorphic }; + + foreach (IDecoder decoder in maybeDecoder) + { + currentState = decoder.NextState(currentState); + } // easier to use nv12 for overlay if (context.HasSubtitleOverlay || context.HasWatermark) @@ -294,7 +299,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder if (!videoStream.ColorParams.IsBt709 || usesVppQsv) { - _logger.LogDebug("Adding colorspace filter"); + // _logger.LogDebug("Adding colorspace filter"); // force p010/nv12 if we're still in hardware if (currentState.FrameDataLocation == FrameDataLocation.Hardware) diff --git a/ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs index e98d48fc..6360000e 100644 --- a/ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs @@ -44,17 +44,19 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase EncoderHardwareAccelerationMode = HardwareAccelerationMode.None }; - protected override void SetDecoder( + protected override Option SetDecoder( VideoInputFile videoInputFile, VideoStream videoStream, FFmpegState ffmpegState, - PipelineContext context, - ICollection pipelineSteps) + PipelineContext context) { foreach (IDecoder decoder in GetSoftwareDecoder(videoStream)) { videoInputFile.AddOption(decoder); + return Some(decoder); } + + return None; } protected virtual Option GetEncoder( @@ -71,6 +73,7 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase Option watermarkInputFile, Option subtitleInputFile, PipelineContext context, + Option maybeDecoder, FFmpegState ffmpegState, FrameState desiredState, string fontsFolder, @@ -87,7 +90,12 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase ScaledSize = videoStream.FrameSize, PaddedSize = videoStream.FrameSize }; - + + foreach (IDecoder decoder in maybeDecoder) + { + currentState = decoder.NextState(currentState); + } + SetDeinterlace(videoInputFile, context, currentState); currentState = SetScale(videoInputFile, videoStream, desiredState, currentState); @@ -139,7 +147,7 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase { if (!videoStream.ColorParams.IsBt709) { - _logger.LogDebug("Adding colorspace filter"); + // _logger.LogDebug("Adding colorspace filter"); var colorspace = new ColorspaceFilter(currentState, videoStream, pixelFormat); currentState = colorspace.NextState(currentState); result.Add(colorspace); diff --git a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs index fb0f6ad3..fc5d5350 100644 --- a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs @@ -85,12 +85,11 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder }; } - protected override void SetDecoder( + protected override Option SetDecoder( VideoInputFile videoInputFile, VideoStream videoStream, FFmpegState ffmpegState, - PipelineContext context, - ICollection pipelineSteps) + PipelineContext context) { Option maybeDecoder = (ffmpegState.DecoderHardwareAccelerationMode, videoStream.Codec) switch { @@ -101,7 +100,10 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder foreach (IDecoder decoder in maybeDecoder) { videoInputFile.AddOption(decoder); + return Some(decoder); } + + return None; } protected override FilterChain SetVideoFilters( @@ -110,6 +112,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder Option watermarkInputFile, Option subtitleInputFile, PipelineContext context, + Option maybeDecoder, FFmpegState ffmpegState, FrameState desiredState, string fontsFolder, @@ -122,15 +125,14 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder { ScaledSize = videoStream.FrameSize, PaddedSize = videoStream.FrameSize, - PixelFormat = videoStream.PixelFormat, - IsAnamorphic = videoStream.IsAnamorphic, - - FrameDataLocation = ffmpegState.DecoderHardwareAccelerationMode == HardwareAccelerationMode.Vaapi - ? FrameDataLocation.Hardware - : FrameDataLocation.Software }; + + foreach (IDecoder decoder in maybeDecoder) + { + currentState = decoder.NextState(currentState); + } // easier to use nv12 for overlay if (context.HasSubtitleOverlay || context.HasWatermark) @@ -246,7 +248,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder if (!videoStream.ColorParams.IsBt709) { - _logger.LogDebug("Adding colorspace filter"); + // _logger.LogDebug("Adding colorspace filter"); var colorspace = new ColorspaceFilter( currentState, videoStream, diff --git a/ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs index d51889cd..80077e33 100644 --- a/ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs @@ -68,12 +68,11 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder }; } - protected override void SetDecoder( + protected override Option SetDecoder( VideoInputFile videoInputFile, VideoStream videoStream, FFmpegState ffmpegState, - PipelineContext context, - ICollection pipelineSteps) + PipelineContext context) { Option maybeDecoder = (ffmpegState.DecoderHardwareAccelerationMode, videoStream.Codec) switch { @@ -85,7 +84,10 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder foreach (IDecoder decoder in maybeDecoder) { videoInputFile.AddOption(decoder); + return Some(decoder); } + + return None; } protected override Option GetEncoder(FFmpegState ffmpegState, FrameState currentState, FrameState desiredState) @@ -113,7 +115,7 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder { if (!videoStream.ColorParams.IsBt709) { - _logger.LogDebug("Adding colorspace filter"); + // _logger.LogDebug("Adding colorspace filter"); var colorspace = new ColorspaceFilter(currentState, videoStream, pixelFormat); currentState = colorspace.NextState(currentState); result.Add(colorspace); diff --git a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj index e2eb0814..d20709f5 100644 --- a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj +++ b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj @@ -15,12 +15,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs index b809a376..ccb839e6 100644 --- a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs @@ -125,38 +125,38 @@ public class TranscodingTests public static VideoScanKind[] VideoScanKinds = { VideoScanKind.Progressive, - // VideoScanKind.Interlaced + VideoScanKind.Interlaced }; public static InputFormat[] InputFormats = { // // // example format that requires colorspace filter - // new("libx264", "yuv420p", "tv", "smpte170m", "bt709", "smpte170m"), + new("libx264", "yuv420p", "tv", "smpte170m", "bt709", "smpte170m"), // // // // // example format that requires setparams filter - // new("libx264", "yuv420p", string.Empty, string.Empty, string.Empty, string.Empty), + new("libx264", "yuv420p", string.Empty, string.Empty, string.Empty, string.Empty), // // // // // new("libx264", "yuvj420p"), - // new("libx264", "yuv420p10le"), + new("libx264", "yuv420p10le"), // // // new("libx264", "yuv444p10le"), // // // // // new("mpeg1video", "yuv420p"), // // // - // // // new("mpeg2video", "yuv420p"), + new("mpeg2video", "yuv420p"), // // - // new("libx265", "yuv420p"), - // new("libx265", "yuv420p10le"), + new("libx265", "yuv420p"), + new("libx265", "yuv420p10le"), // - // // new("mpeg4", "yuv420p"), - // // - new("libvpx-vp9", "yuv420p"), - new("libvpx-vp9", "yuv420p10le"), + // new("mpeg4", "yuv420p"), + // + // new("libvpx-vp9", "yuv420p"), + // new("libvpx-vp9", "yuv420p10le"), // // // // new("libaom-av1", "yuv420p") // // // av1 yuv420p10le 51 // // - // // new("msmpeg4v2", "yuv420p"), - // // new("msmpeg4v3", "yuv420p") + // new("msmpeg4v2", "yuv420p"), + new("msmpeg4v3", "yuv420p") // // // wmv3 yuv420p 1 }; diff --git a/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj b/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj index b8dad3f4..c398a182 100644 --- a/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj +++ b/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj @@ -18,8 +18,7 @@ - - + diff --git a/ErsatzTV.Scanner/Program.cs b/ErsatzTV.Scanner/Program.cs index 842bc4ef..9a121264 100644 --- a/ErsatzTV.Scanner/Program.cs +++ b/ErsatzTV.Scanner/Program.cs @@ -166,8 +166,8 @@ public class Program services.AddSingleton(); // TODO: real bugsnag? services.AddSingleton(_ => new BugsnagNoopClient()); - - services.AddMediatR(typeof(Worker).Assembly); + + services.AddMediatR(config => config.RegisterServicesFromAssemblyContaining()); services.AddMemoryCache(); services.AddHostedService(); diff --git a/ErsatzTV/ErsatzTV.csproj b/ErsatzTV/ErsatzTV.csproj index 1f32155b..f361d5be 100644 --- a/ErsatzTV/ErsatzTV.csproj +++ b/ErsatzTV/ErsatzTV.csproj @@ -54,17 +54,16 @@ - + - - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -72,7 +71,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/ErsatzTV/Startup.cs b/ErsatzTV/Startup.cs index 6c1f682b..3e737421 100644 --- a/ErsatzTV/Startup.cs +++ b/ErsatzTV/Startup.cs @@ -58,7 +58,6 @@ using ErsatzTV.Services.RunOnce; using FluentValidation; using FluentValidation.AspNetCore; using Ganss.Xss; -using MediatR; using MediatR.Courier.DependencyInjection; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; @@ -329,7 +328,7 @@ public class Startup SqlMapper.AddTypeHandler(new GuidHandler()); SqlMapper.AddTypeHandler(new TimeSpanHandler()); - services.AddMediatR(typeof(GetAllChannels).Assembly); + services.AddMediatR(config => config.RegisterServicesFromAssemblyContaining()); services.AddRefitClient() .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://plex.tv/api/v2"));