Browse Source

add h264 profile option to ffmpeg profile (#1686)

* add video profile for nvenc/software h264 encoders

* add h264 profile for all other encoders

* update changelog
pull/1687/head
Jason Dove 1 year ago committed by GitHub
parent
commit
c1d6ddcc57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      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. 33
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  10. 19
      ErsatzTV.FFmpeg/Encoder/Amf/EncoderH264Amf.cs
  11. 19
      ErsatzTV.FFmpeg/Encoder/EncoderLibx264.cs
  12. 19
      ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs
  13. 22
      ErsatzTV.FFmpeg/Encoder/Qsv/EncoderH264Qsv.cs
  14. 14
      ErsatzTV.FFmpeg/Encoder/Vaapi/EncoderH264Vaapi.cs
  15. 19
      ErsatzTV.FFmpeg/Encoder/VideoToolbox/EncoderH264VideoToolbox.cs
  16. 1
      ErsatzTV.FFmpeg/Format/VideoProfile.cs
  17. 2
      ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs
  18. 6
      ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs
  19. 2
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs
  20. 2
      ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs
  21. 9
      ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs
  22. 2
      ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs
  23. 5593
      ErsatzTV.Infrastructure.MySql/Migrations/20240422144541_Add_FFmpegProfile_VideoProfile.Designer.cs
  24. 31
      ErsatzTV.Infrastructure.MySql/Migrations/20240422144541_Add_FFmpegProfile_VideoProfile.cs
  25. 3
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  26. 5438
      ErsatzTV.Infrastructure.Sqlite/Migrations/20240422015853_Add_FFmpegProfile_VideoProfile.Designer.cs
  27. 30
      ErsatzTV.Infrastructure.Sqlite/Migrations/20240422015853_Add_FFmpegProfile_VideoProfile.cs
  28. 3
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  29. 8
      ErsatzTV/Pages/FFmpegEditor.razor
  30. 4
      ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

2
CHANGELOG.md

@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- ETV versions starting with v0.8.5-beta (using dotnet 8) store config data in `$HOME/Library/Application Support/ersatztv` - ETV versions starting with v0.8.5-beta (using dotnet 8) store config data in `$HOME/Library/Application Support/ersatztv`
- 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
### 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
@ -43,6 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `Something.en.sdh.srt` - `Something.en.sdh.srt`
- Fix playback from Jellyfin 10.9 by allowing playlist HTTP HEAD requests - Fix playback from Jellyfin 10.9 by allowing playlist HTTP HEAD requests
- Fix `HLS Segmenter V2` segment duration (previously 10s, now 4s) - Fix `HLS Segmenter V2` segment duration (previously 10s, now 4s)
- Fix `HLS Segmenter V2` error video generation
### Changed ### Changed
- Use ffmpeg 7 in all docker images - Use ffmpeg 7 in all docker images

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

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

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

@ -54,6 +54,7 @@ public class CreateFFmpegProfileHandler :
ResolutionId = resolutionId, ResolutionId = resolutionId,
ScalingBehavior = request.ScalingBehavior, ScalingBehavior = request.ScalingBehavior,
VideoFormat = request.VideoFormat, VideoFormat = request.VideoFormat,
VideoProfile = request.VideoProfile,
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

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

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

@ -42,6 +42,7 @@ public class
p.ResolutionId = update.ResolutionId; p.ResolutionId = update.ResolutionId;
p.ScalingBehavior = update.ScalingBehavior; p.ScalingBehavior = update.ScalingBehavior;
p.VideoFormat = update.VideoFormat; p.VideoFormat = update.VideoFormat;
p.VideoProfile = update.VideoProfile;
// 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

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

1
ErsatzTV.Application/FFmpegProfiles/Mapper.cs

@ -17,6 +17,7 @@ internal static class Mapper
Resolutions.Mapper.ProjectToViewModel(profile.Resolution), Resolutions.Mapper.ProjectToViewModel(profile.Resolution),
profile.ScalingBehavior, profile.ScalingBehavior,
profile.VideoFormat, profile.VideoFormat,
profile.VideoProfile,
profile.BitDepth, profile.BitDepth,
profile.VideoBitrate, profile.VideoBitrate,
profile.VideoBufferSize, profile.VideoBufferSize,

2
ErsatzTV.Core/Domain/FFmpegProfile.cs

@ -15,6 +15,7 @@ public record FFmpegProfile
public Resolution Resolution { get; set; } public Resolution Resolution { get; set; }
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 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; }
@ -35,6 +36,7 @@ public record FFmpegProfile
ResolutionId = resolution.Id, ResolutionId = resolution.Id,
Resolution = resolution, Resolution = resolution,
VideoFormat = FFmpegProfileVideoFormat.H264, VideoFormat = FFmpegProfileVideoFormat.H264,
VideoProfile = "high",
AudioFormat = FFmpegProfileAudioFormat.Aac, AudioFormat = FFmpegProfileAudioFormat.Aac,
VideoBitrate = 2000, VideoBitrate = 2000,
VideoBufferSize = 4000, VideoBufferSize = 4000,

33
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -304,6 +304,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
Option<WatermarkInputFile> watermarkInputFile = GetWatermarkInputFile(watermarkOptions, maybeFadePoints); Option<WatermarkInputFile> watermarkInputFile = GetWatermarkInputFile(watermarkOptions, maybeFadePoints);
string videoFormat = GetVideoFormat(playbackSettings); string videoFormat = GetVideoFormat(playbackSettings);
Option<string> maybeVideoProfile = GetVideoProfile(videoFormat, channel.FFmpegProfile.VideoProfile);
HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode(playbackSettings, fillerKind); HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode(playbackSettings, fillerKind);
@ -343,7 +344,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
playbackSettings.RealtimeOutput, playbackSettings.RealtimeOutput,
fillerKind == FillerKind.Fallback, fillerKind == FillerKind.Fallback,
videoFormat, videoFormat,
Optional(videoStream.Profile), maybeVideoProfile,
Optional(playbackSettings.PixelFormat), Optional(playbackSettings.PixelFormat),
scaledSize, scaledSize,
paddedSize, paddedSize,
@ -444,11 +445,13 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
Option<TimeSpan>.None, Option<TimeSpan>.None,
AudioFilter.None); AudioFilter.None);
var videoFormat = GetVideoFormat(playbackSettings);
var desiredState = new FrameState( var desiredState = new FrameState(
playbackSettings.RealtimeOutput, playbackSettings.RealtimeOutput,
false, false,
GetVideoFormat(playbackSettings), videoFormat,
VideoProfile.Main, GetVideoProfile(videoFormat, channel.FFmpegProfile.VideoProfile),
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),
@ -460,9 +463,16 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
playbackSettings.VideoTrackTimeScale, playbackSettings.VideoTrackTimeScale,
playbackSettings.Deinterlace); playbackSettings.Deinterlace);
OutputFormatKind outputFormat = channel.StreamingMode == StreamingMode.HttpLiveStreamingSegmenter OutputFormatKind outputFormat = OutputFormatKind.MpegTs;
? OutputFormatKind.Hls switch (channel.StreamingMode)
: OutputFormatKind.MpegTs; {
case StreamingMode.HttpLiveStreamingSegmenter:
outputFormat = OutputFormatKind.Hls;
break;
case StreamingMode.HttpLiveStreamingSegmenterV2:
outputFormat = OutputFormatKind.Nut;
break;
}
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")
@ -646,6 +656,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
Option<WatermarkInputFile> watermarkInputFile = Option<WatermarkInputFile>.None; Option<WatermarkInputFile> watermarkInputFile = Option<WatermarkInputFile>.None;
string videoFormat = GetVideoFormat(playbackSettings); string videoFormat = GetVideoFormat(playbackSettings);
Option<string> maybeVideoProfile = GetVideoProfile(videoFormat, channel.FFmpegProfile.VideoProfile);
HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode(playbackSettings, FillerKind.None); HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode(playbackSettings, FillerKind.None);
@ -664,7 +675,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
playbackSettings.RealtimeOutput, playbackSettings.RealtimeOutput,
true, true,
videoFormat, videoFormat,
Option<string>.None, maybeVideoProfile,
Optional(playbackSettings.PixelFormat), Optional(playbackSettings.PixelFormat),
resolution, resolution,
resolution, resolution,
@ -980,6 +991,14 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
_ => throw new ArgumentOutOfRangeException($"unexpected video format {playbackSettings.VideoFormat}") _ => throw new ArgumentOutOfRangeException($"unexpected video format {playbackSettings.VideoFormat}")
}; };
private static Option<string> GetVideoProfile(string videoFormat, string videoProfile) =>
(videoFormat, videoProfile.ToLowerInvariant()) switch
{
(VideoFormat.H264, VideoProfile.Main) => VideoProfile.Main,
(VideoFormat.H264, VideoProfile.High) => VideoProfile.High,
_ => Option<string>.None
};
private static HardwareAccelerationMode GetHardwareAccelerationMode( private static HardwareAccelerationMode GetHardwareAccelerationMode(
FFmpegPlaybackSettings playbackSettings, FFmpegPlaybackSettings playbackSettings,
FillerKind fillerKind) => FillerKind fillerKind) =>

19
ErsatzTV.FFmpeg/Encoder/Amf/EncoderH264Amf.cs

@ -2,10 +2,27 @@
namespace ErsatzTV.FFmpeg.Encoder.Amf; namespace ErsatzTV.FFmpeg.Encoder.Amf;
public class EncoderH264Amf : EncoderBase public class EncoderH264Amf(Option<string> maybeVideoProfile) : EncoderBase
{ {
public override string Name => "h264_amf"; public override string Name => "h264_amf";
public override StreamKind Kind => StreamKind.Video; public override StreamKind Kind => StreamKind.Video;
public override string[] OutputOptions
{
get
{
foreach (string videoProfile in maybeVideoProfile)
{
return
[
"-c:v", Name,
"-profile:v", videoProfile.ToLowerInvariant(),
];
}
return base.OutputOptions;
}
}
public override FrameState NextState(FrameState currentState) => currentState with public override FrameState NextState(FrameState currentState) => currentState with
{ {

19
ErsatzTV.FFmpeg/Encoder/EncoderLibx264.cs

@ -2,10 +2,27 @@
namespace ErsatzTV.FFmpeg.Encoder; namespace ErsatzTV.FFmpeg.Encoder;
public class EncoderLibx264 : EncoderBase public class EncoderLibx264(Option<string> maybeVideoProfile) : EncoderBase
{ {
public override string Name => "libx264"; public override string Name => "libx264";
public override StreamKind Kind => StreamKind.Video; public override StreamKind Kind => StreamKind.Video;
public override string[] OutputOptions
{
get
{
foreach (string videoProfile in maybeVideoProfile)
{
return
[
"-c:v", Name,
"-profile:v", videoProfile.ToLowerInvariant()
];
}
return base.OutputOptions;
}
}
public override FrameState NextState(FrameState currentState) => public override FrameState NextState(FrameState currentState) =>
currentState with { VideoFormat = VideoFormat.H264 }; currentState with { VideoFormat = VideoFormat.H264 };

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

@ -2,11 +2,28 @@
namespace ErsatzTV.FFmpeg.Encoder.Nvenc; namespace ErsatzTV.FFmpeg.Encoder.Nvenc;
public class EncoderH264Nvenc : EncoderBase public class EncoderH264Nvenc(Option<string> maybeVideoProfile) : 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;
public override string[] OutputOptions
{
get
{
foreach (string videoProfile in maybeVideoProfile)
{
return
[
"-c:v", Name,
"-profile:v", videoProfile.ToLowerInvariant(),
];
}
return base.OutputOptions;
}
}
public override FrameState NextState(FrameState currentState) => currentState with public override FrameState NextState(FrameState currentState) => currentState with
{ {
VideoFormat = VideoFormat.H264 VideoFormat = VideoFormat.H264

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

@ -2,13 +2,29 @@
namespace ErsatzTV.FFmpeg.Encoder.Qsv; namespace ErsatzTV.FFmpeg.Encoder.Qsv;
public class EncoderH264Qsv : EncoderBase public class EncoderH264Qsv(Option<string> maybeVideoProfile) : 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;
public override string[] OutputOptions => public override string[] OutputOptions
new[] { "-c:v", "h264_qsv", "-low_power", "0", "-look_ahead", "0" }; {
get
{
foreach (string videoProfile in maybeVideoProfile)
{
return
[
"-c:v", Name,
"-low_power", "0",
"-look_ahead", "0",
"-profile:v", videoProfile.ToLowerInvariant(),
];
}
return ["-c:v", Name, "-low_power", "0", "-look_ahead", "0"];
}
}
public override FrameState NextState(FrameState currentState) => currentState with public override FrameState NextState(FrameState currentState) => currentState with
{ {

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

@ -2,12 +2,8 @@
namespace ErsatzTV.FFmpeg.Encoder.Vaapi; namespace ErsatzTV.FFmpeg.Encoder.Vaapi;
public class EncoderH264Vaapi : EncoderBase public class EncoderH264Vaapi(Option<string> maybeVideoProfile, RateControlMode rateControlMode) : EncoderBase
{ {
private readonly RateControlMode _rateControlMode;
public EncoderH264Vaapi(RateControlMode rateControlMode) => _rateControlMode = rateControlMode;
public override string Name => "h264_vaapi"; public override string Name => "h264_vaapi";
public override StreamKind Kind => StreamKind.Video; public override StreamKind Kind => StreamKind.Video;
@ -18,7 +14,7 @@ public class EncoderH264Vaapi : EncoderBase
{ {
var result = new List<string>(base.OutputOptions); var result = new List<string>(base.OutputOptions);
if (_rateControlMode == RateControlMode.CQP) if (rateControlMode == RateControlMode.CQP)
{ {
result.Add("-rc_mode"); result.Add("-rc_mode");
result.Add("1"); result.Add("1");
@ -26,6 +22,12 @@ public class EncoderH264Vaapi : EncoderBase
result.Add("-sei"); result.Add("-sei");
result.Add("-a53_cc"); result.Add("-a53_cc");
foreach (string videoProfile in maybeVideoProfile)
{
result.Add("-profile:v");
result.Add(videoProfile.ToLowerInvariant());
}
return result.ToArray(); return result.ToArray();
} }

19
ErsatzTV.FFmpeg/Encoder/VideoToolbox/EncoderH264VideoToolbox.cs

@ -2,10 +2,27 @@
namespace ErsatzTV.FFmpeg.Encoder.VideoToolbox; namespace ErsatzTV.FFmpeg.Encoder.VideoToolbox;
public class EncoderH264VideoToolbox : EncoderBase public class EncoderH264VideoToolbox(Option<string> maybeVideoProfile) : EncoderBase
{ {
public override string Name => "h264_videotoolbox"; public override string Name => "h264_videotoolbox";
public override StreamKind Kind => StreamKind.Video; public override StreamKind Kind => StreamKind.Video;
public override string[] OutputOptions
{
get
{
foreach (string videoProfile in maybeVideoProfile)
{
return
[
"-c:v", Name,
"-profile:v", videoProfile.ToLowerInvariant(),
];
}
return base.OutputOptions;
}
}
public override FrameState NextState(FrameState currentState) => currentState with public override FrameState NextState(FrameState currentState) => currentState with
{ {

1
ErsatzTV.FFmpeg/Format/VideoProfile.cs

@ -3,4 +3,5 @@ namespace ErsatzTV.FFmpeg.Format;
public static class VideoProfile public static class VideoProfile
{ {
public const string Main = "main"; public const string Main = "main";
public const string High = "high";
} }

2
ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs

@ -80,7 +80,7 @@ public class AmfPipelineBuilder : SoftwarePipelineBuilder
(HardwareAccelerationMode.Amf, VideoFormat.Hevc) => (HardwareAccelerationMode.Amf, VideoFormat.Hevc) =>
new EncoderHevcAmf(), new EncoderHevcAmf(),
(HardwareAccelerationMode.Amf, VideoFormat.H264) => (HardwareAccelerationMode.Amf, VideoFormat.H264) =>
new EncoderH264Amf(), new EncoderH264Amf(desiredState.VideoProfile),
_ => GetSoftwareEncoder(ffmpegState, currentState, desiredState) _ => GetSoftwareEncoder(ffmpegState, currentState, desiredState)
}; };

6
ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs

@ -249,8 +249,10 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
Option<IEncoder> maybeEncoder = Option<IEncoder> maybeEncoder =
(ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch (ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch
{ {
(HardwareAccelerationMode.Nvenc, VideoFormat.Hevc) => new EncoderHevcNvenc(_hardwareCapabilities), (HardwareAccelerationMode.Nvenc, VideoFormat.Hevc) =>
(HardwareAccelerationMode.Nvenc, VideoFormat.H264) => new EncoderH264Nvenc(), new EncoderHevcNvenc(_hardwareCapabilities),
(HardwareAccelerationMode.Nvenc, VideoFormat.H264) =>
new EncoderH264Nvenc(desiredState.VideoProfile),
(_, _) => GetSoftwareEncoder(ffmpegState, currentState, desiredState) (_, _) => GetSoftwareEncoder(ffmpegState, currentState, desiredState)
}; };

2
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

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

2
ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs

@ -212,7 +212,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
(ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch (ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch
{ {
(HardwareAccelerationMode.Qsv, VideoFormat.Hevc) => new EncoderHevcQsv(), (HardwareAccelerationMode.Qsv, VideoFormat.Hevc) => new EncoderHevcQsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.H264) => new EncoderH264Qsv(), (HardwareAccelerationMode.Qsv, VideoFormat.H264) => new EncoderH264Qsv(desiredState.VideoProfile),
(HardwareAccelerationMode.Qsv, VideoFormat.Mpeg2Video) => new EncoderMpeg2Qsv(), (HardwareAccelerationMode.Qsv, VideoFormat.Mpeg2Video) => new EncoderMpeg2Qsv(),
(_, _) => GetSoftwareEncoder(ffmpegState, currentState, desiredState) (_, _) => GetSoftwareEncoder(ffmpegState, currentState, desiredState)

9
ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs

@ -230,9 +230,12 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
Option<IEncoder> maybeEncoder = Option<IEncoder> maybeEncoder =
(ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch (ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch
{ {
(HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) => new EncoderHevcVaapi(rateControlMode), (HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) =>
(HardwareAccelerationMode.Vaapi, VideoFormat.H264) => new EncoderH264Vaapi(rateControlMode), new EncoderHevcVaapi(rateControlMode),
(HardwareAccelerationMode.Vaapi, VideoFormat.Mpeg2Video) => new EncoderMpeg2Vaapi(rateControlMode), (HardwareAccelerationMode.Vaapi, VideoFormat.H264) =>
new EncoderH264Vaapi(desiredState.VideoProfile, rateControlMode),
(HardwareAccelerationMode.Vaapi, VideoFormat.Mpeg2Video) =>
new EncoderMpeg2Vaapi(rateControlMode),
(_, _) => GetSoftwareEncoder(ffmpegState, currentState, desiredState) (_, _) => GetSoftwareEncoder(ffmpegState, currentState, desiredState)
}; };

2
ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs

@ -103,7 +103,7 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder
(HardwareAccelerationMode.VideoToolbox, VideoFormat.Hevc) => (HardwareAccelerationMode.VideoToolbox, VideoFormat.Hevc) =>
new EncoderHevcVideoToolbox(desiredState.BitDepth), new EncoderHevcVideoToolbox(desiredState.BitDepth),
(HardwareAccelerationMode.VideoToolbox, VideoFormat.H264) => (HardwareAccelerationMode.VideoToolbox, VideoFormat.H264) =>
new EncoderH264VideoToolbox(), new EncoderH264VideoToolbox(desiredState.VideoProfile),
_ => GetSoftwareEncoder(ffmpegState, currentState, desiredState) _ => GetSoftwareEncoder(ffmpegState, currentState, desiredState)
}; };

5593
ErsatzTV.Infrastructure.MySql/Migrations/20240422144541_Add_FFmpegProfile_VideoProfile.Designer.cs generated

File diff suppressed because it is too large Load Diff

31
ErsatzTV.Infrastructure.MySql/Migrations/20240422144541_Add_FFmpegProfile_VideoProfile.cs

@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_FFmpegProfile_VideoProfile : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "VideoProfile",
table: "FFmpegProfile",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.Sql("UPDATE FFmpegProfile SET VideoProfile = 'high'");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "VideoProfile",
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>("VideoProfile")
.HasColumnType("longtext");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("ResolutionId"); b.HasIndex("ResolutionId");

5438
ErsatzTV.Infrastructure.Sqlite/Migrations/20240422015853_Add_FFmpegProfile_VideoProfile.Designer.cs generated

File diff suppressed because it is too large Load Diff

30
ErsatzTV.Infrastructure.Sqlite/Migrations/20240422015853_Add_FFmpegProfile_VideoProfile.cs

@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_FFmpegProfile_VideoProfile : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "VideoProfile",
table: "FFmpegProfile",
type: "TEXT",
nullable: true);
migrationBuilder.Sql("UPDATE FFmpegProfile SET VideoProfile = 'high'");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "VideoProfile",
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>("VideoProfile")
.HasColumnType("TEXT");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("ResolutionId"); b.HasIndex("ResolutionId");

8
ErsatzTV/Pages/FFmpegEditor.razor

@ -5,6 +5,7 @@
@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.Format
@implements IDisposable @implements IDisposable
@inject NavigationManager _navigationManager @inject NavigationManager _navigationManager
@inject ILogger<FFmpegEditor> _logger @inject ILogger<FFmpegEditor> _logger
@ -57,6 +58,13 @@
<MudSelectItem Value="@FFmpegProfileVideoFormat.Hevc">hevc</MudSelectItem> <MudSelectItem Value="@FFmpegProfileVideoFormat.Hevc">hevc</MudSelectItem>
<MudSelectItem Value="@FFmpegProfileVideoFormat.Mpeg2Video">mpeg-2</MudSelectItem> <MudSelectItem Value="@FFmpegProfileVideoFormat.Mpeg2Video">mpeg-2</MudSelectItem>
</MudSelect> </MudSelect>
<MudSelect Label="Profile"
@bind-Value="_model.VideoProfile"
For="@(() => _model.VideoProfile)"
Disabled="@(_model.VideoFormat != FFmpegProfileVideoFormat.H264 || (_model.HardwareAcceleration != HardwareAccelerationKind.Nvenc && _model.HardwareAcceleration != HardwareAccelerationKind.None))">
<MudSelectItem Value="@VideoProfile.Main">main</MudSelectItem>
<MudSelectItem Value="@VideoProfile.High">high</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>

4
ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

@ -33,6 +33,7 @@ public class FFmpegProfileEditViewModel
VideoBitrate = viewModel.VideoBitrate; VideoBitrate = viewModel.VideoBitrate;
VideoBufferSize = viewModel.VideoBufferSize; VideoBufferSize = viewModel.VideoBufferSize;
VideoFormat = viewModel.VideoFormat; VideoFormat = viewModel.VideoFormat;
VideoProfile = viewModel.VideoProfile;
BitDepth = viewModel.BitDepth; BitDepth = viewModel.BitDepth;
} }
@ -56,6 +57,7 @@ public class FFmpegProfileEditViewModel
public int VideoBitrate { get; set; } public int VideoBitrate { get; set; }
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 FFmpegProfileBitDepth BitDepth { get; set; } public FFmpegProfileBitDepth BitDepth { get; set; }
public CreateFFmpegProfile ToCreate() => public CreateFFmpegProfile ToCreate() =>
@ -69,6 +71,7 @@ public class FFmpegProfileEditViewModel
Resolution.Id, Resolution.Id,
ScalingBehavior, ScalingBehavior,
VideoFormat, VideoFormat,
VideoProfile,
BitDepth, BitDepth,
VideoBitrate, VideoBitrate,
VideoBufferSize, VideoBufferSize,
@ -94,6 +97,7 @@ public class FFmpegProfileEditViewModel
Resolution.Id, Resolution.Id,
ScalingBehavior, ScalingBehavior,
VideoFormat, VideoFormat,
VideoProfile,
BitDepth, BitDepth,
VideoBitrate, VideoBitrate,
VideoBufferSize, VideoBufferSize,

Loading…
Cancel
Save