Browse Source

software decoder fixes (#1169)

* fix software decoder pipeline bugs

* tweak nvidia scaling logic

* update changelog

* update dependencies
pull/1171/head
Jason Dove 2 years ago committed by GitHub
parent
commit
1afff11063
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      CHANGELOG.md
  2. 2
      ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKey.cs
  3. 5
      ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKeyHandler.cs
  4. 2
      ErsatzTV.Application/ErsatzTV.Application.csproj
  5. 2
      ErsatzTV.Application/Maintenance/Commands/ReleaseMemory.cs
  6. 8
      ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs
  7. 2
      ErsatzTV.Application/Search/Commands/RebuildSearchIndex.cs
  8. 6
      ErsatzTV.Application/Search/Commands/RebuildSearchIndexHandler.cs
  9. 2
      ErsatzTV.Application/Search/Commands/ReindexMediaItems.cs
  10. 5
      ErsatzTV.Application/Search/Commands/ReindexMediaItemsHandler.cs
  11. 2
      ErsatzTV.Application/Search/Commands/RemoveMediaItems.cs
  12. 5
      ErsatzTV.Application/Search/Commands/RemoveMediaItemsHandler.cs
  13. 2
      ErsatzTV.Core/ErsatzTV.Core.csproj
  14. 10
      ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs
  15. 37
      ErsatzTV.FFmpeg/Filter/Cuda/CudaHardwareDownloadFilter.cs
  16. 5
      ErsatzTV.FFmpeg/Filter/ScaleFilter.cs
  17. 2
      ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs
  18. 59
      ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs
  19. 9
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs
  20. 21
      ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs
  21. 18
      ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs
  22. 22
      ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs
  23. 10
      ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs
  24. 6
      ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj
  25. 26
      ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs
  26. 3
      ErsatzTV.Scanner/ErsatzTV.Scanner.csproj
  27. 4
      ErsatzTV.Scanner/Program.cs
  28. 13
      ErsatzTV/ErsatzTV.csproj
  29. 3
      ErsatzTV/Startup.cs

4
CHANGELOG.md

@ -6,9 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -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

2
ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKey.cs

@ -2,4 +2,4 @@ @@ -2,4 +2,4 @@
namespace ErsatzTV.Application.Configuration;
public record SaveConfigElementByKey(ConfigElementKey Key, string Value) : IRequest<Unit>;
public record SaveConfigElementByKey(ConfigElementKey Key, string Value) : IRequest;

5
ErsatzTV.Application/Configuration/Commands/SaveConfigElementByKeyHandler.cs

@ -2,16 +2,15 @@ @@ -2,16 +2,15 @@
namespace ErsatzTV.Application.Configuration;
public class SaveConfigElementByKeyHandler : IRequestHandler<SaveConfigElementByKey, Unit>
public class SaveConfigElementByKeyHandler : IRequestHandler<SaveConfigElementByKey>
{
private readonly IConfigElementRepository _configElementRepository;
public SaveConfigElementByKeyHandler(IConfigElementRepository configElementRepository) =>
_configElementRepository = configElementRepository;
public async Task<Unit> Handle(SaveConfigElementByKey request, CancellationToken cancellationToken)
public async Task Handle(SaveConfigElementByKey request, CancellationToken cancellationToken)
{
await _configElementRepository.Upsert(request.Key, request.Value);
return Unit.Default;
}
}

2
ErsatzTV.Application/ErsatzTV.Application.csproj

@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
<PackageReference Include="Bugsnag" Version="3.1.0" />
<PackageReference Include="CliWrap" Version="3.6.0" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="MediatR" Version="11.1.0" />
<PackageReference Include="MediatR" Version="12.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.5.22">
<PrivateAssets>all</PrivateAssets>

2
ErsatzTV.Application/Maintenance/Commands/ReleaseMemory.cs

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
namespace ErsatzTV.Application.Maintenance;
public record ReleaseMemory(bool ForceAggressive) : IRequest<Unit>, IBackgroundServiceRequest
public record ReleaseMemory(bool ForceAggressive) : IRequest, IBackgroundServiceRequest
{
public DateTimeOffset RequestTime = DateTimeOffset.Now;
}

8
ErsatzTV.Application/Maintenance/Commands/ReleaseMemoryHandler.cs

@ -4,7 +4,7 @@ using Microsoft.Extensions.Logging; @@ -4,7 +4,7 @@ using Microsoft.Extensions.Logging;
namespace ErsatzTV.Application.Maintenance;
public class ReleaseMemoryHandler : IRequestHandler<ReleaseMemory, Unit>
public class ReleaseMemoryHandler : IRequestHandler<ReleaseMemory>
{
private static long _lastRelease;
@ -19,12 +19,12 @@ public class ReleaseMemoryHandler : IRequestHandler<ReleaseMemory, Unit> @@ -19,12 +19,12 @@ public class ReleaseMemoryHandler : IRequestHandler<ReleaseMemory, Unit>
_logger = logger;
}
public Task<Unit> 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<ReleaseMemory, Unit> @@ -45,6 +45,6 @@ public class ReleaseMemoryHandler : IRequestHandler<ReleaseMemory, Unit>
_logger.LogDebug("Completed garbage collection");
Interlocked.Exchange(ref _lastRelease, DateTimeOffset.Now.Ticks);
return Task.FromResult(Unit.Default);
return Task.CompletedTask;
}
}

2
ErsatzTV.Application/Search/Commands/RebuildSearchIndex.cs

@ -1,3 +1,3 @@ @@ -1,3 +1,3 @@
namespace ErsatzTV.Application.Search;
public record RebuildSearchIndex : IRequest<Unit>, IBackgroundServiceRequest;
public record RebuildSearchIndex : IRequest, IBackgroundServiceRequest;

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

@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace ErsatzTV.Application.Search;
public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Unit>
public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex>
{
private readonly IConfigElementRepository _configElementRepository;
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
@ -35,7 +35,7 @@ public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Uni @@ -35,7 +35,7 @@ public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Uni
_fallbackMetadataProvider = fallbackMetadataProvider;
}
public async Task<Unit> 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<RebuildSearchIndex, Uni @@ -63,7 +63,5 @@ public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Uni
{
_logger.LogInformation("Search index is already version {Version}", _searchIndex.Version);
}
return Unit.Default;
}
}

2
ErsatzTV.Application/Search/Commands/ReindexMediaItems.cs

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
namespace ErsatzTV.Application.Search;
public record ReindexMediaItems(IReadOnlyCollection<int> MediaItemIds) : IRequest<Unit>,
public record ReindexMediaItems(IReadOnlyCollection<int> MediaItemIds) : IRequest,
ISearchIndexBackgroundServiceRequest;

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

@ -4,7 +4,7 @@ using ErsatzTV.Core.Interfaces.Search; @@ -4,7 +4,7 @@ using ErsatzTV.Core.Interfaces.Search;
namespace ErsatzTV.Application.Search;
public class ReindexMediaItemsHandler : IRequestHandler<ReindexMediaItems, Unit>
public class ReindexMediaItemsHandler : IRequestHandler<ReindexMediaItems>
{
private readonly ICachingSearchRepository _cachingSearchRepository;
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
@ -20,10 +20,9 @@ public class ReindexMediaItemsHandler : IRequestHandler<ReindexMediaItems, Unit> @@ -20,10 +20,9 @@ public class ReindexMediaItemsHandler : IRequestHandler<ReindexMediaItems, Unit>
_searchIndex = searchIndex;
}
public async Task<Unit> 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;
}
}

2
ErsatzTV.Application/Search/Commands/RemoveMediaItems.cs

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
namespace ErsatzTV.Application.Search;
public record RemoveMediaItems(IReadOnlyCollection<int> MediaItemIds) : IRequest<Unit>,
public record RemoveMediaItems(IReadOnlyCollection<int> MediaItemIds) : IRequest,
ISearchIndexBackgroundServiceRequest;

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

@ -2,16 +2,15 @@ using ErsatzTV.Core.Interfaces.Search; @@ -2,16 +2,15 @@ using ErsatzTV.Core.Interfaces.Search;
namespace ErsatzTV.Application.Search;
public class RemoveMediaItemsHandler : IRequestHandler<RemoveMediaItems, Unit>
public class RemoveMediaItemsHandler : IRequestHandler<RemoveMediaItems>
{
private readonly ISearchIndex _searchIndex;
public RemoveMediaItemsHandler(ISearchIndex searchIndex) => _searchIndex = searchIndex;
public async Task<Unit> Handle(RemoveMediaItems request, CancellationToken cancellationToken)
public async Task Handle(RemoveMediaItems request, CancellationToken cancellationToken)
{
await _searchIndex.RemoveItems(request.MediaItemIds);
_searchIndex.Commit();
return Unit.Default;
}
}

2
ErsatzTV.Core/ErsatzTV.Core.csproj

@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
<PackageReference Include="Flurl" Version="3.0.7" />
<PackageReference Include="LanguageExt.Core" Version="4.4.2" />
<PackageReference Include="LanguageExt.Transformers" Version="4.4.2" />
<PackageReference Include="MediatR" Version="11.1.0" />
<PackageReference Include="MediatR" Version="12.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />

10
ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs

@ -32,10 +32,16 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities @@ -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
};
}

37
ErsatzTV.FFmpeg/Filter/Cuda/CudaHardwareDownloadFilter.cs

@ -4,16 +4,23 @@ namespace ErsatzTV.FFmpeg.Filter.Cuda; @@ -4,16 +4,23 @@ namespace ErsatzTV.FFmpeg.Filter.Cuda;
public class CudaHardwareDownloadFilter : BaseFilter
{
private readonly Option<IPixelFormat> _maybePixelFormat;
private readonly Option<IPixelFormat> _maybeCurrentPixelFormat;
private readonly Option<IPixelFormat> _maybeTargetPixelFormat;
public CudaHardwareDownloadFilter(Option<IPixelFormat> maybePixelFormat) => _maybePixelFormat = maybePixelFormat;
public CudaHardwareDownloadFilter(
Option<IPixelFormat> maybeCurrentPixelFormat,
Option<IPixelFormat> 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 @@ -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 @@ -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)
{

5
ErsatzTV.FFmpeg/Filter/ScaleFilter.cs

@ -21,6 +21,11 @@ public class ScaleFilter : BaseFilter @@ -21,6 +21,11 @@ public class ScaleFilter : BaseFilter
{
get
{
if (_currentState.ScaledSize == _scaledSize)
{
return string.Empty;
}
string aspectRatio = string.Empty;
if (_scaledSize != _paddedSize)
{

2
ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs

@ -92,7 +92,7 @@ public class AmfPipelineBuilder : SoftwarePipelineBuilder @@ -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);

59
ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs

@ -80,12 +80,11 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder @@ -80,12 +80,11 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
};
}
protected override void SetDecoder(
protected override Option<IDecoder> SetDecoder(
VideoInputFile videoInputFile,
VideoStream videoStream,
FFmpegState ffmpegState,
PipelineContext context,
ICollection<IPipelineStep> pipelineSteps)
PipelineContext context)
{
Option<IDecoder> maybeDecoder = (ffmpegState.DecoderHardwareAccelerationMode, videoStream.Codec) switch
{
@ -105,7 +104,10 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder @@ -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 @@ -114,6 +116,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
Option<WatermarkInputFile> watermarkInputFile,
Option<SubtitleInputFile> subtitleInputFile,
PipelineContext context,
Option<IDecoder> maybeDecoder,
FFmpegState ffmpegState,
FrameState desiredState,
string fontsFolder,
@ -132,10 +135,12 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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,

9
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -395,7 +395,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -395,7 +395,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
ffmpegState = SetAccelState(videoStream, ffmpegState, desiredState, context, pipelineSteps);
SetDecoder(videoInputFile, videoStream, ffmpegState, context, pipelineSteps);
Option<IDecoder> maybeDecoder = SetDecoder(videoInputFile, videoStream, ffmpegState, context);
SetStillImageInfiniteLoop(videoInputFile, videoStream, ffmpegState);
SetRealtimeInput(videoInputFile, desiredState);
@ -411,6 +411,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -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 @@ -421,12 +422,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
return filterChain;
}
protected abstract void SetDecoder(
protected abstract Option<IDecoder> SetDecoder(
VideoInputFile videoInputFile,
VideoStream videoStream,
FFmpegState ffmpegState,
PipelineContext context,
ICollection<IPipelineStep> pipelineSteps);
PipelineContext context);
protected Option<IDecoder> GetSoftwareDecoder(VideoStream videoStream) =>
videoStream.Codec switch
@ -471,6 +471,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -471,6 +471,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
Option<WatermarkInputFile> watermarkInputFile,
Option<SubtitleInputFile> subtitleInputFile,
PipelineContext context,
Option<IDecoder> maybeDecoder,
FFmpegState ffmpegState,
FrameState desiredState,
string fontsFolder,

21
ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs

@ -78,12 +78,11 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder @@ -78,12 +78,11 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
};
}
protected override void SetDecoder(
protected override Option<IDecoder> SetDecoder(
VideoInputFile videoInputFile,
VideoStream videoStream,
FFmpegState ffmpegState,
PipelineContext context,
ICollection<IPipelineStep> pipelineSteps)
PipelineContext context)
{
Option<IDecoder> maybeDecoder = (ffmpegState.DecoderHardwareAccelerationMode, videoStream.Codec) switch
{
@ -99,7 +98,10 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder @@ -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 @@ -108,6 +110,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
Option<WatermarkInputFile> watermarkInputFile,
Option<SubtitleInputFile> subtitleInputFile,
PipelineContext context,
Option<IDecoder> maybeDecoder,
FFmpegState ffmpegState,
FrameState desiredState,
string fontsFolder,
@ -126,11 +129,13 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder @@ -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 @@ -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)

18
ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs

@ -44,17 +44,19 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase @@ -44,17 +44,19 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase
EncoderHardwareAccelerationMode = HardwareAccelerationMode.None
};
protected override void SetDecoder(
protected override Option<IDecoder> SetDecoder(
VideoInputFile videoInputFile,
VideoStream videoStream,
FFmpegState ffmpegState,
PipelineContext context,
ICollection<IPipelineStep> pipelineSteps)
PipelineContext context)
{
foreach (IDecoder decoder in GetSoftwareDecoder(videoStream))
{
videoInputFile.AddOption(decoder);
return Some(decoder);
}
return None;
}
protected virtual Option<IEncoder> GetEncoder(
@ -71,6 +73,7 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase @@ -71,6 +73,7 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase
Option<WatermarkInputFile> watermarkInputFile,
Option<SubtitleInputFile> subtitleInputFile,
PipelineContext context,
Option<IDecoder> maybeDecoder,
FFmpegState ffmpegState,
FrameState desiredState,
string fontsFolder,
@ -87,7 +90,12 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase @@ -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 @@ -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);

22
ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs

@ -85,12 +85,11 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder @@ -85,12 +85,11 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
};
}
protected override void SetDecoder(
protected override Option<IDecoder> SetDecoder(
VideoInputFile videoInputFile,
VideoStream videoStream,
FFmpegState ffmpegState,
PipelineContext context,
ICollection<IPipelineStep> pipelineSteps)
PipelineContext context)
{
Option<IDecoder> maybeDecoder = (ffmpegState.DecoderHardwareAccelerationMode, videoStream.Codec) switch
{
@ -101,7 +100,10 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder @@ -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 @@ -110,6 +112,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
Option<WatermarkInputFile> watermarkInputFile,
Option<SubtitleInputFile> subtitleInputFile,
PipelineContext context,
Option<IDecoder> maybeDecoder,
FFmpegState ffmpegState,
FrameState desiredState,
string fontsFolder,
@ -122,15 +125,14 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder @@ -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 @@ -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,

10
ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs

@ -68,12 +68,11 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder @@ -68,12 +68,11 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder
};
}
protected override void SetDecoder(
protected override Option<IDecoder> SetDecoder(
VideoInputFile videoInputFile,
VideoStream videoStream,
FFmpegState ffmpegState,
PipelineContext context,
ICollection<IPipelineStep> pipelineSteps)
PipelineContext context)
{
Option<IDecoder> maybeDecoder = (ffmpegState.DecoderHardwareAccelerationMode, videoStream.Codec) switch
{
@ -85,7 +84,10 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder @@ -85,7 +84,10 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder
foreach (IDecoder decoder in maybeDecoder)
{
videoInputFile.AddOption(decoder);
return Some(decoder);
}
return None;
}
protected override Option<IEncoder> GetEncoder(FFmpegState ffmpegState, FrameState currentState, FrameState desiredState)
@ -113,7 +115,7 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder @@ -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);

6
ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj

@ -15,12 +15,12 @@ @@ -15,12 +15,12 @@
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.2">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.3" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.5.22">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

26
ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs

@ -125,38 +125,38 @@ public class TranscodingTests @@ -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
};

3
ErsatzTV.Scanner/ErsatzTV.Scanner.csproj

@ -18,8 +18,7 @@ @@ -18,8 +18,7 @@
<PackageReference Include="CliWrap" Version="3.6.0" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="LanguageExt.Core" Version="4.4.2" />
<PackageReference Include="MediatR" Version="11.1.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="MediatR" Version="12.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="Serilog" Version="2.12.0" />

4
ErsatzTV.Scanner/Program.cs

@ -166,8 +166,8 @@ public class Program @@ -166,8 +166,8 @@ public class Program
services.AddSingleton<RecyclableMemoryStreamManager>();
// TODO: real bugsnag?
services.AddSingleton<IClient>(_ => new BugsnagNoopClient());
services.AddMediatR(typeof(Worker).Assembly);
services.AddMediatR(config => config.RegisterServicesFromAssemblyContaining<Worker>());
services.AddMemoryCache();
services.AddHostedService<Worker>();

13
ErsatzTV/ErsatzTV.csproj

@ -54,17 +54,16 @@ @@ -54,17 +54,16 @@
<ItemGroup>
<PackageReference Include="Bugsnag.AspNet.Core" Version="3.1.0" />
<PackageReference Include="FluentValidation" Version="11.4.0" />
<PackageReference Include="FluentValidation" Version="11.5.1" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.2.2" />
<PackageReference Include="HtmlSanitizer" Version="8.0.645" />
<PackageReference Include="LanguageExt.Core" Version="4.4.2" />
<PackageReference Include="Markdig" Version="0.30.4" />
<PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.2" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.2">
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.3" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="7.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@ -72,7 +71,7 @@ @@ -72,7 +71,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MudBlazor" Version="6.1.8" />
<PackageReference Include="MudBlazor" Version="6.1.9" />
<PackageReference Include="NaturalSort.Extension" Version="4.0.0" />
<PackageReference Include="PPioli.FluentValidation.Blazor" Version="11.1.0" />
<PackageReference Include="Refit.HttpClientFactory" Version="6.3.2" />

3
ErsatzTV/Startup.cs

@ -58,7 +58,6 @@ using ErsatzTV.Services.RunOnce; @@ -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 @@ -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<GetAllChannels>());
services.AddRefitClient<IPlexTvApi>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://plex.tv/api/v2"));

Loading…
Cancel
Save