public List <TaskCommand> Generate(Dictionary <string, JToken> sectionData) { #region 输入/输出 JToken ioSection = sectionData[ConfigSectionBase.IOConfigSectionId]; #endregion #region 输入文件 string inputPathIntl = PathExtension.GetFullPathOrEmpty(ioSection["input"]?.ToObject <string>() ?? string.Empty); string inputPath = inputPathIntl.EscapePathStringForArg(); #endregion #region 输入字幕 string subtitlePathIntl = PathExtension.GetFullPathOrEmpty(ioSection["subtitle"]?.ToObject <string>() ?? string.Empty); string subtitlePath = subtitlePathIntl.EscapePathStringForArg(); #endregion #region 字幕相关 bool isIncludingSubtitle = !string.IsNullOrWhiteSpace(subtitlePathIntl); #endregion #region VapourSynth 相关 string lwiPath = (inputPathIntl + ".lwi").EscapePathStringForArg(); bool isVpy = inputPathIntl.EndsWith(".vpy"); bool useVpy = isVpy || isIncludingSubtitle; if (isVpy && isIncludingSubtitle) { throw new OperationException( "不支持在 VapourSynth 输入上使用 VSFilterMod。请在 VapourSynth 中完成字幕处理。", typeof(HwEncOperation)); } #endregion #region 显卡相关 JToken hwEncQualitySection = sectionData["Ruminoid.Toolbox.Plugins.HwEnc.ConfigSections.HwEncQualityConfigSection"]; JToken hwEncCodecSection = sectionData["Ruminoid.Toolbox.Plugins.HwEnc.ConfigSections.HwEncCodecConfigSection"]; JToken hwEncCoreSection = sectionData["Ruminoid.Toolbox.Plugins.HwEnc.ConfigSections.HwEncCoreConfigSection"]; string hwEncCore = hwEncCoreSection["core"]?.ToObject <string>(); string vtempPath = Path.ChangeExtension(inputPathIntl, "vtemp.mp4").EscapePathStringForArg(); #endregion #region 音频 JToken audioSection = sectionData["Ruminoid.Toolbox.Plugins.Audio.ConfigSections.AudioConfigSection"]; #endregion #region 音频相关 string atempPath = Path.ChangeExtension(inputPathIntl, "atemp.mp4").EscapePathStringForArg(); string audioMode = audioSection["mode"]?.ToObject <string>(); bool hasAudio = audioMode != "none"; bool isVpyWithAudio = useVpy && hasAudio; int audioBitrate = audioSection["bitrate"].ToObject <int>(); string audioArgs = audioMode switch { "process" => "--audio-codec --audio-bitrate " + audioBitrate, "none" => "", _ => "--audio-copy" }; #endregion #region 输出相关 string outputPath = PathExtension.GetFullPathOrEmpty(ioSection["output"]?.ToObject <string>() ?? string.Empty).EscapePathStringForArg(); #endregion #region 自定义参数 JToken customArgsSection = sectionData["Ruminoid.Toolbox.Plugins.Common.ConfigSections.CustomArgsConfigSection"]; string customArgs = customArgsSection["args"]?.ToObject <string>(); bool useCustomArgs = customArgsSection["use_custom_args"]?.ToObject <bool>() ?? false; #endregion #region 准备命令 List <TaskCommand> result = new(); #endregion // 开始处理 #region 处理音频 if (isVpyWithAudio) { result.Add(new( "ffmpeg", $"-i {inputPath} -vn -sn -c:a copy -y -map 0:a:0 {atempPath}", "null")); } #endregion #region 处理字幕 if (isIncludingSubtitle) { string vpyPath = Path.ChangeExtension(inputPathIntl, "vpy").EscapePathStringForArg(); result.Add( ("node", $"rmbox-vpygen.js {inputPath} {subtitlePath} {vpyPath}", "null")); inputPath = vpyPath; } #endregion #region 处理视频 string encodeMode = hwEncQualitySection["encode_mode"]?.ToObject <string>() switch { "cqp" => $"--cqp {hwEncQualitySection["cqp_value"]?.ToObject<string>()}", "2pass" => $"--vbr {hwEncQualitySection["2pass_value"]?.ToObject<int>()} --multipass 2pass-full", // ReSharper disable once NotResolvedInText _ => throw new ArgumentOutOfRangeException("encode_mode") }; result.Add( (hwEncCore, $"{(useVpy ? "--vpy" : "--avhw")} -i {inputPath} -o {outputPath} {(isVpyWithAudio ? "--audio-source " + atempPath : "")} {audioArgs} --codec {hwEncCodecSection["codec"]?.ToObject<string>()} {encodeMode} {(useCustomArgs ? customArgs : DefaultArgs)}", hwEncCore)); #endregion #region 清理临时文件 result.AddRange(new[] { vtempPath, atempPath, lwiPath } .Select(CommandExtension.GenerateTryDeleteCommand) .ToList()); if (isIncludingSubtitle) { result.Add(CommandExtension.GenerateTryDeleteCommand(inputPath)); } #endregion return(result); }
public List <TaskCommand> Generate(Dictionary <string, JToken> sectionData) { #region 输入/输出 JToken ioSection = sectionData[ConfigSectionBase.IOConfigSectionId]; #endregion #region 输入文件 string inputPathIntl = PathExtension.GetFullPathOrEmpty(ioSection["input"]?.ToObject <string>() ?? string.Empty); string inputPath = inputPathIntl.EscapePathStringForArg(); #endregion #region 输入字幕 string subtitlePathIntl = PathExtension.GetFullPathOrEmpty(ioSection["subtitle"]?.ToObject <string>() ?? string.Empty); string subtitlePath = subtitlePathIntl.EscapePathStringForArg(); #endregion #region 字幕相关 bool isIncludingSubtitle = !string.IsNullOrWhiteSpace(subtitlePathIntl); #endregion #region VapourSynth 相关 string lwiPath = (inputPathIntl + ".lwi").EscapePathStringForArg(); bool isVpy = inputPathIntl.EndsWith(".vpy"); bool useVpy = isVpy || isIncludingSubtitle; if (isVpy && isIncludingSubtitle) { throw new OperationException( "不支持在 VapourSynth 输入上使用 VSFilterMod。请在 VapourSynth 中完成字幕处理。", typeof(X264EncodeOperation)); } #endregion #region x264 分离器 JToken x264DemuxerSection = sectionData["Ruminoid.Toolbox.Plugins.X264.ConfigSections.X264DemuxerConfigSection"]; string demuxer = x264DemuxerSection["demuxer"]?.ToObject <string>(); string demuxerArgs = string.IsNullOrWhiteSpace(demuxer) || demuxer == "auto" ? "" : "--demuxer " + demuxer; #endregion #region x264 质量 JToken x264QualitySection = sectionData["Ruminoid.Toolbox.Plugins.X264.ConfigSections.X264EncodeQualityConfigSection"]; #endregion #region x264 核心 JToken x264CoreSection = sectionData["Ruminoid.Toolbox.Plugins.X264.ConfigSections.X264CoreConfigSection"]; #endregion #region x264 相关 string vtempPath = Path.ChangeExtension(inputPathIntl, "vtemp.mp4").EscapePathStringForArg(); string vtempStatsPath = Path.ChangeExtension(inputPathIntl, "vtemp.stats").EscapePathStringForArg(); string vtempStatsMbtreePath = Path.ChangeExtension(inputPathIntl, "vtemp.stats.mbtree").EscapePathStringForArg(); string x264EncodeMode = x264QualitySection["encode_mode"]?.ToObject <string>() ?? string.Empty; string x264Core = x264CoreSection["core"]?.ToObject <string>(); #endregion #region 音频 JToken audioSection = sectionData["Ruminoid.Toolbox.Plugins.Audio.ConfigSections.AudioConfigSection"]; #endregion #region 音频相关 string atempPath = Path.ChangeExtension(inputPathIntl, "atemp.mp4").EscapePathStringForArg(); string audioMode = audioSection["mode"]?.ToObject <string>(); bool hasAudio = audioMode != "none"; int audioBitrate = audioSection["bitrate"].ToObject <int>(); #endregion #region 输出相关 string outputPath = PathExtension.GetFullPathOrEmpty(ioSection["output"]?.ToObject <string>() ?? string.Empty).EscapePathStringForArg(); #endregion #region 自定义参数 JToken customArgsSection = sectionData["Ruminoid.Toolbox.Plugins.Common.ConfigSections.CustomArgsConfigSection"]; string customArgs = customArgsSection["args"]?.ToObject <string>(); bool useCustomArgs = customArgsSection["use_custom_args"]?.ToObject <bool>() ?? false; #endregion #region 准备命令 List <TaskCommand> result = new(); #endregion // 开始处理 #region 处理音频 switch (audioMode) { case "copy": result.Add(new( "ffmpeg", $"-i {inputPath} -vn -sn -c:a copy -y -map 0:a:0 {atempPath}", "null")); break; case "process": result.Add(new( "ffmpeg", $"-i {inputPath} -vn -sn -v 0 -c:a pcm_s16le -f wav pipe: | {PathExtension.GetTargetPath("qaac64")} -q 2 --ignorelength -c {audioBitrate} - -o {atempPath}", "null")); break; } #endregion #region 处理字幕 if (isIncludingSubtitle) { string vpyPath = Path.ChangeExtension(inputPathIntl, "vpy").EscapePathStringForArg(); result.Add( ("node", $"rmbox-vpygen.js {inputPath} {subtitlePath} {vpyPath}", "null")); inputPath = vpyPath; } #endregion #region 处理 x264 参数 string x264Args = $"{(useCustomArgs ? customArgs : DefaultArgs)} {(isIncludingSubtitle && !isIncludingSubtitle ? "--vf subtitles --sub " + subtitlePath : "")}"; #endregion #region 处理视频 switch (x264EncodeMode) { case "crf": result.Add( GenerateVideoProcessingCommand( $"--crf {x264QualitySection["crf_value"]?.ToObject<double>():N1} {demuxerArgs} {x264Args} -o {(hasAudio ? vtempPath : outputPath)}", x264Core, inputPath, useVpy)); break; case "2pass": result.AddRange(new[] { GenerateVideoProcessingCommand( $"--pass 1 --bitrate {x264QualitySection["2pass_value"]?.ToObject<int>()} {demuxerArgs} --stats {vtempStatsPath} {x264Args} -o NUL", x264Core, inputPath, useVpy), GenerateVideoProcessingCommand( $"--pass 2 --bitrate {x264QualitySection["2pass_value"]?.ToObject<int>()} {demuxerArgs} --stats {vtempStatsPath} {x264Args} -o {(hasAudio ? vtempPath : outputPath)}", x264Core, inputPath, useVpy) }); break; default: // ReSharper disable once NotResolvedInText throw new ArgumentOutOfRangeException("encode_mode"); } #endregion #region 混流 if (hasAudio) { result.Add(new( "ffmpeg", $"-i {vtempPath} -i {atempPath} -vcodec copy -acodec copy -y {outputPath}", "ffmpeg")); } #endregion #region 清理临时文件 result.AddRange(new[] { vtempPath, atempPath, vtempStatsPath, vtempStatsMbtreePath, lwiPath } .Select(CommandExtension.GenerateTryDeleteCommand) .ToList()); if (isIncludingSubtitle) { result.Add(CommandExtension.GenerateTryDeleteCommand(inputPath)); } #endregion return(result); }