diff --git a/OKEGui/OKEGui/JobProcessor/ExceptionParser.cs b/OKEGui/OKEGui/JobProcessor/ExceptionParser.cs index ecc808ed..2b0026da 100644 --- a/OKEGui/OKEGui/JobProcessor/ExceptionParser.cs +++ b/OKEGui/OKEGui/JobProcessor/ExceptionParser.cs @@ -89,6 +89,10 @@ public static ExceptionMsg Parse(OKETaskException ex, TaskDetail task) msg.errorMsg = string.Format(Constants.x265ErrorMsg, ex.Data["X265_ERROR"], task.InputFile); break; + case Constants.svtav1ErrorSmr: + msg.errorMsg = string.Format(Constants.svtav1ErrorMsg, ex.Data["SVTAV1_ERROR"], task.InputFile); + break; + case Constants.vpyErrorSmr: msg.errorMsg = string.Format(Constants.vpyErrorMsg, ex.Data["VPY_ERROR"], task.InputFile); break; @@ -105,6 +109,10 @@ public static ExceptionMsg Parse(OKETaskException ex, TaskDetail task) msg.errorMsg = string.Format(Constants.x265CrashMsg, task.InputFile); break; + case Constants.svtav1CrashSmr: + msg.errorMsg = string.Format(Constants.svtav1CrashMsg, task.InputFile); + break; + case Constants.qaacErrorSmr: msg.errorMsg = string.Format(Constants.qaacErrorMsg); break; diff --git a/OKEGui/OKEGui/JobProcessor/Muxer/AutoMuxer.cs b/OKEGui/OKEGui/JobProcessor/Muxer/AutoMuxer.cs index 4bfd55dc..3af44183 100644 --- a/OKEGui/OKEGui/JobProcessor/Muxer/AutoMuxer.cs +++ b/OKEGui/OKEGui/JobProcessor/Muxer/AutoMuxer.cs @@ -48,7 +48,7 @@ private struct Episode }; private static List s_VideoFileExtensions = new List { - ".hevc", ".h265", ".avc", ".h264", ".mkv" + ".ivf", ".hevc", ".h265", ".avc", ".h264", ".mkv" }; private string _mkvMergePath; diff --git a/OKEGui/OKEGui/JobProcessor/Video/CommandlineVideoEncoder.cs b/OKEGui/OKEGui/JobProcessor/Video/CommandlineVideoEncoder.cs index 8990fda8..0b850cd5 100644 --- a/OKEGui/OKEGui/JobProcessor/Video/CommandlineVideoEncoder.cs +++ b/OKEGui/OKEGui/JobProcessor/Video/CommandlineVideoEncoder.cs @@ -81,14 +81,16 @@ protected bool setFrameNumber(string frameString, bool isUpdateSpeed = false) return false; } - protected bool setSpeed(string speed) + protected bool setSpeed(string speed, string unit = "fps") { - double fps; + double fps, factor = 1; + if (unit == "fpm") + factor = 60; if (double.TryParse(speed, out fps)) { if (fps > 0) { - this.speed = fps; + this.speed = fps / factor; } else { diff --git a/OKEGui/OKEGui/JobProcessor/Video/svtav1Encoder.cs b/OKEGui/OKEGui/JobProcessor/Video/svtav1Encoder.cs new file mode 100644 index 00000000..063fa7af --- /dev/null +++ b/OKEGui/OKEGui/JobProcessor/Video/svtav1Encoder.cs @@ -0,0 +1,143 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using OKEGui.Utils; +using OKEGui.JobProcessor; +using System.Collections.Generic; + +namespace OKEGui +{ + public class SVTAV1Encoder : CommandlineVideoEncoder + { + private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + private readonly string svtav1Path = ""; + private readonly string vspipePath = ""; + private bool expectTotalFrames = false; + + public SVTAV1Encoder(VideoJob job) : base() + { + this.job = job; + getInputProperties(job); + + executable = Path.Combine(Environment.SystemDirectory, "cmd.exe"); + + if (File.Exists(job.EncoderPath)) + { + this.svtav1Path = job.EncoderPath; + } + + // 获取VSPipe路径 + this.vspipePath = Initializer.Config.vspipePath; + + commandLine = BuildCommandline(job.EncodeParam, job.NumaNode, job.VspipeArgs); + } + + public override void ProcessLine(string line, StreamType stream) + { + if (line.Contains("Svt[error]: ") || line.Contains("[SVT-Error]: ") || line.Contains("Error: ")) + { + Logger.Error(line); + OKETaskException ex = new OKETaskException(Constants.svtav1ErrorSmr); + ex.progress = 0.0; + ex.Data["SVTAV1_ERROR"] = line.Substring(line.IndexOf(':')+2); + throw ex; + } + + if (line.Contains("Error: fwrite() call failed when writing frame: ")) + { + Logger.Error(line); + OKETaskException ex = new OKETaskException(Constants.svtav1CrashSmr); + throw ex; + } + + if (line.ToLowerInvariant().Contains("all_done_encoding")) // svt-av1 must be built with -DLOG_ENC_DONE=1. + { + Logger.Debug(line); + Regex rf = new Regex("all_done_encoding *([0-9]+) frames"); + + var result = rf.Split(line); + + ulong reportedFrames = ulong.Parse(result[1]); + + Debugger.Log(0, "EncodeFinish", result[1] + " frames\n"); + + base.encodeFinish(reportedFrames); + } + if (line.StartsWith("Total Frames\t")) + { + Logger.Debug(line); + expectTotalFrames = true; + return; + } + if (expectTotalFrames) + { + Logger.Debug(line); + Regex rf = new Regex("[\t ]*([0-9]+)[\t ]"); + var result = rf.Split(line); + if (result.Length < 2) + return; + ulong reportedFrames = ulong.Parse(result[1]); + Debugger.Log(0, "EncodeFinish", result[1] + " frames\n"); + base.encodeFinish(reportedFrames); + expectTotalFrames = false; + } + + Regex regOfficial = new Regex("Encoding frame *([0-9]+) *([0-9]+.[0-9]+) *kbps *([0-9]+.[0-9]+) *(fp[sm])", RegexOptions.IgnoreCase); + + string[] status; + + if (regOfficial.Split(line).Length >= 4) + { + status = regOfficial.Split(line); + } + else + { + Logger.Debug(line); + return; + } + + if (!base.setFrameNumber(status[1], true)) + { + return; + } + + base.setBitrate(status[2], "kb/s"); + + if (!base.setSpeed(status[3], status[4])) + { + return; + } + } + + private string BuildCommandline(string extractParam, int numaNode, List vspipeArgs) + { + StringBuilder sb = new StringBuilder(); + sb.Append("/c \"start \"foo\" /b /wait "); + if (!Initializer.Config.singleNuma) + { + sb.Append("/affinity 0xFFFFFFFFFFFFFFFF /node "); + sb.Append(numaNode.ToString()); + } + // 构建vspipe参数 + sb.Append(" \"" + vspipePath + "\""); + sb.Append(" --y4m"); + foreach (string arg in vspipeArgs) + { + sb.Append($" --arg \"{arg}\""); + } + sb.Append(" \"" + job.Input + "\""); + sb.Append(" - |"); + + // 构建svtav1参数 + sb.Append(" \"" + svtav1Path + "\""); + sb.Append(" --progress 2 " + extractParam + " -b"); + sb.Append(" \"" + job.Output + "\" -i -"); + sb.Append(" \""); // leave extra spaces for ApppendParameter. + + return sb.ToString(); + } + + } +} diff --git a/OKEGui/OKEGui/OKEGui.csproj b/OKEGui/OKEGui/OKEGui.csproj index 308ff94e..857d358d 100644 --- a/OKEGui/OKEGui/OKEGui.csproj +++ b/OKEGui/OKEGui/OKEGui.csproj @@ -114,6 +114,7 @@ + diff --git a/OKEGui/OKEGui/Task/AddTaskService.cs b/OKEGui/OKEGui/Task/AddTaskService.cs index ffd12946..25128cf5 100644 --- a/OKEGui/OKEGui/Task/AddTaskService.cs +++ b/OKEGui/OKEGui/Task/AddTaskService.cs @@ -81,7 +81,7 @@ public static TaskProfile ProcessJsonProfile(TaskProfile json, DirectoryInfo pro } } - // 编码器设置,目前只允许x264/x265 + // 编码器设置,目前只允许x264/x265/svtav1 json.EncoderType = json.EncoderType.ToLower(); switch (json.EncoderType) { @@ -91,8 +91,11 @@ public static TaskProfile ProcessJsonProfile(TaskProfile json, DirectoryInfo pro case "x265": json.VideoFormat = "HEVC"; break; + case "svtav1": + json.VideoFormat = "AV1"; + break; default: - MessageBox.Show("EncoderType请填写x264或者x265", "编码器版本错误", MessageBoxButton.OK, MessageBoxImage.Error); + MessageBox.Show("EncoderType请填写x264/x265/svtav1", "编码器版本错误", MessageBoxButton.OK, MessageBoxImage.Error); return null; } diff --git a/OKEGui/OKEGui/Utils/Constants.cs b/OKEGui/OKEGui/Utils/Constants.cs index f0af17b0..ca66c6c8 100644 --- a/OKEGui/OKEGui/Utils/Constants.cs +++ b/OKEGui/OKEGui/Utils/Constants.cs @@ -47,6 +47,9 @@ public static class Constants public const string x265ErrorMsg = "x265出错:{0}。该文件{1}将跳过处理。请转告技术总监复查。"; public const string x265ErrorSmr = "x265出错"; + public const string svtav1ErrorMsg = "svt-av1出错:{0}。该文件{1}将跳过处理。请转告技术总监复查。"; + public const string svtav1ErrorSmr = "svt-av1出错"; + public const string vpyErrorMsg = "vpy出错:{0}。该文件{1}将跳过处理。请转告技术总监复查。"; public const string vpyErrorSmr = "vpy出错"; @@ -62,6 +65,9 @@ public static class Constants public const string x265CrashMsg = "压制未能完成,预计是x265崩溃。该文件{0}将跳过处理,半成品以HEVC形式保留在目录中。请转告技术总监复查。"; public const string x265CrashSmr = "x265崩溃"; + public const string svtav1CrashMsg = "压制未能完成,预计是svt-av1崩溃。该文件{0}将跳过处理,半成品以HEVC形式保留在目录中。请转告技术总监复查。"; + public const string svtav1CrashSmr = "svt-av1崩溃"; + public const string qaacErrorMsg = "QAAC无法正常运行。请确保你安装了Apple Application Support 64bit"; public const string qaacErrorSmr = "QAAC无法运行"; diff --git a/OKEGui/OKEGui/Worker/ExecuteTaskService.cs b/OKEGui/OKEGui/Worker/ExecuteTaskService.cs index 3e85740c..93534a45 100644 --- a/OKEGui/OKEGui/Worker/ExecuteTaskService.cs +++ b/OKEGui/OKEGui/Worker/ExecuteTaskService.cs @@ -151,7 +151,7 @@ private void WorkerDoWork(object sender, DoWorkEventArgs e) videoJob.EncodeParam += " --pools " + NumaNode.X265PoolsParam(videoJob.NumaNode); } } - else + else if (profile.VideoFormat == "AVC") { videoJob.Output = task.Taskfile.WorkingPathPrefix; videoJob.Output += profile.ContainerFormat == "MKV" ? "_.mkv" : ".h264"; @@ -160,6 +160,18 @@ private void WorkerDoWork(object sender, DoWorkEventArgs e) videoJob.EncodeParam += " --threads 16"; } } + else if (profile.VideoFormat == "AV1") + { + videoJob.Output = task.Taskfile.WorkingPathPrefix + ".ivf"; + if (!profile.EncoderParam.ToLower().Contains("--lp") && NumaNode.UsableCoreCount > 8) + { + videoJob.EncodeParam += " --lp 8"; + } + } + else + { + throw new Exception("unknown video codec: " + profile.VideoFormat); + } task.JobQueue.Enqueue(videoJob); @@ -241,10 +253,18 @@ private void WorkerDoWork(object sender, DoWorkEventArgs e) { processor = new X265Encoder(vJob); } - else + else if (vJob.CodecString == "AVC") { processor = new X264Encoder(vJob); } + else if (vJob.CodecString == "AV1") + { + processor = new SVTAV1Encoder(vJob); + } + else + { + throw new Exception("unknown video codec: " + vJob.CodecString); + } // 时间码文件 Timecode timecode = null; @@ -296,8 +316,16 @@ private void WorkerDoWork(object sender, DoWorkEventArgs e) string qpFile = vJob.Vfr ? ChapterService.GenerateQpFile(chapterInfo, timecode) : ChapterService.GenerateQpFile(chapterInfo, vJob.Fps); - File.WriteAllText(qpFileName, qpFile); - processor.AppendParameter($"--qpfile \"{qpFileName}\""); + if (vJob.CodecString == "AV1") + { + qpFile = String.Join(",", qpFile.Replace(" I", "f").Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries)); + processor.AppendParameter($"--force-key-frames \"{qpFile}\""); + } + else + { + File.WriteAllText(qpFileName, qpFile); + processor.AppendParameter($"--qpfile \"{qpFileName}\""); + } } else {