Files
VideoEncoderService/Encoder/Strategies.cs
2025-12-18 02:11:52 +01:00

288 lines
13 KiB
C#

using FFMpegCore;
using FFMpegCore.Enums;
namespace Encoder;
public struct EncodingProgress {
public TimeSpan progress;
public TimeSpan duration;
public float framerate;
public EncodingJob job;
public string inputPath;
public string outputPath;
}
public class EncodingStrategyAttribute(EncoderType encoderType) : Attribute {
public EncoderType EncoderType { get; } = encoderType;
}
public interface IEncodingStrategy {
public string InputFilePath { get; set; }
public string OutputFilePath { get; set; }
public Task<bool> ExecuteAsync(CancellationToken cancellationToken, Action<EncodingProgress> onProgress);
}
[EncodingStrategy(EncoderType.AV1)]
public class AV1NvencEncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy {
public string InputFilePath { get; set; } = inputFilePath;
public string OutputFilePath { get; set; } = outputFilePath;
private static readonly Tuple<int, int>[] QPTable = new Tuple<int, int>[] {
new(1280*720, 64),
new(1920*1080, 96),
new(3840*2160, 128),
new(5760*2880, 96), //VR6K
new(8128*4096, 120) //VR8K
}.OrderBy(t => t.Item1).ToArray();
public async Task<bool> ExecuteAsync(CancellationToken cancellationToken, Action<EncodingProgress> onProgress) {
var mediaInfo = await FFProbe.AnalyseAsync(InputFilePath, cancellationToken: cancellationToken);
if (mediaInfo.PrimaryVideoStream == null) return false;
int W = mediaInfo.PrimaryVideoStream.Width;
int H = mediaInfo.PrimaryVideoStream.Height;
int qp = Utils.ToQPValue(W, H, QPTable);
return await FFMpegArguments
.FromFileInput(InputFilePath, true, args => args.WithHardwareAcceleration())
.OutputToFile(OutputFilePath, true, args => args
.CopyChannel(Channel.Audio)
.CopyChannel(Channel.Subtitle)
.WithVideoCodec("av1_nvenc")
.WithArgument(new NvencSpeedPreset(NvencSpeed.p2))
.WithArgument(new NvencTuneArgument(NvencTune.hq))
.WithArgument(new NvencHighBitDepthArgument(true))
.WithArgument(new NvencQPArgument((byte)qp))
.WithFastStart()
)
.NotifyOnProgress(progress => onProgress(new() {
progress = progress,
duration = mediaInfo.Duration,
framerate = (float)mediaInfo.PrimaryVideoStream.FrameRate,
job = job,
inputPath = InputFilePath,
outputPath = OutputFilePath
}))
.CancellableThrough(cancellationToken)
.ProcessAsynchronously();
}
}
[EncodingStrategy(EncoderType.HEVC)]
public class HevcNvencEncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy {
public string InputFilePath { get; set; } = inputFilePath;
public string OutputFilePath { get; set; } = outputFilePath;
//TODO: needs to be adjusted
private static readonly Tuple<int, int>[] QPTable = new Tuple<int, int>[] {
new(1280*720, 0),
new(1920*1080, 0),
new(3840*2160, 0),
new(5760*2880, 0), //VR6K
new(8128*4096, 0) //VR8K
}.OrderBy(t => t.Item1).ToArray();
public async Task<bool> ExecuteAsync(CancellationToken cancellationToken, Action<EncodingProgress> onProgress) {
var mediaInfo = await FFProbe.AnalyseAsync(InputFilePath, cancellationToken: cancellationToken);
if (mediaInfo.PrimaryVideoStream == null) return false;
int W = mediaInfo.PrimaryVideoStream.Width;
int H = mediaInfo.PrimaryVideoStream.Height;
int qp = Utils.ToQPValue(W, H, QPTable);
return await FFMpegArguments
.FromFileInput(InputFilePath, true, args => args.WithHardwareAcceleration())
.OutputToFile(OutputFilePath, true, args => args
.CopyChannel(Channel.Audio)
.CopyChannel(Channel.Subtitle)
.WithVideoCodec("hevc_nvenc")
.WithArgument(new NvencSpeedPreset(NvencSpeed.p2))
.WithArgument(new NvencTuneArgument(NvencTune.hq))
.WithArgument(new NvencHighBitDepthArgument(true))
.WithArgument(new NvencQPArgument((byte)qp))
.WithFastStart()
)
.NotifyOnProgress(progress => onProgress(new() {
progress = progress,
duration = mediaInfo.Duration,
framerate = (float)mediaInfo.PrimaryVideoStream.FrameRate,
job = job,
inputPath = InputFilePath,
outputPath = OutputFilePath
}))
.CancellableThrough(cancellationToken)
.ProcessAsynchronously();
}
}
[EncodingStrategy(EncoderType.H264)]
public class H264NvencEncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy {
public string InputFilePath { get; set; } = inputFilePath;
public string OutputFilePath { get; set; } = outputFilePath;
//TODO: needs to be adjusted
private static readonly Tuple<int, int>[] QPTable = new Tuple<int, int>[] {
new(1280*720, 0),
new(1920*1080, 0),
new(3840*2160, 0),
new(5760*2880, 0), //VR6K
new(8128*4096, 0) //VR8K
}.OrderBy(t => t.Item1).ToArray();
public async Task<bool> ExecuteAsync(CancellationToken cancellationToken, Action<EncodingProgress> onProgress) {
var mediaInfo = await FFProbe.AnalyseAsync(InputFilePath, cancellationToken: cancellationToken);
if (mediaInfo.PrimaryVideoStream == null) return false;
int W = mediaInfo.PrimaryVideoStream.Width;
int H = mediaInfo.PrimaryVideoStream.Height;
int qp = Utils.ToQPValue(W, H, QPTable);
return await FFMpegArguments
.FromFileInput(InputFilePath, true, args => args.WithHardwareAcceleration())
.OutputToFile(OutputFilePath, true, args => args
.CopyChannel(Channel.Audio)
.CopyChannel(Channel.Subtitle)
.WithVideoCodec("h264_nvenc")
.WithArgument(new NvencSpeedPreset(NvencSpeed.p2))
.WithArgument(new NvencTuneArgument(NvencTune.hq))
.WithArgument(new NvencHighBitDepthArgument(true))
.WithArgument(new NvencQPArgument((byte)qp))
.WithFastStart()
)
.NotifyOnProgress(progress => onProgress(new() {
progress = progress,
duration = mediaInfo.Duration,
framerate = (float)mediaInfo.PrimaryVideoStream.FrameRate,
job = job,
inputPath = InputFilePath,
outputPath = OutputFilePath
}))
.CancellableThrough(cancellationToken)
.ProcessAsynchronously();
}
}
[EncodingStrategy(EncoderType.AV1)]
public class AV1SwEncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy {
public string InputFilePath { get; set; } = inputFilePath;
public string OutputFilePath { get; set; } = outputFilePath;
private static readonly Tuple<int, int>[] QPTable = new Tuple<int, int>[] {
new(1280*720, 64),
new(1920*1080, 96),
new(3840*2160, 128),
new(5760*2880, 96), //VR6K
new(8128*4096, 120) //VR8K
}.OrderBy(t => t.Item1).ToArray();
public async Task<bool> ExecuteAsync(CancellationToken cancellationToken, Action<EncodingProgress> onProgress) {
var mediaInfo = await FFProbe.AnalyseAsync(InputFilePath, cancellationToken: cancellationToken);
if (mediaInfo.PrimaryVideoStream == null) return false;
int W = mediaInfo.PrimaryVideoStream.Width;
int H = mediaInfo.PrimaryVideoStream.Height;
int qp = Utils.ToQPValue(W, H, QPTable);
return await FFMpegArguments
.FromFileInput(InputFilePath, true, args => args.WithHardwareAcceleration())
.OutputToFile(OutputFilePath, true, args => args
.CopyChannel(Channel.Audio)
.CopyChannel(Channel.Subtitle)
.WithVideoCodec(VideoCodec.LibaomAv1)
// TODO: adjust settings for software AV1 encoding
.WithFastStart()
)
.NotifyOnProgress(progress => onProgress(new() {
progress = progress,
duration = mediaInfo.Duration,
framerate = (float)mediaInfo.PrimaryVideoStream.FrameRate,
job = job,
inputPath = InputFilePath,
outputPath = OutputFilePath
}))
.CancellableThrough(cancellationToken)
.ProcessAsynchronously();
}
}
[EncodingStrategy(EncoderType.HEVC)]
public class HevcSwEncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy {
public string InputFilePath { get; set; } = inputFilePath;
public string OutputFilePath { get; set; } = outputFilePath;
//TODO: needs to be adjusted for software HEVC encoding
private static readonly Tuple<int, int>[] QPTable = new Tuple<int, int>[] {
new(1280*720, 0),
new(1920*1080, 0),
new(3840*2160, 0),
new(5760*2880, 0), //VR6K
new(8128*4096, 0) //VR8K
}.OrderBy(t => t.Item1).ToArray();
public async Task<bool> ExecuteAsync(CancellationToken cancellationToken, Action<EncodingProgress> onProgress) {
var mediaInfo = await FFProbe.AnalyseAsync(InputFilePath, cancellationToken: cancellationToken);
if (mediaInfo.PrimaryVideoStream == null) return false;
int W = mediaInfo.PrimaryVideoStream.Width;
int H = mediaInfo.PrimaryVideoStream.Height;
int qp = Utils.ToQPValue(W, H, QPTable);
return await FFMpegArguments
.FromFileInput(InputFilePath, true, args => args.WithHardwareAcceleration())
.OutputToFile(OutputFilePath, true, args => args
.CopyChannel(Channel.Audio)
.CopyChannel(Channel.Subtitle)
.WithVideoCodec(VideoCodec.LibX265)
.WithArgument(new NvencSpeedPreset(NvencSpeed.p2))
.WithArgument(new NvencTuneArgument(NvencTune.hq))
.WithArgument(new NvencHighBitDepthArgument(true))
.WithArgument(new NvencQPArgument((byte)qp))
.WithFastStart()
)
.NotifyOnProgress(progress => onProgress(new() {
progress = progress,
duration = mediaInfo.Duration,
framerate = (float)mediaInfo.PrimaryVideoStream.FrameRate,
job = job,
inputPath = InputFilePath,
outputPath = OutputFilePath
}))
.CancellableThrough(cancellationToken)
.ProcessAsynchronously();
}
}
[EncodingStrategy(EncoderType.H264)]
public class H264SwEncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy {
public string InputFilePath { get; set; } = inputFilePath;
public string OutputFilePath { get; set; } = outputFilePath;
//TODO: needs to be adjusted
private static readonly Tuple<int, int>[] QPTable = new Tuple<int, int>[] {
new(1280*720, 0),
new(1920*1080, 0),
new(3840*2160, 0),
new(5760*2880, 0), //VR6K
new(8128*4096, 0) //VR8K
}.OrderBy(t => t.Item1).ToArray();
public async Task<bool> ExecuteAsync(CancellationToken cancellationToken, Action<EncodingProgress> onProgress) {
var mediaInfo = await FFProbe.AnalyseAsync(InputFilePath, cancellationToken: cancellationToken);
if (mediaInfo.PrimaryVideoStream == null) return false;
int W = mediaInfo.PrimaryVideoStream.Width;
int H = mediaInfo.PrimaryVideoStream.Height;
int qp = Utils.ToQPValue(W, H, QPTable);
return await FFMpegArguments
.FromFileInput(InputFilePath, true, args => args.WithHardwareAcceleration())
.OutputToFile(OutputFilePath, true, args => args
.CopyChannel(Channel.Audio)
.CopyChannel(Channel.Subtitle)
.WithVideoCodec(VideoCodec.LibX264)
.WithArgument(new NvencSpeedPreset(NvencSpeed.p2))
.WithArgument(new NvencTuneArgument(NvencTune.hq))
.WithArgument(new NvencHighBitDepthArgument(true))
.WithArgument(new NvencQPArgument((byte)qp))
.WithFastStart()
)
.NotifyOnProgress(progress => onProgress(new() {
progress = progress,
duration = mediaInfo.Duration,
framerate = (float)mediaInfo.PrimaryVideoStream.FrameRate,
job = job,
inputPath = InputFilePath,
outputPath = OutputFilePath
}))
.CancellableThrough(cancellationToken)
.ProcessAsynchronously();
}
}