add ffmpeg profile pad mode (#2775)
* add ffmpeg profile pad mode * update changelog
This commit is contained in:
@@ -10,6 +10,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- fr-FR can match sunday using `day_of_week = 6`
|
||||
- As a complete example, to match Saturday from 9pm (inclusive) to 11pm (exclusive), based on content start time
|
||||
- `content_condition: day_of_week = 6 and (time_of_day_seconds >= 75600 and time_of_day_seconds < 82800)`
|
||||
- Add `Pad Mode` to ffmpeg profile. Options are:
|
||||
- `Hardware If Possible` - default/existing behavior when hardware acceleration is properly configured
|
||||
- `Software` - force software padding
|
||||
- This can be used to work around buggy GPU driver behavior where padding is green instead of black
|
||||
- This is most often seen with VAAPI acceleration (radeonsi or i965 drivers)
|
||||
|
||||
### Fixed
|
||||
- Use code signing on all Windows executables (`ErsatzTV-Windows.exe`, `ErsatzTV.exe`, `ErsatzTV.Scanner.exe`)
|
||||
|
||||
@@ -14,6 +14,7 @@ public record CreateFFmpegProfile(
|
||||
int? QsvExtraHardwareFrames,
|
||||
int ResolutionId,
|
||||
ScalingBehavior ScalingBehavior,
|
||||
FilterMode PadMode,
|
||||
FFmpegProfileVideoFormat VideoFormat,
|
||||
string VideoProfile,
|
||||
string VideoPreset,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.FFmpeg.Pipeline;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using ErsatzTV.Infrastructure.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -54,6 +55,15 @@ public class CreateFFmpegProfileHandler :
|
||||
QsvExtraHardwareFrames = request.QsvExtraHardwareFrames,
|
||||
ResolutionId = resolutionId,
|
||||
ScalingBehavior = request.ScalingBehavior,
|
||||
|
||||
// only allow customization with VAAPI accel
|
||||
PadMode = request.HardwareAcceleration switch
|
||||
{
|
||||
HardwareAccelerationKind.None => FilterMode.Software,
|
||||
HardwareAccelerationKind.Vaapi => request.PadMode,
|
||||
_ => FilterMode.HardwareIfPossible
|
||||
},
|
||||
|
||||
VideoFormat = request.VideoFormat,
|
||||
VideoProfile = request.VideoProfile,
|
||||
VideoPreset = request.VideoPreset,
|
||||
|
||||
@@ -15,6 +15,7 @@ public record UpdateFFmpegProfile(
|
||||
int? QsvExtraHardwareFrames,
|
||||
int ResolutionId,
|
||||
ScalingBehavior ScalingBehavior,
|
||||
FilterMode PadMode,
|
||||
FFmpegProfileVideoFormat VideoFormat,
|
||||
string VideoProfile,
|
||||
string VideoPreset,
|
||||
|
||||
@@ -36,6 +36,7 @@ public class UpdateFFmpegProfileHandler(IDbContextFactory<TvContext> dbContextFa
|
||||
p.QsvExtraHardwareFrames = update.QsvExtraHardwareFrames;
|
||||
p.ResolutionId = update.ResolutionId;
|
||||
p.ScalingBehavior = update.ScalingBehavior;
|
||||
p.PadMode = update.PadMode;
|
||||
p.VideoFormat = update.VideoFormat;
|
||||
p.VideoProfile = update.VideoProfile;
|
||||
p.VideoPreset = update.VideoPreset;
|
||||
@@ -53,6 +54,16 @@ public class UpdateFFmpegProfileHandler(IDbContextFactory<TvContext> dbContextFa
|
||||
p.VideoFormat = FFmpegProfileVideoFormat.Hevc;
|
||||
}
|
||||
|
||||
// only allow customization with VAAPI accel
|
||||
if (p.HardwareAcceleration is HardwareAccelerationKind.None)
|
||||
{
|
||||
p.PadMode = FilterMode.Software;
|
||||
}
|
||||
else if (p.HardwareAcceleration is not HardwareAccelerationKind.Vaapi)
|
||||
{
|
||||
p.PadMode = FilterMode.HardwareIfPossible;
|
||||
}
|
||||
|
||||
p.VideoBitrate = update.VideoBitrate;
|
||||
p.VideoBufferSize = update.VideoBufferSize;
|
||||
p.TonemapAlgorithm = update.TonemapAlgorithm;
|
||||
|
||||
@@ -15,6 +15,7 @@ public record FFmpegProfileViewModel(
|
||||
int? QsvExtraHardwareFrames,
|
||||
ResolutionViewModel Resolution,
|
||||
ScalingBehavior ScalingBehavior,
|
||||
FilterMode PadMode,
|
||||
FFmpegProfileVideoFormat VideoFormat,
|
||||
string VideoProfile,
|
||||
string VideoPreset,
|
||||
|
||||
@@ -17,6 +17,7 @@ internal static class Mapper
|
||||
profile.QsvExtraHardwareFrames,
|
||||
Resolutions.Mapper.ProjectToViewModel(profile.Resolution),
|
||||
profile.ScalingBehavior,
|
||||
profile.PadMode,
|
||||
profile.VideoFormat,
|
||||
profile.VideoProfile,
|
||||
profile.VideoPreset ?? string.Empty,
|
||||
|
||||
@@ -15,6 +15,7 @@ public record FFmpegProfile
|
||||
public int ResolutionId { get; set; }
|
||||
public Resolution Resolution { get; set; }
|
||||
public ScalingBehavior ScalingBehavior { get; set; }
|
||||
public FilterMode PadMode { get; set; }
|
||||
public FFmpegProfileVideoFormat VideoFormat { get; set; }
|
||||
public string VideoProfile { get; set; }
|
||||
public string VideoPreset { get; set; }
|
||||
@@ -40,6 +41,8 @@ public record FFmpegProfile
|
||||
ThreadCount = 0,
|
||||
ResolutionId = resolution.Id,
|
||||
Resolution = resolution,
|
||||
ScalingBehavior = ScalingBehavior.ScaleAndPad,
|
||||
PadMode = FilterMode.Software,
|
||||
VideoFormat = FFmpegProfileVideoFormat.H264,
|
||||
VideoProfile = "high",
|
||||
VideoPreset = ErsatzTV.FFmpeg.Preset.VideoPreset.Unset,
|
||||
|
||||
7
ErsatzTV.Core/Domain/FilterMode.cs
Normal file
7
ErsatzTV.Core/Domain/FilterMode.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace ErsatzTV.Core.Domain;
|
||||
|
||||
public enum FilterMode
|
||||
{
|
||||
HardwareIfPossible = 0,
|
||||
Software = 1
|
||||
}
|
||||
@@ -509,7 +509,10 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
|
||||
scaledSize,
|
||||
paddedSize,
|
||||
cropSize,
|
||||
false,
|
||||
channel.FFmpegProfile.PadMode is FilterMode.HardwareIfPossible
|
||||
? FFmpegFilterMode.HardwareIfPossible
|
||||
: FFmpegFilterMode.Software,
|
||||
IsAnamorphic: false,
|
||||
playbackSettings.FrameRate,
|
||||
playbackSettings.VideoBitrate,
|
||||
playbackSettings.VideoBufferSize,
|
||||
@@ -711,7 +714,10 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
|
||||
new FrameSize(desiredResolution.Width, desiredResolution.Height),
|
||||
new FrameSize(desiredResolution.Width, desiredResolution.Height),
|
||||
Option<FrameSize>.None,
|
||||
false,
|
||||
channel.FFmpegProfile.PadMode is FilterMode.HardwareIfPossible
|
||||
? FFmpegFilterMode.HardwareIfPossible
|
||||
: FFmpegFilterMode.Software,
|
||||
IsAnamorphic: false,
|
||||
playbackSettings.FrameRate,
|
||||
playbackSettings.VideoBitrate,
|
||||
playbackSettings.VideoBufferSize,
|
||||
|
||||
@@ -67,6 +67,7 @@ public class PipelineBuilderBaseTests
|
||||
new FrameSize(1920, 1080),
|
||||
new FrameSize(1920, 1080),
|
||||
Option<FrameSize>.None,
|
||||
FFmpegFilterMode.HardwareIfPossible,
|
||||
false,
|
||||
Option<FrameRate>.None,
|
||||
2000,
|
||||
@@ -168,6 +169,7 @@ public class PipelineBuilderBaseTests
|
||||
new FrameSize(1920, 1080),
|
||||
new FrameSize(1920, 1080),
|
||||
Option<FrameSize>.None,
|
||||
FFmpegFilterMode.HardwareIfPossible,
|
||||
false,
|
||||
Option<FrameRate>.None,
|
||||
2000,
|
||||
@@ -327,6 +329,7 @@ public class PipelineBuilderBaseTests
|
||||
new FrameSize(1920, 1080),
|
||||
new FrameSize(1920, 1080),
|
||||
Option<FrameSize>.None,
|
||||
FFmpegFilterMode.HardwareIfPossible,
|
||||
false,
|
||||
Option<FrameRate>.None,
|
||||
2000,
|
||||
@@ -421,6 +424,7 @@ public class PipelineBuilderBaseTests
|
||||
new FrameSize(1920, 1080),
|
||||
new FrameSize(1920, 1080),
|
||||
Option<FrameSize>.None,
|
||||
FFmpegFilterMode.HardwareIfPossible,
|
||||
false,
|
||||
Option<FrameRate>.None,
|
||||
2000,
|
||||
|
||||
7
ErsatzTV.FFmpeg/FFmpegFilterMode.cs
Normal file
7
ErsatzTV.FFmpeg/FFmpegFilterMode.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace ErsatzTV.FFmpeg;
|
||||
|
||||
public enum FFmpegFilterMode
|
||||
{
|
||||
HardwareIfPossible = 0,
|
||||
Software = 1
|
||||
}
|
||||
@@ -13,6 +13,7 @@ public record FrameState(
|
||||
FrameSize ScaledSize,
|
||||
FrameSize PaddedSize,
|
||||
Option<FrameSize> CroppedSize,
|
||||
FFmpegFilterMode PadMode,
|
||||
bool IsAnamorphic,
|
||||
Option<FrameRate> FrameRate,
|
||||
Option<int> VideoBitrate,
|
||||
|
||||
@@ -199,7 +199,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
|
||||
bool isHdrTonemap = videoStream.ColorParams.IsHdr;
|
||||
currentState = SetTonemap(videoInputFile, videoStream, ffmpegState, desiredState, currentState);
|
||||
|
||||
currentState = SetPad(videoInputFile, ffmpegState, desiredState, currentState, isHdrTonemap);
|
||||
currentState = SetPad(videoInputFile, desiredState, currentState, isHdrTonemap);
|
||||
// _logger.LogDebug("After pad: {PixelFormat}", currentState.PixelFormat);
|
||||
|
||||
currentState = SetCrop(videoInputFile, desiredState, currentState);
|
||||
@@ -617,28 +617,13 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
|
||||
|
||||
private static FrameState SetPad(
|
||||
VideoInputFile videoInputFile,
|
||||
FFmpegState ffmpegState,
|
||||
FrameState desiredState,
|
||||
FrameState currentState,
|
||||
bool isHdrTonemap)
|
||||
{
|
||||
if (desiredState.CroppedSize.IsNone && currentState.PaddedSize != desiredState.PaddedSize)
|
||||
{
|
||||
// pad_vaapi seems to pad with green when input is HDR
|
||||
// also green with i965 driver
|
||||
// also green with radeonsi and h264 main profile
|
||||
// so use software pad in these cases
|
||||
bool is965 = ffmpegState.VaapiDriver
|
||||
.IfNone(string.Empty)
|
||||
.Contains("i965", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
bool isRadeonSiMain = ffmpegState.VaapiDriver.IfNone(string.Empty)
|
||||
.Contains("radeonsi", StringComparison.OrdinalIgnoreCase)
|
||||
&& ffmpegState.EncoderHardwareAccelerationMode is HardwareAccelerationMode.Vaapi
|
||||
&& desiredState.VideoFormat is VideoFormat.H264
|
||||
&& desiredState.VideoProfile.IfNone(string.Empty) is VideoProfile.Main;
|
||||
|
||||
if (isHdrTonemap || is965 || isRadeonSiMain)
|
||||
if (desiredState.PadMode is FFmpegFilterMode.Software || isHdrTonemap)
|
||||
{
|
||||
var padStep = new PadFilter(currentState, desiredState.PaddedSize);
|
||||
currentState = padStep.NextState(currentState);
|
||||
|
||||
7024
ErsatzTV.Infrastructure.MySql/Migrations/20260115145631_Add_FFmpegProfilePadMode.Designer.cs
generated
Normal file
7024
ErsatzTV.Infrastructure.MySql/Migrations/20260115145631_Add_FFmpegProfilePadMode.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_FFmpegProfilePadMode : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PadMode",
|
||||
table: "FFmpegProfile",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PadMode",
|
||||
table: "FFmpegProfile");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -765,6 +765,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
b.Property<int>("NormalizeLoudnessMode")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PadMode")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("QsvExtraHardwareFrames")
|
||||
.HasColumnType("int");
|
||||
|
||||
|
||||
6851
ErsatzTV.Infrastructure.Sqlite/Migrations/20260115145551_Add_FFmpegProfilePadMode.Designer.cs
generated
Normal file
6851
ErsatzTV.Infrastructure.Sqlite/Migrations/20260115145551_Add_FFmpegProfilePadMode.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_FFmpegProfilePadMode : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PadMode",
|
||||
table: "FFmpegProfile",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PadMode",
|
||||
table: "FFmpegProfile");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -734,6 +734,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
b.Property<int>("NormalizeLoudnessMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PadMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("QsvExtraHardwareFrames")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
||||
@@ -57,6 +57,18 @@
|
||||
<MudSelectItem Value="@ScalingBehavior.Crop">Crop</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
||||
<div class="d-flex">
|
||||
<MudText>Pad Mode</MudText>
|
||||
</div>
|
||||
<MudSelect @bind-Value="_model.PadMode"
|
||||
For="@(() => _model.PadMode)"
|
||||
Disabled="_model.HardwareAcceleration is not HardwareAccelerationKind.Vaapi"
|
||||
HelperText="If hardware padding is green, software padding should be used instead">
|
||||
<MudSelectItem Value="@FilterMode.HardwareIfPossible">Hardware If Possible</MudSelectItem>
|
||||
<MudSelectItem Value="@FilterMode.Software">Software</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Video</MudText>
|
||||
<MudDivider Class="mb-6"/>
|
||||
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
||||
|
||||
@@ -7,9 +7,6 @@ namespace ErsatzTV.ViewModels;
|
||||
|
||||
public class FFmpegProfileEditViewModel
|
||||
{
|
||||
private string _videoProfile;
|
||||
private NormalizeLoudnessMode _normalizeLoudnessMode;
|
||||
|
||||
public FFmpegProfileEditViewModel()
|
||||
{
|
||||
}
|
||||
@@ -29,6 +26,7 @@ public class FFmpegProfileEditViewModel
|
||||
DeinterlaceVideo = viewModel.DeinterlaceVideo;
|
||||
Resolution = viewModel.Resolution;
|
||||
ScalingBehavior = viewModel.ScalingBehavior;
|
||||
PadMode = viewModel.PadMode;
|
||||
ThreadCount = viewModel.ThreadCount;
|
||||
HardwareAcceleration = viewModel.HardwareAcceleration;
|
||||
VaapiDisplay = viewModel.VaapiDisplay;
|
||||
@@ -53,13 +51,14 @@ public class FFmpegProfileEditViewModel
|
||||
|
||||
public NormalizeLoudnessMode NormalizeLoudnessMode
|
||||
{
|
||||
get => _normalizeLoudnessMode;
|
||||
get;
|
||||
|
||||
set
|
||||
{
|
||||
if (_normalizeLoudnessMode != value)
|
||||
if (field != value)
|
||||
{
|
||||
_normalizeLoudnessMode = value;
|
||||
if (_normalizeLoudnessMode is NormalizeLoudnessMode.LoudNorm)
|
||||
field = value;
|
||||
if (field is NormalizeLoudnessMode.LoudNorm)
|
||||
{
|
||||
TargetLoudness = -16;
|
||||
}
|
||||
@@ -78,6 +77,20 @@ public class FFmpegProfileEditViewModel
|
||||
public bool DeinterlaceVideo { get; set; }
|
||||
public ResolutionViewModel Resolution { get; set; }
|
||||
public ScalingBehavior ScalingBehavior { get; set; }
|
||||
|
||||
public FilterMode PadMode
|
||||
{
|
||||
// only allow customization with VAAPI accel
|
||||
get => HardwareAcceleration switch
|
||||
{
|
||||
HardwareAccelerationKind.None => FilterMode.Software,
|
||||
HardwareAccelerationKind.Vaapi => field,
|
||||
_ => FilterMode.HardwareIfPossible
|
||||
};
|
||||
|
||||
set;
|
||||
}
|
||||
|
||||
public int ThreadCount { get; set; }
|
||||
public HardwareAccelerationKind HardwareAcceleration { get; set; }
|
||||
public string VaapiDisplay { get; set; }
|
||||
@@ -95,10 +108,11 @@ public class FFmpegProfileEditViewModel
|
||||
{
|
||||
(HardwareAccelerationKind.Nvenc, FFmpegProfileVideoFormat.H264, FFmpegProfileBitDepth.TenBit) => FFmpeg
|
||||
.Format.VideoProfile.High444p,
|
||||
(_, FFmpegProfileVideoFormat.H264, _) => _videoProfile,
|
||||
(_, FFmpegProfileVideoFormat.H264, _) => field,
|
||||
_ => string.Empty
|
||||
};
|
||||
set => _videoProfile = value;
|
||||
|
||||
set;
|
||||
}
|
||||
|
||||
public string VideoPreset { get; set; }
|
||||
@@ -117,6 +131,7 @@ public class FFmpegProfileEditViewModel
|
||||
QsvExtraHardwareFrames,
|
||||
Resolution.Id,
|
||||
ScalingBehavior,
|
||||
PadMode,
|
||||
VideoFormat,
|
||||
VideoProfile,
|
||||
VideoPreset,
|
||||
@@ -148,6 +163,7 @@ public class FFmpegProfileEditViewModel
|
||||
QsvExtraHardwareFrames,
|
||||
Resolution.Id,
|
||||
ScalingBehavior,
|
||||
PadMode,
|
||||
VideoFormat,
|
||||
VideoProfile,
|
||||
VideoPreset,
|
||||
|
||||
Reference in New Issue
Block a user