Files
VideoEncoderService/Encoder/Strategies.cs

291 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, 27),
new(1920*1080, 28),
new(3840*2160, 29),
new(5760*2880, 30), //VR6K
new(8128*4096, 32) //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("libsvtav1")
.WithCustomArgument($"-preset 7")
.WithConstantRateFactor(qp)
.WithCustomArgument("-svtav1-params tune=0")
.WithVideoFilters(filterOptions => {if (W > 0 && H > 0) filterOptions.Scale(W, H);})
.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, 20),
new(1920*1080, 22),
new(3840*2160, 26),
new(5760*2880, 26), //VR6K
new(8128*4096, 28) //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)
.WithSpeedPreset(Speed.Slow)
.WithConstantRateFactor(qp)
.WithCustomArgument("-x265-params profile=main10:high-tier=1:aq-mode=3:psy-rd=1:rc-lookahead=60:bframes=6")
.WithVideoFilters(filterOptions => {if (W > 0 && H > 0) filterOptions.Scale(W, H);})
.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();
}
}