Browse Source

qsv and vaapi fixes (#1139)

* lots of qsv fixes

* update changelog

* fix qsv mpeg2

* vaapi fixes

* update changelog

* upgrade mudblazor

* fix bug with undefined input colorspace
pull/1140/head
Jason Dove 4 years ago committed by GitHub
parent
commit
2689a67eb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      CHANGELOG.md
  2. 7
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
  3. 12
      ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj
  4. 4
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
  5. 13
      ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs
  6. 4
      ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs
  7. 7
      ErsatzTV.FFmpeg/Decoder/DecoderVaapi.cs
  8. 21
      ErsatzTV.FFmpeg/Decoder/Qsv/DecoderHevcQsv.cs
  9. 21
      ErsatzTV.FFmpeg/Decoder/Qsv/DecoderVp9Qsv.cs
  10. 18
      ErsatzTV.FFmpeg/Encoder/Qsv/EncoderMpeg2Qsv.cs
  11. 14
      ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderMpeg2Vaapi.cs
  12. 5
      ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs
  13. 8
      ErsatzTV.FFmpeg/Format/PixelFormatP010.cs
  14. 12
      ErsatzTV.FFmpeg/Format/PixelFormatVaapi.cs
  15. 130
      ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs
  16. 1
      ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs
  17. 76
      ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs
  18. 9
      ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj
  19. 0
      ErsatzTV.Scanner.Tests/Resources/ErsatzTV.png
  20. 0
      ErsatzTV.Scanner.Tests/Resources/test.srt
  21. 0
      ErsatzTV.Scanner.Tests/Resources/test.sup
  22. 2
      ErsatzTV/ErsatzTV.csproj
  23. 6
      ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs

4
CHANGELOG.md

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Fixed
- Fix many QSV pipeline bugs
- Fix MPEG2 video format with QSV and VAAPI acceleration
- Fix playback of content with undefined colorspace
## [0.7.3-beta] - 2023-01-25 ## [0.7.3-beta] - 2023-01-25
### Added ### Added

7
ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs

@ -36,7 +36,12 @@ public class
p.QsvExtraHardwareFrames = update.QsvExtraHardwareFrames; p.QsvExtraHardwareFrames = update.QsvExtraHardwareFrames;
p.ResolutionId = update.ResolutionId; p.ResolutionId = update.ResolutionId;
p.VideoFormat = update.VideoFormat; p.VideoFormat = update.VideoFormat;
p.BitDepth = update.BitDepth;
// mpeg2video only supports 8-bit content
p.BitDepth = update.VideoFormat == FFmpegProfileVideoFormat.Mpeg2Video
? FFmpegProfileBitDepth.EightBit
: update.BitDepth;
p.VideoBitrate = update.VideoBitrate; p.VideoBitrate = update.VideoBitrate;
p.VideoBufferSize = update.VideoBufferSize; p.VideoBufferSize = update.VideoBufferSize;
p.AudioFormat = update.AudioFormat; p.AudioFormat = update.AudioFormat;

12
ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj

@ -34,16 +34,4 @@
<ProjectReference Include="..\ErsatzTV.Infrastructure\ErsatzTV.Infrastructure.csproj" /> <ProjectReference Include="..\ErsatzTV.Infrastructure\ErsatzTV.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="Resources\ErsatzTV.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Resources\test.sup">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Resources\test.srt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project> </Project>

4
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs

@ -141,9 +141,9 @@ public class FFmpegPlaybackSettingsCalculator
result.PixelFormat = ffmpegProfile.BitDepth switch result.PixelFormat = ffmpegProfile.BitDepth switch
{ {
FFmpegProfileBitDepth.TenBit => new PixelFormatYuv420P10Le(), FFmpegProfileBitDepth.TenBit when ffmpegProfile.VideoFormat != FFmpegProfileVideoFormat.Mpeg2Video
=> new PixelFormatYuv420P10Le(),
_ => new PixelFormatYuv420P() _ => new PixelFormatYuv420P()
// _ => new PixelFormatYuv420P10Le()
}; };
result.AudioFormat = ffmpegProfile.AudioFormat; result.AudioFormat = ffmpegProfile.AudioFormat;

13
ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs

@ -4,7 +4,18 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public class DefaultHardwareCapabilities : IHardwareCapabilities public class DefaultHardwareCapabilities : IHardwareCapabilities
{ {
public bool CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) => true; public bool CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat)
{
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
return (videoFormat, bitDepth) switch
{
// 10-bit h264 decoding is likely not support by any hardware
(VideoFormat.H264, 10) => false,
_ => true
};
}
public bool CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) public bool CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat)
{ {

4
ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs

@ -107,6 +107,10 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
_profileEntrypoints.Contains( _profileEntrypoints.Contains(
new VaapiProfileEntrypoint(VaapiProfile.HevcMain, VaapiEntrypoint.Encode)), new VaapiProfileEntrypoint(VaapiProfile.HevcMain, VaapiEntrypoint.Encode)),
VideoFormat.Mpeg2Video =>
_profileEntrypoints.Contains(
new VaapiProfileEntrypoint(VaapiProfile.Mpeg2Main, VaapiEntrypoint.Encode)),
_ => false _ => false
}; };

7
ErsatzTV.FFmpeg/Decoder/DecoderVaapi.cs

@ -16,7 +16,12 @@ public class DecoderVaapi : DecoderBase
FrameState nextState = base.NextState(currentState); FrameState nextState = base.NextState(currentState);
return currentState.PixelFormat.Match( return currentState.PixelFormat.Match(
pixelFormat => nextState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) }, pixelFormat =>
{
return pixelFormat.BitDepth == 8
? nextState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) }
: nextState with { PixelFormat = new PixelFormatVaapi(pixelFormat.Name) };
},
() => nextState); () => nextState);
} }
} }

21
ErsatzTV.FFmpeg/Decoder/Qsv/DecoderHevcQsv.cs

@ -1,8 +1,27 @@
namespace ErsatzTV.FFmpeg.Decoder.Qsv; using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Decoder.Qsv;
public class DecoderHevcQsv : DecoderBase public class DecoderHevcQsv : DecoderBase
{ {
public override string Name => "hevc_qsv"; public override string Name => "hevc_qsv";
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware; protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
public override FrameState NextState(FrameState currentState)
{
FrameState nextState = base.NextState(currentState);
return currentState.PixelFormat.Match(
pixelFormat =>
{
if (pixelFormat.BitDepth == 10)
{
return nextState with { PixelFormat = new PixelFormatP010() };
}
return nextState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) };
},
() => nextState);
}
} }

21
ErsatzTV.FFmpeg/Decoder/Qsv/DecoderVp9Qsv.cs

@ -1,8 +1,27 @@
namespace ErsatzTV.FFmpeg.Decoder.Qsv; using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Decoder.Qsv;
public class DecoderVp9Qsv : DecoderBase public class DecoderVp9Qsv : DecoderBase
{ {
public override string Name => "vp9_qsv"; public override string Name => "vp9_qsv";
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware; protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
public override FrameState NextState(FrameState currentState)
{
FrameState nextState = base.NextState(currentState);
return currentState.PixelFormat.Match(
pixelFormat =>
{
if (pixelFormat.BitDepth == 10)
{
return nextState with { PixelFormat = new PixelFormatP010() };
}
return nextState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) };
},
() => nextState);
}
} }

18
ErsatzTV.FFmpeg/Encoder/Qsv/EncoderMpeg2Qsv.cs

@ -0,0 +1,18 @@
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Encoder.Qsv;
public class EncoderMpeg2Qsv : EncoderBase
{
public override string Name => "mpeg2_qsv";
public override StreamKind Kind => StreamKind.Video;
public override IList<string> OutputOptions =>
new[] { "-c:v", "mpeg2_qsv", "-low_power", "0", "-look_ahead", "0" };
public override FrameState NextState(FrameState currentState) => currentState with
{
VideoFormat = VideoFormat.Mpeg2Video,
FrameDataLocation = FrameDataLocation.Hardware
};
}

14
ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderMpeg2Vaapi.cs

@ -0,0 +1,14 @@
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Encoder.Vaapi;
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
// don't change the frame data location
};
}

5
ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs

@ -65,9 +65,12 @@ public class ColorspaceFilter : BaseFilter
string primaries = string.IsNullOrWhiteSpace(cp.ColorPrimaries) string primaries = string.IsNullOrWhiteSpace(cp.ColorPrimaries)
? "bt709" ? "bt709"
: cp.ColorPrimaries; : cp.ColorPrimaries;
string space = string.IsNullOrWhiteSpace(cp.ColorSpace)
? "bt709"
: cp.ColorSpace;
inputOverrides = inputOverrides =
$"irange={range}:ispace={cp.ColorSpace}:itrc={transfer}:iprimaries={primaries}:"; $"irange={range}:ispace={space}:itrc={transfer}:iprimaries={primaries}:";
} }
string colorspace = _desiredPixelFormat.BitDepth switch string colorspace = _desiredPixelFormat.BitDepth switch

8
ErsatzTV.FFmpeg/Format/PixelFormatP010.cs

@ -0,0 +1,8 @@
namespace ErsatzTV.FFmpeg.Format;
public class PixelFormatP010 : IPixelFormat
{
public string Name => "p010le";
public string FFmpegName => "p010";
public int BitDepth => 10;
}

12
ErsatzTV.FFmpeg/Format/PixelFormatVaapi.cs

@ -0,0 +1,12 @@
namespace ErsatzTV.FFmpeg.Format;
public class PixelFormatVaapi : IPixelFormat
{
public PixelFormatVaapi(string name) => Name = name;
public string Name { get; }
public string FFmpegName => "vaapi";
public int BitDepth => 8;
}

130
ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs

@ -121,9 +121,9 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
ScaledSize = videoStream.FrameSize, ScaledSize = videoStream.FrameSize,
PaddedSize = videoStream.FrameSize, PaddedSize = videoStream.FrameSize,
// consider hardware frames to be wrapped in nv12 // consider 8-bit hardware frames to be wrapped in nv12
PixelFormat = ffmpegState.DecoderHardwareAccelerationMode == HardwareAccelerationMode.Qsv PixelFormat = ffmpegState.DecoderHardwareAccelerationMode == HardwareAccelerationMode.Qsv
? videoStream.PixelFormat.Map(pf => (IPixelFormat)new PixelFormatNv12(pf.Name)) ? videoStream.PixelFormat.Map(pf => pf.BitDepth == 8 ? new PixelFormatNv12(pf.Name) : pf)
: videoStream.PixelFormat, : videoStream.PixelFormat,
IsAnamorphic = videoStream.IsAnamorphic, IsAnamorphic = videoStream.IsAnamorphic,
@ -137,7 +137,10 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
{ {
IPixelFormat pixelFormat = desiredState.PixelFormat.IfNone( IPixelFormat pixelFormat = desiredState.PixelFormat.IfNone(
context.Is10BitOutput ? new PixelFormatYuv420P10Le() : new PixelFormatYuv420P()); context.Is10BitOutput ? new PixelFormatYuv420P10Le() : new PixelFormatYuv420P());
desiredState = desiredState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) }; desiredState = desiredState with
{
PixelFormat = Some(context.Is10BitOutput ? pixelFormat : new PixelFormatNv12(pixelFormat.Name))
};
} }
// _logger.LogDebug("After decode: {PixelFormat}", currentState.PixelFormat); // _logger.LogDebug("After decode: {PixelFormat}", currentState.PixelFormat);
@ -184,6 +187,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
{ {
(HardwareAccelerationMode.Qsv, VideoFormat.Hevc) => new EncoderHevcQsv(), (HardwareAccelerationMode.Qsv, VideoFormat.Hevc) => new EncoderHevcQsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.H264) => new EncoderH264Qsv(), (HardwareAccelerationMode.Qsv, VideoFormat.H264) => new EncoderH264Qsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.Mpeg2Video) => new EncoderMpeg2Qsv(),
(_, _) => GetSoftwareEncoder(currentState, desiredState) (_, _) => GetSoftwareEncoder(currentState, desiredState)
}; };
@ -238,11 +242,78 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
IPixelFormat formatForDownload = pixelFormat; IPixelFormat formatForDownload = pixelFormat;
bool usesVppQsv = videoInputFile.FilterSteps.Any(f => f is QsvFormatFilter or ScaleQsvFilter); bool usesVppQsv =
videoInputFile.FilterSteps.Any(f => f is QsvFormatFilter or ScaleQsvFilter or DeinterlaceQsvFilter);
// if we have no filters, check whether we need to convert pixel format
// since qsv doesn't seem to like doing that at the encoder
if (!videoInputFile.FilterSteps.Any(f => f is not IEncoder))
{
foreach (IPixelFormat currentPixelFormat in currentState.PixelFormat)
{
bool requiresConversion = false;
if (currentPixelFormat is PixelFormatNv12 nv)
{
foreach (IPixelFormat pf in AvailablePixelFormats.ForPixelFormat(nv.Name, null))
{
requiresConversion = pf.FFmpegName != format.FFmpegName;
if (!requiresConversion)
{
currentState = currentState with { PixelFormat = Some(pf) };
}
}
}
else
{
requiresConversion = currentPixelFormat.FFmpegName != format.FFmpegName;
}
if (requiresConversion)
{
if (currentState.FrameDataLocation == FrameDataLocation.Hardware)
{
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)
{
var p010Filter = new QsvFormatFilter(new PixelFormatP010());
result.Add(p010Filter);
currentState = p010Filter.NextState(currentState);
}
usesVppQsv = true;
}
}
}
}
if (!videoStream.ColorParams.IsBt709 || usesVppQsv) 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)
{
foreach (int bitDepth in currentState.PixelFormat.Map(pf => pf.BitDepth))
{
if (bitDepth is 10 && formatForDownload is not PixelFormatYuv420P10Le)
{
formatForDownload = new PixelFormatYuv420P10Le();
currentState = currentState with { PixelFormat = Some(formatForDownload) };
}
else if (bitDepth is 8 && formatForDownload is not PixelFormatNv12)
{
formatForDownload = new PixelFormatNv12(formatForDownload.Name);
currentState = currentState with { PixelFormat = Some(formatForDownload) };
}
}
}
// vpp_qsv seems to strip color info, so if we use that at all, force overriding input color info // vpp_qsv seems to strip color info, so if we use that at all, force overriding input color info
var colorspace = new ColorspaceFilter( var colorspace = new ColorspaceFilter(
currentState, currentState,
@ -250,15 +321,6 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
format, format,
forceInputOverrides: usesVppQsv); forceInputOverrides: usesVppQsv);
// force nv12 if we're still in hardware
if (currentState.FrameDataLocation == FrameDataLocation.Hardware)
{
if (formatForDownload is not PixelFormatNv12)
{
formatForDownload = new PixelFormatNv12(pixelFormat.Name);
}
}
currentState = colorspace.NextState(currentState); currentState = colorspace.NextState(currentState);
result.Add(colorspace); result.Add(colorspace);
} }
@ -295,13 +357,6 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
} }
} }
// qsv encoders don't like yuv420p
format = format switch
{
PixelFormatYuv420P => new PixelFormatNv12(PixelFormat.YUV420P),
_ => format
};
pipelineSteps.Add(new PixelFormatOutputOption(format)); pipelineSteps.Add(new PixelFormatOutputOption(format));
} }
} }
@ -375,6 +430,12 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
pf, pf,
_logger); _logger);
watermarkOverlayFilterSteps.Add(watermarkFilter); watermarkOverlayFilterSteps.Add(watermarkFilter);
// overlay filter with 10-bit vp9 seems to output alpha channel, so remove it with a pixel format change
if (videoStream.Codec == "vp9" && desiredPixelFormat.BitDepth == 10)
{
watermarkOverlayFilterSteps.Add(new PixelFormatFilter(new PixelFormatYuv420P10Le()));
}
} }
} }
@ -397,20 +458,9 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
{ {
videoInputFile.AddOption(new CopyTimestampInputOption()); videoInputFile.AddOption(new CopyTimestampInputOption());
// if (videoInputFile.FilterSteps.Count == 0 && videoInputFile.InputOptions.OfType<CuvidDecoder>().Any()) var downloadFilter = new HardwareDownloadFilter(currentState);
// { currentState = downloadFilter.NextState(currentState);
// // change the hw accel output to software so the explicit download isn't needed videoInputFile.FilterSteps.Add(downloadFilter);
// foreach (CuvidDecoder decoder in videoInputFile.InputOptions.OfType<CuvidDecoder>())
// {
// decoder.HardwareAccelerationMode = HardwareAccelerationMode.None;
// }
// }
// else
// {
var downloadFilter = new HardwareDownloadFilter(currentState);
currentState = downloadFilter.NextState(currentState);
videoInputFile.FilterSteps.Add(downloadFilter);
// }
var subtitlesFilter = new SubtitlesFilter(fontsFolder, subtitle); var subtitlesFilter = new SubtitlesFilter(fontsFolder, subtitle);
currentState = subtitlesFilter.NextState(currentState); currentState = subtitlesFilter.NextState(currentState);
@ -436,6 +486,12 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
var subtitlesFilter = new OverlaySubtitleFilter(pf); var subtitlesFilter = new OverlaySubtitleFilter(pf);
subtitleOverlayFilterSteps.Add(subtitlesFilter); subtitleOverlayFilterSteps.Add(subtitlesFilter);
// overlay filter with 10-bit vp9 seems to output alpha channel, so remove it with a pixel format change
if (videoInputFile.VideoStreams.Any(vs => vs.Codec == "vp9") && context.Is10BitOutput)
{
subtitleOverlayFilterSteps.Add(new PixelFormatFilter(new PixelFormatYuv420P10Le()));
}
} }
} }
} }
@ -488,11 +544,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
scaleStep = new ScaleQsvFilter( scaleStep = new ScaleQsvFilter(
currentState with currentState with
{ {
PixelFormat = //context.HasWatermark || PixelFormat = ffmpegState is
//context.HasSubtitleOverlay ||
// (desiredState.ScaledSize != desiredState.PaddedSize) ||
// context.HasSubtitleText ||
ffmpegState is
{ {
DecoderHardwareAccelerationMode: HardwareAccelerationMode.Nvenc, DecoderHardwareAccelerationMode: HardwareAccelerationMode.Nvenc,
EncoderHardwareAccelerationMode: HardwareAccelerationMode.None EncoderHardwareAccelerationMode: HardwareAccelerationMode.None

1
ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs

@ -197,6 +197,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
{ {
(HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) => new EncoderHevcVaapi(), (HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) => new EncoderHevcVaapi(),
(HardwareAccelerationMode.Vaapi, VideoFormat.H264) => new EncoderH264Vaapi(), (HardwareAccelerationMode.Vaapi, VideoFormat.H264) => new EncoderH264Vaapi(),
(HardwareAccelerationMode.Vaapi, VideoFormat.Mpeg2Video) => new EncoderMpeg2Vaapi(),
(_, _) => GetSoftwareEncoder(currentState, desiredState) (_, _) => GetSoftwareEncoder(currentState, desiredState)
}; };

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

@ -3,6 +3,7 @@ using System.Security.Cryptography;
using System.Text; using System.Text;
using Bugsnag; using Bugsnag;
using CliWrap; using CliWrap;
using CliWrap.Buffered;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Filler;
@ -102,41 +103,41 @@ public class TranscodingTests
public static Watermark[] Watermarks = public static Watermark[] Watermarks =
{ {
Watermark.None, Watermark.None,
// Watermark.PermanentOpaqueScaled, Watermark.PermanentOpaqueScaled,
// Watermark.PermanentOpaqueActualSize, // Watermark.PermanentOpaqueActualSize,
// Watermark.PermanentTransparentScaled, Watermark.PermanentTransparentScaled,
// Watermark.PermanentTransparentActualSize // Watermark.PermanentTransparentActualSize
}; };
public static Subtitle[] Subtitles = public static Subtitle[] Subtitles =
{ {
Subtitle.None, Subtitle.None,
// Subtitle.Picture, Subtitle.Picture,
// Subtitle.Text Subtitle.Text
}; };
public static Padding[] Paddings = public static Padding[] Paddings =
{ {
Padding.NoPadding, Padding.NoPadding,
// Padding.WithPadding Padding.WithPadding
}; };
public static VideoScanKind[] VideoScanKinds = public static VideoScanKind[] VideoScanKinds =
{ {
VideoScanKind.Progressive, VideoScanKind.Progressive,
// VideoScanKind.Interlaced VideoScanKind.Interlaced
}; };
public static InputFormat[] InputFormats = public static InputFormat[] InputFormats =
{ {
// // example format that requires colorspace filter // // 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 // // 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", "yuvj420p"),
// new("libx264", "yuv420p10le"), new("libx264", "yuv420p10le"),
// // new("libx264", "yuv444p10le"), // // new("libx264", "yuv444p10le"),
// //
// // new("mpeg1video", "yuv420p"), // // new("mpeg1video", "yuv420p"),
@ -144,12 +145,13 @@ public class TranscodingTests
// // new("mpeg2video", "yuv420p"), // // new("mpeg2video", "yuv420p"),
// //
new("libx265", "yuv420p"), new("libx265", "yuv420p"),
// new("libx265", "yuv420p10le"), new("libx265", "yuv420p10le"),
// //
// // new("mpeg4", "yuv420p"), // // new("mpeg4", "yuv420p"),
// // // //
// // new("libvpx-vp9", "yuv420p"), new("libvpx-vp9", "yuv420p"),
// // new("libvpx-vp9", "yuv420p10le"),
//
// // // new("libaom-av1", "yuv420p") // // // new("libaom-av1", "yuv420p")
// // // av1 yuv420p10le 51 // // // av1 yuv420p10le 51
// // // //
@ -162,26 +164,27 @@ public class TranscodingTests
public static Resolution[] Resolutions = public static Resolution[] Resolutions =
{ {
new() { Width = 1920, Height = 1080 }, new() { Width = 1920, Height = 1080 },
// new() { Width = 1280, Height = 720 } new() { Width = 1280, Height = 720 }
}; };
public static FFmpegProfileBitDepth[] BitDepths = public static FFmpegProfileBitDepth[] BitDepths =
{ {
FFmpegProfileBitDepth.EightBit, FFmpegProfileBitDepth.EightBit,
// FFmpegProfileBitDepth.TenBit FFmpegProfileBitDepth.TenBit
}; };
public static FFmpegProfileVideoFormat[] VideoFormats = public static FFmpegProfileVideoFormat[] VideoFormats =
{ {
FFmpegProfileVideoFormat.H264, FFmpegProfileVideoFormat.H264,
// FFmpegProfileVideoFormat.Hevc FFmpegProfileVideoFormat.Hevc,
// FFmpegProfileVideoFormat.Mpeg2Video
}; };
public static HardwareAccelerationKind[] TestAccelerations = public static HardwareAccelerationKind[] TestAccelerations =
{ {
// HardwareAccelerationKind.None, // HardwareAccelerationKind.None,
HardwareAccelerationKind.Nvenc, // HardwareAccelerationKind.Nvenc,
// HardwareAccelerationKind.Vaapi, HardwareAccelerationKind.Vaapi,
// HardwareAccelerationKind.Qsv, // HardwareAccelerationKind.Qsv,
// HardwareAccelerationKind.VideoToolbox, // HardwareAccelerationKind.VideoToolbox,
// HardwareAccelerationKind.Amf // HardwareAccelerationKind.Amf
@ -216,11 +219,13 @@ public class TranscodingTests
string file = fileToTest; string file = fileToTest;
if (string.IsNullOrWhiteSpace(file)) if (string.IsNullOrWhiteSpace(file))
{ {
if (inputFormat.Encoder is "mpeg1video" or "msmpeg4v2" or "msmpeg4v3") // 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 (videoScanKind == VideoScanKind.Interlaced) if (videoScanKind == VideoScanKind.Interlaced)
{ {
Assert.Inconclusive($"{inputFormat.Encoder} does not support interlaced content"); Assert.Inconclusive($"{inputFormat.Encoder}/{inputFormat.PixelFormat} does not support interlaced content");
return; return;
} }
} }
@ -518,7 +523,7 @@ public class TranscodingTests
string.Empty, string.Empty,
subtitleMode, subtitleMode,
now, now,
now + TimeSpan.FromSeconds(5), now + TimeSpan.FromSeconds(3),
now, now,
Option<ChannelWatermark>.None, Option<ChannelWatermark>.None,
channelWatermark, channelWatermark,
@ -528,7 +533,7 @@ public class TranscodingTests
false, false,
FillerKind.None, FillerKind.None,
TimeSpan.Zero, TimeSpan.Zero,
TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(3),
0, 0,
None, None,
false, false,
@ -557,6 +562,12 @@ public class TranscodingTests
.WithStandardOutputPipe(PipeTarget.ToFile(tempFile)) .WithStandardOutputPipe(PipeTarget.ToFile(tempFile))
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(sb)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(sb))
.ExecuteAsync(timeoutSignal.Token); .ExecuteAsync(timeoutSignal.Token);
// var arguments = string.Join(
// ' ',
// process.Arguments.Split(" ").Map(a => a.Contains('[') ? $"\"{a}\"" : a));
//
// Log.Logger.Debug(arguments);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -636,7 +647,9 @@ public class TranscodingTests
videoStream.ColorPrimaries); videoStream.ColorPrimaries);
// AMF doesn't seem to set this metadata properly // AMF doesn't seem to set this metadata properly
if (profileAcceleration != HardwareAccelerationKind.Amf) // MPEG2Video doesn't always seem to set this properly
if (profileAcceleration != HardwareAccelerationKind.Amf &&
profileVideoFormat != FFmpegProfileVideoFormat.Mpeg2Video)
{ {
colorParams.IsBt709.Should().BeTrue($"{colorParams}"); colorParams.IsBt709.Should().BeTrue($"{colorParams}");
} }
@ -709,20 +722,13 @@ public class TranscodingTests
TestContext.CurrentContext.TestDirectory, TestContext.CurrentContext.TestDirectory,
"Resources", "Resources",
subtitle == Subtitle.Picture ? "test.sup" : "test.srt"); subtitle == Subtitle.Picture ? "test.sup" : "test.srt");
var p2 = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = ExecutableName("mkvmerge"),
Arguments =
$"-o {tempFileName} {sourceFile} --field-order 0:{(videoScanKind == VideoScanKind.Interlaced ? '1' : '0')} {subPath}"
}
};
p2.Start(); BufferedCommandResult p2 = await new Command(ExecutableName("mkvmerge"))
await p2.WaitForExitAsync(); .WithArguments(
// ReSharper disable once MethodHasAsyncOverload $"-o {tempFileName} {sourceFile} --field-order 0:{(videoScanKind == VideoScanKind.Interlaced ? '1' : '0')} {subPath}")
p2.WaitForExit(); .WithValidation(CommandResultValidation.None)
.ExecuteBufferedAsync();
if (p2.ExitCode != 0) if (p2.ExitCode != 0)
{ {
if (File.Exists(sourceFile)) if (File.Exists(sourceFile))

9
ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj

@ -39,6 +39,15 @@
<Content Include="Resources\Nfo\EpisodeInvalidCharacters.nfo"> <Content Include="Resources\Nfo\EpisodeInvalidCharacters.nfo">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Resources\ErsatzTV.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Resources\test.srt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Resources\test.sup">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
</Project> </Project>

0
ErsatzTV.Core.Tests/Resources/ErsatzTV.png → ErsatzTV.Scanner.Tests/Resources/ErsatzTV.png

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

0
ErsatzTV.Core.Tests/Resources/test.srt → ErsatzTV.Scanner.Tests/Resources/test.srt

0
ErsatzTV.Core.Tests/Resources/test.sup → ErsatzTV.Scanner.Tests/Resources/test.sup

2
ErsatzTV/ErsatzTV.csproj

@ -72,7 +72,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MudBlazor" Version="6.1.7" /> <PackageReference Include="MudBlazor" Version="6.1.8" />
<PackageReference Include="NaturalSort.Extension" Version="4.0.0" /> <PackageReference Include="NaturalSort.Extension" Version="4.0.0" />
<PackageReference Include="PPioli.FluentValidation.Blazor" Version="11.1.0" /> <PackageReference Include="PPioli.FluentValidation.Blazor" Version="11.1.0" />
<PackageReference Include="Refit.HttpClientFactory" Version="6.3.2" /> <PackageReference Include="Refit.HttpClientFactory" Version="6.3.2" />

6
ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs

@ -90,5 +90,11 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator<FFmpegProfi
RuleFor(x => x.VideoFormat).Must(c => AmfFormats.Contains(c)) RuleFor(x => x.VideoFormat).Must(c => AmfFormats.Contains(c))
.WithMessage("Amf supports formats (h264, hevc)"); .WithMessage("Amf supports formats (h264, hevc)");
}); });
When(
x => x.VideoFormat == FFmpegProfileVideoFormat.Mpeg2Video,
() => RuleFor(x => x.BitDepth)
.Must(bd => bd is FFmpegProfileBitDepth.EightBit)
.WithMessage("Mpeg2Video does not support 10-bit content"));
} }
} }

Loading…
Cancel
Save