Browse Source

add video presets for nvenc, qsv, software h264 and hevc encoders (#1688)

* add video preset to ffmpeg profile

* add some hevc video presets
pull/1689/head
Jason Dove 1 year ago committed by GitHub
parent
commit
b9a7ad2f5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      CHANGELOG.md
  2. 1
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs
  3. 1
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs
  4. 1
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs
  5. 1
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
  6. 1
      ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs
  7. 1
      ErsatzTV.Application/FFmpegProfiles/Mapper.cs
  8. 2
      ErsatzTV.Core/Domain/FFmpegProfile.cs
  9. 22
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  10. 5
      ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs
  11. 22
      ErsatzTV.FFmpeg/Encoder/EncoderLibx264.cs
  12. 33
      ErsatzTV.FFmpeg/Encoder/EncoderLibx265.cs
  13. 22
      ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs
  14. 29
      ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs
  15. 24
      ErsatzTV.FFmpeg/Encoder/Qsv/EncoderH264Qsv.cs
  16. 22
      ErsatzTV.FFmpeg/Encoder/Qsv/EncoderHevcQsv.cs
  17. 1
      ErsatzTV.FFmpeg/FrameState.cs
  18. 4
      ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs
  19. 4
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs
  20. 6
      ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs
  21. 31
      ErsatzTV.FFmpeg/Preset/AvailablePresets.cs
  22. 13
      ErsatzTV.FFmpeg/Preset/VideoPreset.cs
  23. 5596
      ErsatzTV.Infrastructure.MySql/Migrations/20240423191402_Add_FFmpegProfile_VideoPreset.Designer.cs
  24. 29
      ErsatzTV.Infrastructure.MySql/Migrations/20240423191402_Add_FFmpegProfile_VideoPreset.cs
  25. 3
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  26. 5441
      ErsatzTV.Infrastructure.Sqlite/Migrations/20240423185231_Add_FFmpegProfile_VideoPreset.Designer.cs
  27. 28
      ErsatzTV.Infrastructure.Sqlite/Migrations/20240423185231_Add_FFmpegProfile_VideoPreset.cs
  28. 3
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  29. 37
      ErsatzTV/Pages/FFmpegEditor.razor
  30. 4
      ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

7
CHANGELOG.md

@ -28,6 +28,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- If a dotnet 8 version of ETV has NOT been launched on MacOS, it will automatically migrate the config folder on startup - If a dotnet 8 version of ETV has NOT been launched on MacOS, it will automatically migrate the config folder on startup
- If a dotnet 8 version of ETV *has* been launched on MacOS, a failing health check will display with instructions on how to resolve the config issue to restore data - If a dotnet 8 version of ETV *has* been launched on MacOS, a failing health check will display with instructions on how to resolve the config issue to restore data
- Add `Video Profile` setting to `FFmpeg Profile` editor when `h264` format is selected - Add `Video Profile` setting to `FFmpeg Profile` editor when `h264` format is selected
- Add `Video Preset` setting to `FFmpeg Profile` editor for some combinations of acceleration and video format:
- `Nvenc` / `h264`
- `Nvenc` / `hevc`
- `Qsv` / `h264`
- `Qsv` / `hevc`
- `None` / `h264`
- `None` / `hevc`
### Fixed ### Fixed
- Fix some cases of 404s from Plex when files were replaced and scanning the library from ETV didn't help - Fix some cases of 404s from Plex when files were replaced and scanning the library from ETV didn't help

1
ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs

@ -15,6 +15,7 @@ public record CreateFFmpegProfile(
ScalingBehavior ScalingBehavior, ScalingBehavior ScalingBehavior,
FFmpegProfileVideoFormat VideoFormat, FFmpegProfileVideoFormat VideoFormat,
string VideoProfile, string VideoProfile,
string VideoPreset,
FFmpegProfileBitDepth BitDepth, FFmpegProfileBitDepth BitDepth,
int VideoBitrate, int VideoBitrate,
int VideoBufferSize, int VideoBufferSize,

1
ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs

@ -55,6 +55,7 @@ public class CreateFFmpegProfileHandler :
ScalingBehavior = request.ScalingBehavior, ScalingBehavior = request.ScalingBehavior,
VideoFormat = request.VideoFormat, VideoFormat = request.VideoFormat,
VideoProfile = request.VideoProfile, VideoProfile = request.VideoProfile,
VideoPreset = request.VideoPreset,
BitDepth = request.BitDepth, BitDepth = request.BitDepth,
VideoBitrate = request.VideoBitrate, VideoBitrate = request.VideoBitrate,
VideoBufferSize = request.VideoBufferSize, VideoBufferSize = request.VideoBufferSize,

1
ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs

@ -16,6 +16,7 @@ public record UpdateFFmpegProfile(
ScalingBehavior ScalingBehavior, ScalingBehavior ScalingBehavior,
FFmpegProfileVideoFormat VideoFormat, FFmpegProfileVideoFormat VideoFormat,
string VideoProfile, string VideoProfile,
string VideoPreset,
FFmpegProfileBitDepth BitDepth, FFmpegProfileBitDepth BitDepth,
int VideoBitrate, int VideoBitrate,
int VideoBufferSize, int VideoBufferSize,

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

@ -43,6 +43,7 @@ public class
p.ScalingBehavior = update.ScalingBehavior; p.ScalingBehavior = update.ScalingBehavior;
p.VideoFormat = update.VideoFormat; p.VideoFormat = update.VideoFormat;
p.VideoProfile = update.VideoProfile; p.VideoProfile = update.VideoProfile;
p.VideoPreset = update.VideoPreset;
// mpeg2video only supports 8-bit content // mpeg2video only supports 8-bit content
p.BitDepth = update.VideoFormat == FFmpegProfileVideoFormat.Mpeg2Video p.BitDepth = update.VideoFormat == FFmpegProfileVideoFormat.Mpeg2Video

1
ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs

@ -16,6 +16,7 @@ public record FFmpegProfileViewModel(
ScalingBehavior ScalingBehavior, ScalingBehavior ScalingBehavior,
FFmpegProfileVideoFormat VideoFormat, FFmpegProfileVideoFormat VideoFormat,
string VideoProfile, string VideoProfile,
string VideoPreset,
FFmpegProfileBitDepth BitDepth, FFmpegProfileBitDepth BitDepth,
int VideoBitrate, int VideoBitrate,
int VideoBufferSize, int VideoBufferSize,

1
ErsatzTV.Application/FFmpegProfiles/Mapper.cs

@ -18,6 +18,7 @@ internal static class Mapper
profile.ScalingBehavior, profile.ScalingBehavior,
profile.VideoFormat, profile.VideoFormat,
profile.VideoProfile, profile.VideoProfile,
profile.VideoPreset ?? string.Empty,
profile.BitDepth, profile.BitDepth,
profile.VideoBitrate, profile.VideoBitrate,
profile.VideoBufferSize, profile.VideoBufferSize,

2
ErsatzTV.Core/Domain/FFmpegProfile.cs

@ -16,6 +16,7 @@ public record FFmpegProfile
public ScalingBehavior ScalingBehavior { get; set; } public ScalingBehavior ScalingBehavior { get; set; }
public FFmpegProfileVideoFormat VideoFormat { get; set; } public FFmpegProfileVideoFormat VideoFormat { get; set; }
public string VideoProfile { get; set; } public string VideoProfile { get; set; }
public string VideoPreset { get; set; }
public FFmpegProfileBitDepth BitDepth { get; set; } public FFmpegProfileBitDepth BitDepth { get; set; }
public int VideoBitrate { get; set; } public int VideoBitrate { get; set; }
public int VideoBufferSize { get; set; } public int VideoBufferSize { get; set; }
@ -37,6 +38,7 @@ public record FFmpegProfile
Resolution = resolution, Resolution = resolution,
VideoFormat = FFmpegProfileVideoFormat.H264, VideoFormat = FFmpegProfileVideoFormat.H264,
VideoProfile = "high", VideoProfile = "high",
VideoPreset = ErsatzTV.FFmpeg.Preset.VideoPreset.Unset,
AudioFormat = FFmpegProfileAudioFormat.Aac, AudioFormat = FFmpegProfileAudioFormat.Aac,
VideoBitrate = 2000, VideoBitrate = 2000,
VideoBufferSize = 4000, VideoBufferSize = 4000,

22
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -8,6 +8,7 @@ using ErsatzTV.FFmpeg.Environment;
using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.Format;
using ErsatzTV.FFmpeg.OutputFormat; using ErsatzTV.FFmpeg.OutputFormat;
using ErsatzTV.FFmpeg.Pipeline; using ErsatzTV.FFmpeg.Pipeline;
using ErsatzTV.FFmpeg.Preset;
using ErsatzTV.FFmpeg.State; using ErsatzTV.FFmpeg.State;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MediaStream = ErsatzTV.Core.Domain.MediaStream; using MediaStream = ErsatzTV.Core.Domain.MediaStream;
@ -303,10 +304,11 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
Option<WatermarkInputFile> watermarkInputFile = GetWatermarkInputFile(watermarkOptions, maybeFadePoints); Option<WatermarkInputFile> watermarkInputFile = GetWatermarkInputFile(watermarkOptions, maybeFadePoints);
HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode(playbackSettings, fillerKind);
string videoFormat = GetVideoFormat(playbackSettings); string videoFormat = GetVideoFormat(playbackSettings);
Option<string> maybeVideoProfile = GetVideoProfile(videoFormat, channel.FFmpegProfile.VideoProfile); Option<string> maybeVideoProfile = GetVideoProfile(videoFormat, channel.FFmpegProfile.VideoProfile);
Option<string> maybeVideoPreset = GetVideoPreset(hwAccel, videoFormat, channel.FFmpegProfile.VideoPreset);
HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode(playbackSettings, fillerKind);
Option<string> hlsPlaylistPath = outputFormat == OutputFormatKind.Hls Option<string> hlsPlaylistPath = outputFormat == OutputFormatKind.Hls
? Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live.m3u8") ? Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live.m3u8")
@ -345,6 +347,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
fillerKind == FillerKind.Fallback, fillerKind == FillerKind.Fallback,
videoFormat, videoFormat,
maybeVideoProfile, maybeVideoProfile,
maybeVideoPreset,
Optional(playbackSettings.PixelFormat), Optional(playbackSettings.PixelFormat),
scaledSize, scaledSize,
paddedSize, paddedSize,
@ -452,6 +455,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
false, false,
videoFormat, videoFormat,
GetVideoProfile(videoFormat, channel.FFmpegProfile.VideoProfile), GetVideoProfile(videoFormat, channel.FFmpegProfile.VideoProfile),
VideoPreset.Unset,
new PixelFormatYuv420P(), new PixelFormatYuv420P(),
new FrameSize(desiredResolution.Width, desiredResolution.Height), new FrameSize(desiredResolution.Width, desiredResolution.Height),
new FrameSize(desiredResolution.Width, desiredResolution.Height), new FrameSize(desiredResolution.Width, desiredResolution.Height),
@ -655,10 +659,11 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
Option<SubtitleInputFile> subtitleInputFile = Option<SubtitleInputFile>.None; Option<SubtitleInputFile> subtitleInputFile = Option<SubtitleInputFile>.None;
Option<WatermarkInputFile> watermarkInputFile = Option<WatermarkInputFile>.None; Option<WatermarkInputFile> watermarkInputFile = Option<WatermarkInputFile>.None;
HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode(playbackSettings, FillerKind.None);
string videoFormat = GetVideoFormat(playbackSettings); string videoFormat = GetVideoFormat(playbackSettings);
Option<string> maybeVideoProfile = GetVideoProfile(videoFormat, channel.FFmpegProfile.VideoProfile); Option<string> maybeVideoProfile = GetVideoProfile(videoFormat, channel.FFmpegProfile.VideoProfile);
Option<string> maybeVideoPreset = GetVideoPreset(hwAccel, videoFormat, channel.FFmpegProfile.VideoPreset);
HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode(playbackSettings, FillerKind.None);
Option<string> hlsPlaylistPath = Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live.m3u8"); Option<string> hlsPlaylistPath = Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live.m3u8");
@ -676,6 +681,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
true, true,
videoFormat, videoFormat,
maybeVideoProfile, maybeVideoProfile,
maybeVideoPreset,
Optional(playbackSettings.PixelFormat), Optional(playbackSettings.PixelFormat),
resolution, resolution,
resolution, resolution,
@ -999,6 +1005,14 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
_ => Option<string>.None _ => Option<string>.None
}; };
private static Option<string> GetVideoPreset(
HardwareAccelerationMode hardwareAccelerationMode,
string videoFormat,
string videoPreset) =>
AvailablePresets
.ForAccelAndFormat(hardwareAccelerationMode, videoFormat)
.Find(p => string.Equals(p, videoPreset, StringComparison.OrdinalIgnoreCase));
private static HardwareAccelerationMode GetHardwareAccelerationMode( private static HardwareAccelerationMode GetHardwareAccelerationMode(
FFmpegPlaybackSettings playbackSettings, FFmpegPlaybackSettings playbackSettings,
FillerKind fillerKind) => FillerKind fillerKind) =>

5
ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs

@ -6,6 +6,7 @@ using ErsatzTV.FFmpeg.Format;
using ErsatzTV.FFmpeg.InputOption; using ErsatzTV.FFmpeg.InputOption;
using ErsatzTV.FFmpeg.OutputFormat; using ErsatzTV.FFmpeg.OutputFormat;
using ErsatzTV.FFmpeg.Pipeline; using ErsatzTV.FFmpeg.Pipeline;
using ErsatzTV.FFmpeg.Preset;
using ErsatzTV.FFmpeg.State; using ErsatzTV.FFmpeg.State;
using FluentAssertions; using FluentAssertions;
using LanguageExt; using LanguageExt;
@ -58,6 +59,7 @@ public class PipelineBuilderBaseTests
false, false,
VideoFormat.Hevc, VideoFormat.Hevc,
VideoProfile.Main, VideoProfile.Main,
VideoPreset.Unset,
new PixelFormatYuv420P(), new PixelFormatYuv420P(),
new FrameSize(1920, 1080), new FrameSize(1920, 1080),
new FrameSize(1920, 1080), new FrameSize(1920, 1080),
@ -148,6 +150,7 @@ public class PipelineBuilderBaseTests
false, false,
VideoFormat.Hevc, VideoFormat.Hevc,
VideoProfile.Main, VideoProfile.Main,
VideoPreset.Unset,
new PixelFormatYuv420P(), new PixelFormatYuv420P(),
new FrameSize(1920, 1080), new FrameSize(1920, 1080),
new FrameSize(1920, 1080), new FrameSize(1920, 1080),
@ -294,6 +297,7 @@ public class PipelineBuilderBaseTests
false, false,
VideoFormat.Copy, VideoFormat.Copy,
VideoProfile.Main, VideoProfile.Main,
VideoPreset.Unset,
Option<IPixelFormat>.None, Option<IPixelFormat>.None,
new FrameSize(1920, 1080), new FrameSize(1920, 1080),
new FrameSize(1920, 1080), new FrameSize(1920, 1080),
@ -378,6 +382,7 @@ public class PipelineBuilderBaseTests
false, false,
VideoFormat.Copy, VideoFormat.Copy,
VideoProfile.Main, VideoProfile.Main,
VideoPreset.Unset,
new PixelFormatYuv420P(), new PixelFormatYuv420P(),
new FrameSize(1920, 1080), new FrameSize(1920, 1080),
new FrameSize(1920, 1080), new FrameSize(1920, 1080),

22
ErsatzTV.FFmpeg/Encoder/EncoderLibx264.cs

@ -2,7 +2,7 @@
namespace ErsatzTV.FFmpeg.Encoder; namespace ErsatzTV.FFmpeg.Encoder;
public class EncoderLibx264(Option<string> maybeVideoProfile) : EncoderBase public class EncoderLibx264(Option<string> maybeVideoProfile, Option<string> maybeVideoPreset) : EncoderBase
{ {
public override string Name => "libx264"; public override string Name => "libx264";
public override StreamKind Kind => StreamKind.Video; public override StreamKind Kind => StreamKind.Video;
@ -11,16 +11,24 @@ public class EncoderLibx264(Option<string> maybeVideoProfile) : EncoderBase
{ {
get get
{ {
var result = new List<string>(base.OutputOptions);
foreach (string videoProfile in maybeVideoProfile) foreach (string videoProfile in maybeVideoProfile)
{ {
return result.Add("-profile:v");
[ result.Add(videoProfile.ToLowerInvariant());
"-c:v", Name,
"-profile:v", videoProfile.ToLowerInvariant()
];
} }
return base.OutputOptions; foreach (string videoPreset in maybeVideoPreset)
{
if (!string.IsNullOrWhiteSpace(videoPreset))
{
result.Add("-preset:v");
result.Add(videoPreset);
}
}
return result.ToArray();
} }
} }

33
ErsatzTV.FFmpeg/Encoder/EncoderLibx265.cs

@ -3,17 +3,34 @@ using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Encoder; namespace ErsatzTV.FFmpeg.Encoder;
public class EncoderLibx265 : EncoderBase public class EncoderLibx265(FrameState currentState, Option<string> maybeVideoPreset) : EncoderBase
{ {
private readonly FrameState _currentState; public override string Filter => new HardwareDownloadFilter(currentState).Filter;
public EncoderLibx265(FrameState currentState) => _currentState = currentState;
public override string Filter => new HardwareDownloadFilter(_currentState).Filter;
// TODO: is tag:v needed for mpegts? // TODO: is tag:v needed for mpegts?
public override string[] OutputOptions => new[] public override string[] OutputOptions
{ "-c:v", Name, "-tag:v", "hvc1", "-x265-params", "log-level=error" }; {
get
{
var result = new List<string>
{
"-c:v", Name,
"-tag:v", "hvc1",
"-x265-params", "log-level=error"
};
foreach (string videoPreset in maybeVideoPreset)
{
if (!string.IsNullOrWhiteSpace(videoPreset))
{
result.Add("-preset:v");
result.Add(videoPreset);
}
}
return result.ToArray();
}
}
public override string Name => "libx265"; public override string Name => "libx265";
public override StreamKind Kind => StreamKind.Video; public override StreamKind Kind => StreamKind.Video;

22
ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs

@ -2,7 +2,7 @@
namespace ErsatzTV.FFmpeg.Encoder.Nvenc; namespace ErsatzTV.FFmpeg.Encoder.Nvenc;
public class EncoderH264Nvenc(Option<string> maybeVideoProfile) : EncoderBase public class EncoderH264Nvenc(Option<string> maybeVideoProfile, Option<string> maybeVideoPreset) : EncoderBase
{ {
public override string Name => "h264_nvenc"; public override string Name => "h264_nvenc";
public override StreamKind Kind => StreamKind.Video; public override StreamKind Kind => StreamKind.Video;
@ -11,16 +11,24 @@ public class EncoderH264Nvenc(Option<string> maybeVideoProfile) : EncoderBase
{ {
get get
{ {
var result = new List<string>(base.OutputOptions);
foreach (string videoProfile in maybeVideoProfile) foreach (string videoProfile in maybeVideoProfile)
{ {
return result.Add("-profile:v");
[ result.Add(videoProfile.ToLowerInvariant());
"-c:v", Name,
"-profile:v", videoProfile.ToLowerInvariant(),
];
} }
return base.OutputOptions; foreach (string videoPreset in maybeVideoPreset)
{
if (!string.IsNullOrWhiteSpace(videoPreset))
{
result.Add("-preset:v");
result.Add(videoPreset);
}
}
return result.ToArray();
} }
} }

29
ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs

@ -5,10 +5,12 @@ namespace ErsatzTV.FFmpeg.Encoder.Nvenc;
public class EncoderHevcNvenc : EncoderBase public class EncoderHevcNvenc : EncoderBase
{ {
private readonly Option<string> _maybeVideoPreset;
private readonly bool _bFrames; private readonly bool _bFrames;
public EncoderHevcNvenc(IHardwareCapabilities hardwareCapabilities) public EncoderHevcNvenc(IHardwareCapabilities hardwareCapabilities, Option<string> maybeVideoPreset)
{ {
_maybeVideoPreset = maybeVideoPreset;
if (hardwareCapabilities is NvidiaHardwareCapabilities nvidia) if (hardwareCapabilities is NvidiaHardwareCapabilities nvidia)
{ {
_bFrames = nvidia.HevcBFrames; _bFrames = nvidia.HevcBFrames;
@ -18,8 +20,29 @@ public class EncoderHevcNvenc : EncoderBase
public override string Name => "hevc_nvenc"; public override string Name => "hevc_nvenc";
public override StreamKind Kind => StreamKind.Video; public override StreamKind Kind => StreamKind.Video;
public override string[] OutputOptions => public override string[] OutputOptions
new[] { "-c:v", "hevc_nvenc", "-tag:v", "hvc1", "-b_ref_mode", _bFrames ? "1" : "0" }; {
get
{
var result = new List<string>
{
"-c:v", "hevc_nvenc",
"-tag:v", "hvc1",
"-b_ref_mode", _bFrames ? "1" : "0"
};
foreach (string videoPreset in _maybeVideoPreset)
{
if (!string.IsNullOrWhiteSpace(videoPreset))
{
result.Add("-preset:v");
result.Add(videoPreset);
}
}
return result.ToArray();
}
}
public override FrameState NextState(FrameState currentState) => currentState with public override FrameState NextState(FrameState currentState) => currentState with
{ {

24
ErsatzTV.FFmpeg/Encoder/Qsv/EncoderH264Qsv.cs

@ -2,7 +2,7 @@
namespace ErsatzTV.FFmpeg.Encoder.Qsv; namespace ErsatzTV.FFmpeg.Encoder.Qsv;
public class EncoderH264Qsv(Option<string> maybeVideoProfile) : EncoderBase public class EncoderH264Qsv(Option<string> maybeVideoProfile, Option<string> maybeVideoPreset) : EncoderBase
{ {
public override string Name => "h264_qsv"; public override string Name => "h264_qsv";
public override StreamKind Kind => StreamKind.Video; public override StreamKind Kind => StreamKind.Video;
@ -11,18 +11,24 @@ public class EncoderH264Qsv(Option<string> maybeVideoProfile) : EncoderBase
{ {
get get
{ {
var result = new List<string> { "-c:v", Name, "-low_power", "0", "-look_ahead", "0" };
foreach (string videoProfile in maybeVideoProfile) foreach (string videoProfile in maybeVideoProfile)
{ {
return result.Add("-profile:v");
[ result.Add(videoProfile.ToLowerInvariant());
"-c:v", Name, }
"-low_power", "0",
"-look_ahead", "0", foreach (string videoPreset in maybeVideoPreset)
"-profile:v", videoProfile.ToLowerInvariant(), {
]; if (!string.IsNullOrWhiteSpace(videoPreset))
{
result.Add("-preset:v");
result.Add(videoPreset);
}
} }
return ["-c:v", Name, "-low_power", "0", "-look_ahead", "0"]; return result.ToArray();
} }
} }

22
ErsatzTV.FFmpeg/Encoder/Qsv/EncoderHevcQsv.cs

@ -2,13 +2,29 @@
namespace ErsatzTV.FFmpeg.Encoder.Qsv; namespace ErsatzTV.FFmpeg.Encoder.Qsv;
public class EncoderHevcQsv : EncoderBase public class EncoderHevcQsv(Option<string> maybeVideoPreset) : EncoderBase
{ {
public override string Name => "hevc_qsv"; public override string Name => "hevc_qsv";
public override StreamKind Kind => StreamKind.Video; public override StreamKind Kind => StreamKind.Video;
public override string[] OutputOptions => public override string[] OutputOptions
new[] { "-c:v", "hevc_qsv", "-low_power", "0", "-look_ahead", "0" }; {
get
{
var result = new List<string> { "-c:v", Name, "-low_power", "0", "-look_ahead", "0" };
foreach (string videoPreset in maybeVideoPreset)
{
if (!string.IsNullOrWhiteSpace(videoPreset))
{
result.Add("-preset:v");
result.Add(videoPreset);
}
}
return result.ToArray();
}
}
public override FrameState NextState(FrameState currentState) => currentState with public override FrameState NextState(FrameState currentState) => currentState with
{ {

1
ErsatzTV.FFmpeg/FrameState.cs

@ -7,6 +7,7 @@ public record FrameState(
bool InfiniteLoop, bool InfiniteLoop,
string VideoFormat, string VideoFormat,
Option<string> VideoProfile, Option<string> VideoProfile,
Option<string> VideoPreset,
Option<IPixelFormat> PixelFormat, Option<IPixelFormat> PixelFormat,
FrameSize ScaledSize, FrameSize ScaledSize,
FrameSize PaddedSize, FrameSize PaddedSize,

4
ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs

@ -250,9 +250,9 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
(ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch (ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch
{ {
(HardwareAccelerationMode.Nvenc, VideoFormat.Hevc) => (HardwareAccelerationMode.Nvenc, VideoFormat.Hevc) =>
new EncoderHevcNvenc(_hardwareCapabilities), new EncoderHevcNvenc(_hardwareCapabilities, desiredState.VideoPreset),
(HardwareAccelerationMode.Nvenc, VideoFormat.H264) => (HardwareAccelerationMode.Nvenc, VideoFormat.H264) =>
new EncoderH264Nvenc(desiredState.VideoProfile), new EncoderH264Nvenc(desiredState.VideoProfile, desiredState.VideoPreset),
(_, _) => GetSoftwareEncoder(ffmpegState, currentState, desiredState) (_, _) => GetSoftwareEncoder(ffmpegState, currentState, desiredState)
}; };

4
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -574,8 +574,8 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
return desiredState.VideoFormat switch return desiredState.VideoFormat switch
{ {
VideoFormat.Hevc => new EncoderLibx265( VideoFormat.Hevc => new EncoderLibx265(
currentState with { FrameDataLocation = FrameDataLocation.Software }), currentState with { FrameDataLocation = FrameDataLocation.Software }, desiredState.VideoPreset),
VideoFormat.H264 => new EncoderLibx264(desiredState.VideoProfile), VideoFormat.H264 => new EncoderLibx264(desiredState.VideoProfile, desiredState.VideoPreset),
VideoFormat.Mpeg2Video => new EncoderMpeg2Video(), VideoFormat.Mpeg2Video => new EncoderMpeg2Video(),
VideoFormat.Copy => new EncoderCopyVideo(), VideoFormat.Copy => new EncoderCopyVideo(),

6
ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs

@ -211,8 +211,10 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
Option<IEncoder> maybeEncoder = Option<IEncoder> maybeEncoder =
(ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch (ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch
{ {
(HardwareAccelerationMode.Qsv, VideoFormat.Hevc) => new EncoderHevcQsv(), (HardwareAccelerationMode.Qsv, VideoFormat.Hevc) => new EncoderHevcQsv(desiredState.VideoPreset),
(HardwareAccelerationMode.Qsv, VideoFormat.H264) => new EncoderH264Qsv(desiredState.VideoProfile), (HardwareAccelerationMode.Qsv, VideoFormat.H264) => new EncoderH264Qsv(
desiredState.VideoProfile,
desiredState.VideoPreset),
(HardwareAccelerationMode.Qsv, VideoFormat.Mpeg2Video) => new EncoderMpeg2Qsv(), (HardwareAccelerationMode.Qsv, VideoFormat.Mpeg2Video) => new EncoderMpeg2Qsv(),
(_, _) => GetSoftwareEncoder(ffmpegState, currentState, desiredState) (_, _) => GetSoftwareEncoder(ffmpegState, currentState, desiredState)

31
ErsatzTV.FFmpeg/Preset/AvailablePresets.cs

@ -0,0 +1,31 @@
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Preset;
public static class AvailablePresets
{
public static ICollection<string> ForAccelAndFormat(
HardwareAccelerationMode hardwareAccelerationMode,
string videoFormat)
{
return (hardwareAccelerationMode, videoFormat) switch
{
(HardwareAccelerationMode.Nvenc, VideoFormat.H264 or VideoFormat.Hevc) =>
[
VideoPreset.LowLatencyHighPerformance, VideoPreset.LowLatencyHighQuality
],
(HardwareAccelerationMode.Qsv, VideoFormat.H264 or VideoFormat.Hevc) =>
[
VideoPreset.VeryFast
],
(HardwareAccelerationMode.None, VideoFormat.H264 or VideoFormat.Hevc) =>
[
VideoPreset.VeryFast
],
_ => Array.Empty<string>()
};
}
}

13
ErsatzTV.FFmpeg/Preset/VideoPreset.cs

@ -0,0 +1,13 @@
namespace ErsatzTV.FFmpeg.Preset;
public static class VideoPreset
{
public const string Unset = "";
// NVENC
public const string LowLatencyHighPerformance = "llhp";
public const string LowLatencyHighQuality = "llhq";
// x264
public const string VeryFast = "veryfast";
}

5596
ErsatzTV.Infrastructure.MySql/Migrations/20240423191402_Add_FFmpegProfile_VideoPreset.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.MySql/Migrations/20240423191402_Add_FFmpegProfile_VideoPreset.cs

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_FFmpegProfile_VideoPreset : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "VideoPreset",
table: "FFmpegProfile",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "VideoPreset",
table: "FFmpegProfile");
}
}
}

3
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -643,6 +643,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int>("VideoFormat") b.Property<int>("VideoFormat")
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("VideoPreset")
.HasColumnType("longtext");
b.Property<string>("VideoProfile") b.Property<string>("VideoProfile")
.HasColumnType("longtext"); .HasColumnType("longtext");

5441
ErsatzTV.Infrastructure.Sqlite/Migrations/20240423185231_Add_FFmpegProfile_VideoPreset.Designer.cs generated

File diff suppressed because it is too large Load Diff

28
ErsatzTV.Infrastructure.Sqlite/Migrations/20240423185231_Add_FFmpegProfile_VideoPreset.cs

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_FFmpegProfile_VideoPreset : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "VideoPreset",
table: "FFmpegProfile",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "VideoPreset",
table: "FFmpegProfile");
}
}
}

3
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -612,6 +612,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int>("VideoFormat") b.Property<int>("VideoFormat")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("VideoPreset")
.HasColumnType("TEXT");
b.Property<string>("VideoProfile") b.Property<string>("VideoProfile")
.HasColumnType("TEXT"); .HasColumnType("TEXT");

37
ErsatzTV/Pages/FFmpegEditor.razor

@ -5,7 +5,9 @@
@using ErsatzTV.Application.FFmpegProfiles @using ErsatzTV.Application.FFmpegProfiles
@using System.Runtime.InteropServices @using System.Runtime.InteropServices
@using ErsatzTV.Core.FFmpeg @using ErsatzTV.Core.FFmpeg
@using ErsatzTV.FFmpeg
@using ErsatzTV.FFmpeg.Format @using ErsatzTV.FFmpeg.Format
@using ErsatzTV.FFmpeg.Preset
@implements IDisposable @implements IDisposable
@inject NavigationManager _navigationManager @inject NavigationManager _navigationManager
@inject ILogger<FFmpegEditor> _logger @inject ILogger<FFmpegEditor> _logger
@ -65,6 +67,22 @@
<MudSelectItem Value="@VideoProfile.Main">main</MudSelectItem> <MudSelectItem Value="@VideoProfile.Main">main</MudSelectItem>
<MudSelectItem Value="@VideoProfile.High">high</MudSelectItem> <MudSelectItem Value="@VideoProfile.High">high</MudSelectItem>
</MudSelect> </MudSelect>
@{
ICollection<string> presets = AvailablePresets.ForAccelAndFormat(MapAccel(_model.HardwareAcceleration), MapVideoFormat(_model.VideoFormat));
}
<MudSelect Label="Preset"
@bind-Value="_model.VideoPreset"
For="@(() => _model.VideoPreset)"
Disabled="@(presets.Count == 0)"
Clearable="true">
@foreach (string preset in presets)
{
if (!string.IsNullOrWhiteSpace(preset))
{
<MudSelectItem Value="@preset">@preset</MudSelectItem>
}
}
</MudSelect>
<MudSelect Label="Bit Depth" @bind-Value="_model.BitDepth" For="@(() => _model.BitDepth)"> <MudSelect Label="Bit Depth" @bind-Value="_model.BitDepth" For="@(() => _model.BitDepth)">
<MudSelectItem Value="@FFmpegProfileBitDepth.EightBit">8-bit</MudSelectItem> <MudSelectItem Value="@FFmpegProfileBitDepth.EightBit">8-bit</MudSelectItem>
<MudSelectItem Value="@FFmpegProfileBitDepth.TenBit">10-bit</MudSelectItem> <MudSelectItem Value="@FFmpegProfileBitDepth.TenBit">10-bit</MudSelectItem>
@ -283,4 +301,23 @@
} }
} }
private static HardwareAccelerationMode MapAccel(HardwareAccelerationKind kind) =>
kind switch
{
HardwareAccelerationKind.Amf => HardwareAccelerationMode.Amf,
HardwareAccelerationKind.Nvenc => HardwareAccelerationMode.Nvenc,
HardwareAccelerationKind.Qsv => HardwareAccelerationMode.Qsv,
HardwareAccelerationKind.Vaapi => HardwareAccelerationMode.Vaapi,
HardwareAccelerationKind.VideoToolbox => HardwareAccelerationMode.VideoToolbox,
_ => HardwareAccelerationMode.None
};
private static string MapVideoFormat(FFmpegProfileVideoFormat format) =>
format switch
{
FFmpegProfileVideoFormat.H264 => VideoFormat.H264,
FFmpegProfileVideoFormat.Hevc => VideoFormat.Hevc,
_ => VideoFormat.Mpeg2Video
};
} }

4
ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

@ -34,6 +34,7 @@ public class FFmpegProfileEditViewModel
VideoBufferSize = viewModel.VideoBufferSize; VideoBufferSize = viewModel.VideoBufferSize;
VideoFormat = viewModel.VideoFormat; VideoFormat = viewModel.VideoFormat;
VideoProfile = viewModel.VideoProfile; VideoProfile = viewModel.VideoProfile;
VideoPreset = viewModel.VideoPreset;
BitDepth = viewModel.BitDepth; BitDepth = viewModel.BitDepth;
} }
@ -58,6 +59,7 @@ public class FFmpegProfileEditViewModel
public int VideoBufferSize { get; set; } public int VideoBufferSize { get; set; }
public FFmpegProfileVideoFormat VideoFormat { get; set; } public FFmpegProfileVideoFormat VideoFormat { get; set; }
public string VideoProfile { get; set; } public string VideoProfile { get; set; }
public string VideoPreset { get; set; }
public FFmpegProfileBitDepth BitDepth { get; set; } public FFmpegProfileBitDepth BitDepth { get; set; }
public CreateFFmpegProfile ToCreate() => public CreateFFmpegProfile ToCreate() =>
@ -72,6 +74,7 @@ public class FFmpegProfileEditViewModel
ScalingBehavior, ScalingBehavior,
VideoFormat, VideoFormat,
VideoProfile, VideoProfile,
VideoPreset,
BitDepth, BitDepth,
VideoBitrate, VideoBitrate,
VideoBufferSize, VideoBufferSize,
@ -98,6 +101,7 @@ public class FFmpegProfileEditViewModel
ScalingBehavior, ScalingBehavior,
VideoFormat, VideoFormat,
VideoProfile, VideoProfile,
VideoPreset,
BitDepth, BitDepth,
VideoBitrate, VideoBitrate,
VideoBufferSize, VideoBufferSize,

Loading…
Cancel
Save