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 ExecuteAsync(CancellationToken cancellationToken, Action 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[] QPTable = new Tuple[] { 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 ExecuteAsync(CancellationToken cancellationToken, Action 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[] QPTable = new Tuple[] { 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 ExecuteAsync(CancellationToken cancellationToken, Action 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[] QPTable = new Tuple[] { 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 ExecuteAsync(CancellationToken cancellationToken, Action 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[] QPTable = new Tuple[] { 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 ExecuteAsync(CancellationToken cancellationToken, Action 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[] QPTable = new Tuple[] { 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 ExecuteAsync(CancellationToken cancellationToken, Action 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[] QPTable = new Tuple[] { 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 ExecuteAsync(CancellationToken cancellationToken, Action 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(); } }