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. 31
      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/). @@ -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* 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 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
- 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( @@ -15,6 +15,7 @@ public record CreateFFmpegProfile(
ScalingBehavior ScalingBehavior,
FFmpegProfileVideoFormat VideoFormat,
string VideoProfile,
string VideoPreset,
FFmpegProfileBitDepth BitDepth,
int VideoBitrate,
int VideoBufferSize,

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

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

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

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

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

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

1
ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs

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

1
ErsatzTV.Application/FFmpegProfiles/Mapper.cs

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

2
ErsatzTV.Core/Domain/FFmpegProfile.cs

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

22
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

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

5
ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs

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

22
ErsatzTV.FFmpeg/Encoder/EncoderLibx264.cs

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

31
ErsatzTV.FFmpeg/Encoder/EncoderLibx265.cs

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

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

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

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

@ -5,10 +5,12 @@ namespace ErsatzTV.FFmpeg.Encoder.Nvenc; @@ -5,10 +5,12 @@ namespace ErsatzTV.FFmpeg.Encoder.Nvenc;
public class EncoderHevcNvenc : EncoderBase
{
private readonly Option<string> _maybeVideoPreset;
private readonly bool _bFrames;
public EncoderHevcNvenc(IHardwareCapabilities hardwareCapabilities)
public EncoderHevcNvenc(IHardwareCapabilities hardwareCapabilities, Option<string> maybeVideoPreset)
{
_maybeVideoPreset = maybeVideoPreset;
if (hardwareCapabilities is NvidiaHardwareCapabilities nvidia)
{
_bFrames = nvidia.HevcBFrames;
@ -18,8 +20,29 @@ public class EncoderHevcNvenc : EncoderBase @@ -18,8 +20,29 @@ public class EncoderHevcNvenc : EncoderBase
public override string Name => "hevc_nvenc";
public override StreamKind Kind => StreamKind.Video;
public override string[] OutputOptions =>
new[] { "-c:v", "hevc_nvenc", "-tag:v", "hvc1", "-b_ref_mode", _bFrames ? "1" : "0" };
public override string[] OutputOptions
{
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
{

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

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
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 StreamKind Kind => StreamKind.Video;
@ -11,18 +11,24 @@ public class EncoderH264Qsv(Option<string> maybeVideoProfile) : EncoderBase @@ -11,18 +11,24 @@ public class EncoderH264Qsv(Option<string> maybeVideoProfile) : EncoderBase
{
get
{
var result = new List<string> { "-c:v", Name, "-low_power", "0", "-look_ahead", "0" };
foreach (string videoProfile in maybeVideoProfile)
{
return
[
"-c:v", Name,
"-low_power", "0",
"-look_ahead", "0",
"-profile:v", videoProfile.ToLowerInvariant(),
];
result.Add("-profile:v");
result.Add(videoProfile.ToLowerInvariant());
}
foreach (string videoPreset in maybeVideoPreset)
{
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 @@ @@ -2,13 +2,29 @@
namespace ErsatzTV.FFmpeg.Encoder.Qsv;
public class EncoderHevcQsv : EncoderBase
public class EncoderHevcQsv(Option<string> maybeVideoPreset) : EncoderBase
{
public override string Name => "hevc_qsv";
public override StreamKind Kind => StreamKind.Video;
public override string[] OutputOptions =>
new[] { "-c:v", "hevc_qsv", "-low_power", "0", "-look_ahead", "0" };
public override string[] OutputOptions
{
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
{

1
ErsatzTV.FFmpeg/FrameState.cs

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

4
ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs

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

4
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

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

6
ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs

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

31
ErsatzTV.FFmpeg/Preset/AvailablePresets.cs

@ -0,0 +1,31 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ -643,6 +643,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int>("VideoFormat")
.HasColumnType("int");
b.Property<string>("VideoPreset")
.HasColumnType("longtext");
b.Property<string>("VideoProfile")
.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 @@ @@ -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 @@ -612,6 +612,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int>("VideoFormat")
.HasColumnType("INTEGER");
b.Property<string>("VideoPreset")
.HasColumnType("TEXT");
b.Property<string>("VideoProfile")
.HasColumnType("TEXT");

37
ErsatzTV/Pages/FFmpegEditor.razor

@ -5,7 +5,9 @@ @@ -5,7 +5,9 @@
@using ErsatzTV.Application.FFmpegProfiles
@using System.Runtime.InteropServices
@using ErsatzTV.Core.FFmpeg
@using ErsatzTV.FFmpeg
@using ErsatzTV.FFmpeg.Format
@using ErsatzTV.FFmpeg.Preset
@implements IDisposable
@inject NavigationManager _navigationManager
@inject ILogger<FFmpegEditor> _logger
@ -65,6 +67,22 @@ @@ -65,6 +67,22 @@
<MudSelectItem Value="@VideoProfile.Main">main</MudSelectItem>
<MudSelectItem Value="@VideoProfile.High">high</MudSelectItem>
</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)">
<MudSelectItem Value="@FFmpegProfileBitDepth.EightBit">8-bit</MudSelectItem>
<MudSelectItem Value="@FFmpegProfileBitDepth.TenBit">10-bit</MudSelectItem>
@ -283,4 +301,23 @@ @@ -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 @@ -34,6 +34,7 @@ public class FFmpegProfileEditViewModel
VideoBufferSize = viewModel.VideoBufferSize;
VideoFormat = viewModel.VideoFormat;
VideoProfile = viewModel.VideoProfile;
VideoPreset = viewModel.VideoPreset;
BitDepth = viewModel.BitDepth;
}
@ -58,6 +59,7 @@ public class FFmpegProfileEditViewModel @@ -58,6 +59,7 @@ public class FFmpegProfileEditViewModel
public int VideoBufferSize { get; set; }
public FFmpegProfileVideoFormat VideoFormat { get; set; }
public string VideoProfile { get; set; }
public string VideoPreset { get; set; }
public FFmpegProfileBitDepth BitDepth { get; set; }
public CreateFFmpegProfile ToCreate() =>
@ -72,6 +74,7 @@ public class FFmpegProfileEditViewModel @@ -72,6 +74,7 @@ public class FFmpegProfileEditViewModel
ScalingBehavior,
VideoFormat,
VideoProfile,
VideoPreset,
BitDepth,
VideoBitrate,
VideoBufferSize,
@ -98,6 +101,7 @@ public class FFmpegProfileEditViewModel @@ -98,6 +101,7 @@ public class FFmpegProfileEditViewModel
ScalingBehavior,
VideoFormat,
VideoProfile,
VideoPreset,
BitDepth,
VideoBitrate,
VideoBufferSize,

Loading…
Cancel
Save