diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc9069556..ba7cac100 100644
--- a/CHANGELOG.md
+++ b/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/).
## [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
### Added
diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
index cb84bab89..e4ec0640d 100644
--- a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
+++ b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
@@ -36,7 +36,12 @@ public class
p.QsvExtraHardwareFrames = update.QsvExtraHardwareFrames;
p.ResolutionId = update.ResolutionId;
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.VideoBufferSize = update.VideoBufferSize;
p.AudioFormat = update.AudioFormat;
diff --git a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj
index f868743ff..05ee1dcbf 100644
--- a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj
+++ b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj
@@ -34,16 +34,4 @@
-
-
- Always
-
-
- Always
-
-
- Always
-
-
-
diff --git a/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs b/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
index 6ff541b79..b5c4c246a 100644
--- a/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
+++ b/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
@@ -141,9 +141,9 @@ public class FFmpegPlaybackSettingsCalculator
result.PixelFormat = ffmpegProfile.BitDepth switch
{
- FFmpegProfileBitDepth.TenBit => new PixelFormatYuv420P10Le(),
+ FFmpegProfileBitDepth.TenBit when ffmpegProfile.VideoFormat != FFmpegProfileVideoFormat.Mpeg2Video
+ => new PixelFormatYuv420P10Le(),
_ => new PixelFormatYuv420P()
- // _ => new PixelFormatYuv420P10Le()
};
result.AudioFormat = ffmpegProfile.AudioFormat;
diff --git a/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs
index 568425dd5..010d4056b 100644
--- a/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs
+++ b/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs
@@ -4,7 +4,18 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public class DefaultHardwareCapabilities : IHardwareCapabilities
{
- public bool CanDecode(string videoFormat, Option videoProfile, Option maybePixelFormat) => true;
+ public bool CanDecode(string videoFormat, Option videoProfile, Option 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 videoProfile, Option maybePixelFormat)
{
diff --git a/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs
index 657ed6d6e..e9e594d1e 100644
--- a/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs
+++ b/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs
@@ -107,6 +107,10 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
_profileEntrypoints.Contains(
new VaapiProfileEntrypoint(VaapiProfile.HevcMain, VaapiEntrypoint.Encode)),
+ VideoFormat.Mpeg2Video =>
+ _profileEntrypoints.Contains(
+ new VaapiProfileEntrypoint(VaapiProfile.Mpeg2Main, VaapiEntrypoint.Encode)),
+
_ => false
};
diff --git a/ErsatzTV.FFmpeg/Decoder/DecoderVaapi.cs b/ErsatzTV.FFmpeg/Decoder/DecoderVaapi.cs
index 1aec3a217..e0c799c2c 100644
--- a/ErsatzTV.FFmpeg/Decoder/DecoderVaapi.cs
+++ b/ErsatzTV.FFmpeg/Decoder/DecoderVaapi.cs
@@ -16,7 +16,12 @@ public class DecoderVaapi : DecoderBase
FrameState nextState = base.NextState(currentState);
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);
}
}
diff --git a/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderHevcQsv.cs b/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderHevcQsv.cs
index 00be75d07..1c850ad64 100644
--- a/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderHevcQsv.cs
+++ b/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 override string Name => "hevc_qsv";
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);
+ }
}
diff --git a/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderVp9Qsv.cs b/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderVp9Qsv.cs
index 023df2853..e08be07b0 100644
--- a/ErsatzTV.FFmpeg/Decoder/Qsv/DecoderVp9Qsv.cs
+++ b/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 override string Name => "vp9_qsv";
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);
+ }
}
diff --git a/ErsatzTV.FFmpeg/Encoder/Qsv/EncoderMpeg2Qsv.cs b/ErsatzTV.FFmpeg/Encoder/Qsv/EncoderMpeg2Qsv.cs
new file mode 100644
index 000000000..a0d013e5a
--- /dev/null
+++ b/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 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
+ };
+}
diff --git a/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderMpeg2Vaapi.cs b/ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderMpeg2Vaapi.cs
new file mode 100644
index 000000000..8fa25a4ef
--- /dev/null
+++ b/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
+ };
+}
diff --git a/ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs b/ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs
index 02cf1ce69..d0515170b 100644
--- a/ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs
+++ b/ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs
@@ -65,9 +65,12 @@ public class ColorspaceFilter : BaseFilter
string primaries = string.IsNullOrWhiteSpace(cp.ColorPrimaries)
? "bt709"
: cp.ColorPrimaries;
+ string space = string.IsNullOrWhiteSpace(cp.ColorSpace)
+ ? "bt709"
+ : cp.ColorSpace;
inputOverrides =
- $"irange={range}:ispace={cp.ColorSpace}:itrc={transfer}:iprimaries={primaries}:";
+ $"irange={range}:ispace={space}:itrc={transfer}:iprimaries={primaries}:";
}
string colorspace = _desiredPixelFormat.BitDepth switch
diff --git a/ErsatzTV.FFmpeg/Format/PixelFormatP010.cs b/ErsatzTV.FFmpeg/Format/PixelFormatP010.cs
new file mode 100644
index 000000000..95de8741c
--- /dev/null
+++ b/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;
+}
diff --git a/ErsatzTV.FFmpeg/Format/PixelFormatVaapi.cs b/ErsatzTV.FFmpeg/Format/PixelFormatVaapi.cs
new file mode 100644
index 000000000..57d84fe45
--- /dev/null
+++ b/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;
+}
diff --git a/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs
index a47bbc42b..457198f74 100644
--- a/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs
+++ b/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs
@@ -121,9 +121,9 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
ScaledSize = 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
- ? videoStream.PixelFormat.Map(pf => (IPixelFormat)new PixelFormatNv12(pf.Name))
+ ? videoStream.PixelFormat.Map(pf => pf.BitDepth == 8 ? new PixelFormatNv12(pf.Name) : pf)
: videoStream.PixelFormat,
IsAnamorphic = videoStream.IsAnamorphic,
@@ -137,7 +137,10 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
{
IPixelFormat pixelFormat = desiredState.PixelFormat.IfNone(
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);
@@ -184,6 +187,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
{
(HardwareAccelerationMode.Qsv, VideoFormat.Hevc) => new EncoderHevcQsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.H264) => new EncoderH264Qsv(),
+ (HardwareAccelerationMode.Qsv, VideoFormat.Mpeg2Video) => new EncoderMpeg2Qsv(),
(_, _) => GetSoftwareEncoder(currentState, desiredState)
};
@@ -238,11 +242,78 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
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)
{
_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
var colorspace = new ColorspaceFilter(
currentState,
@@ -250,15 +321,6 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
format,
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);
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));
}
}
@@ -375,6 +430,12 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
pf,
_logger);
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());
- // if (videoInputFile.FilterSteps.Count == 0 && videoInputFile.InputOptions.OfType().Any())
- // {
- // // change the hw accel output to software so the explicit download isn't needed
- // foreach (CuvidDecoder decoder in videoInputFile.InputOptions.OfType())
- // {
- // decoder.HardwareAccelerationMode = HardwareAccelerationMode.None;
- // }
- // }
- // else
- // {
- var downloadFilter = new HardwareDownloadFilter(currentState);
- currentState = downloadFilter.NextState(currentState);
- videoInputFile.FilterSteps.Add(downloadFilter);
- // }
+ var downloadFilter = new HardwareDownloadFilter(currentState);
+ currentState = downloadFilter.NextState(currentState);
+ videoInputFile.FilterSteps.Add(downloadFilter);
var subtitlesFilter = new SubtitlesFilter(fontsFolder, subtitle);
currentState = subtitlesFilter.NextState(currentState);
@@ -436,6 +486,12 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
var subtitlesFilter = new OverlaySubtitleFilter(pf);
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(
currentState with
{
- PixelFormat = //context.HasWatermark ||
- //context.HasSubtitleOverlay ||
- // (desiredState.ScaledSize != desiredState.PaddedSize) ||
- // context.HasSubtitleText ||
- ffmpegState is
+ PixelFormat = ffmpegState is
{
DecoderHardwareAccelerationMode: HardwareAccelerationMode.Nvenc,
EncoderHardwareAccelerationMode: HardwareAccelerationMode.None
diff --git a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs
index 49475fd57..fb0f6ad35 100644
--- a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs
+++ b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs
@@ -197,6 +197,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
{
(HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) => new EncoderHevcVaapi(),
(HardwareAccelerationMode.Vaapi, VideoFormat.H264) => new EncoderH264Vaapi(),
+ (HardwareAccelerationMode.Vaapi, VideoFormat.Mpeg2Video) => new EncoderMpeg2Vaapi(),
(_, _) => GetSoftwareEncoder(currentState, desiredState)
};
diff --git a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs
index 7c7bb9325..8ff296843 100644
--- a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs
+++ b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs
@@ -3,6 +3,7 @@ using System.Security.Cryptography;
using System.Text;
using Bugsnag;
using CliWrap;
+using CliWrap.Buffered;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler;
@@ -102,41 +103,41 @@ public class TranscodingTests
public static Watermark[] Watermarks =
{
Watermark.None,
- // Watermark.PermanentOpaqueScaled,
+ Watermark.PermanentOpaqueScaled,
// Watermark.PermanentOpaqueActualSize,
- // Watermark.PermanentTransparentScaled,
+ Watermark.PermanentTransparentScaled,
// Watermark.PermanentTransparentActualSize
};
public static Subtitle[] Subtitles =
{
Subtitle.None,
- // Subtitle.Picture,
- // Subtitle.Text
+ Subtitle.Picture,
+ Subtitle.Text
};
public static Padding[] Paddings =
{
Padding.NoPadding,
- // Padding.WithPadding
+ Padding.WithPadding
};
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"),
@@ -144,12 +145,13 @@ public class TranscodingTests
// // new("mpeg2video", "yuv420p"),
//
new("libx265", "yuv420p"),
- // new("libx265", "yuv420p10le"),
+ new("libx265", "yuv420p10le"),
//
// // new("mpeg4", "yuv420p"),
// //
- // // new("libvpx-vp9", "yuv420p"),
- // //
+ new("libvpx-vp9", "yuv420p"),
+ new("libvpx-vp9", "yuv420p10le"),
+ //
// // // new("libaom-av1", "yuv420p")
// // // av1 yuv420p10le 51
// //
@@ -162,26 +164,27 @@ public class TranscodingTests
public static Resolution[] Resolutions =
{
new() { Width = 1920, Height = 1080 },
- // new() { Width = 1280, Height = 720 }
+ new() { Width = 1280, Height = 720 }
};
public static FFmpegProfileBitDepth[] BitDepths =
{
FFmpegProfileBitDepth.EightBit,
- // FFmpegProfileBitDepth.TenBit
+ FFmpegProfileBitDepth.TenBit
};
public static FFmpegProfileVideoFormat[] VideoFormats =
{
FFmpegProfileVideoFormat.H264,
- // FFmpegProfileVideoFormat.Hevc
+ FFmpegProfileVideoFormat.Hevc,
+ // FFmpegProfileVideoFormat.Mpeg2Video
};
public static HardwareAccelerationKind[] TestAccelerations =
{
// HardwareAccelerationKind.None,
- HardwareAccelerationKind.Nvenc,
- // HardwareAccelerationKind.Vaapi,
+ // HardwareAccelerationKind.Nvenc,
+ HardwareAccelerationKind.Vaapi,
// HardwareAccelerationKind.Qsv,
// HardwareAccelerationKind.VideoToolbox,
// HardwareAccelerationKind.Amf
@@ -216,11 +219,13 @@ public class TranscodingTests
string file = fileToTest;
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)
{
- Assert.Inconclusive($"{inputFormat.Encoder} does not support interlaced content");
+ Assert.Inconclusive($"{inputFormat.Encoder}/{inputFormat.PixelFormat} does not support interlaced content");
return;
}
}
@@ -518,7 +523,7 @@ public class TranscodingTests
string.Empty,
subtitleMode,
now,
- now + TimeSpan.FromSeconds(5),
+ now + TimeSpan.FromSeconds(3),
now,
Option.None,
channelWatermark,
@@ -528,7 +533,7 @@ public class TranscodingTests
false,
FillerKind.None,
TimeSpan.Zero,
- TimeSpan.FromSeconds(5),
+ TimeSpan.FromSeconds(3),
0,
None,
false,
@@ -557,6 +562,12 @@ public class TranscodingTests
.WithStandardOutputPipe(PipeTarget.ToFile(tempFile))
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(sb))
.ExecuteAsync(timeoutSignal.Token);
+
+ // var arguments = string.Join(
+ // ' ',
+ // process.Arguments.Split(" ").Map(a => a.Contains('[') ? $"\"{a}\"" : a));
+ //
+ // Log.Logger.Debug(arguments);
}
catch (OperationCanceledException)
{
@@ -636,7 +647,9 @@ public class TranscodingTests
videoStream.ColorPrimaries);
// 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}");
}
@@ -709,20 +722,13 @@ public class TranscodingTests
TestContext.CurrentContext.TestDirectory,
"Resources",
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();
- await p2.WaitForExitAsync();
- // ReSharper disable once MethodHasAsyncOverload
- p2.WaitForExit();
+ BufferedCommandResult p2 = await new Command(ExecutableName("mkvmerge"))
+ .WithArguments(
+ $"-o {tempFileName} {sourceFile} --field-order 0:{(videoScanKind == VideoScanKind.Interlaced ? '1' : '0')} {subPath}")
+ .WithValidation(CommandResultValidation.None)
+ .ExecuteBufferedAsync();
+
if (p2.ExitCode != 0)
{
if (File.Exists(sourceFile))
diff --git a/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj b/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj
index 2fc64d23e..439348d57 100644
--- a/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj
+++ b/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj
@@ -39,6 +39,15 @@
Always
+
+ Always
+
+
+ Always
+
+
+ Always
+
diff --git a/ErsatzTV.Core.Tests/Resources/ErsatzTV.png b/ErsatzTV.Scanner.Tests/Resources/ErsatzTV.png
similarity index 100%
rename from ErsatzTV.Core.Tests/Resources/ErsatzTV.png
rename to ErsatzTV.Scanner.Tests/Resources/ErsatzTV.png
diff --git a/ErsatzTV.Core.Tests/Resources/test.srt b/ErsatzTV.Scanner.Tests/Resources/test.srt
similarity index 100%
rename from ErsatzTV.Core.Tests/Resources/test.srt
rename to ErsatzTV.Scanner.Tests/Resources/test.srt
diff --git a/ErsatzTV.Core.Tests/Resources/test.sup b/ErsatzTV.Scanner.Tests/Resources/test.sup
old mode 100755
new mode 100644
similarity index 100%
rename from ErsatzTV.Core.Tests/Resources/test.sup
rename to ErsatzTV.Scanner.Tests/Resources/test.sup
diff --git a/ErsatzTV/ErsatzTV.csproj b/ErsatzTV/ErsatzTV.csproj
index de6c018af..9617f4c2e 100644
--- a/ErsatzTV/ErsatzTV.csproj
+++ b/ErsatzTV/ErsatzTV.csproj
@@ -72,7 +72,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs
index c8eb0c54f..000abc87e 100644
--- a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs
+++ b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs
@@ -90,5 +90,11 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator x.VideoFormat).Must(c => AmfFormats.Contains(c))
.WithMessage("Amf supports formats (h264, hevc)");
});
+
+ When(
+ x => x.VideoFormat == FFmpegProfileVideoFormat.Mpeg2Video,
+ () => RuleFor(x => x.BitDepth)
+ .Must(bd => bd is FFmpegProfileBitDepth.EightBit)
+ .WithMessage("Mpeg2Video does not support 10-bit content"));
}
}