2022-12-06 23:10:50 +08:00

514 lines
28 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using N_m3u8DL_RE.Common.Enum;
using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Common.Resource;
using N_m3u8DL_RE.Common.Util;
using N_m3u8DL_RE.Entity;
using N_m3u8DL_RE.Enum;
using N_m3u8DL_RE.Util;
using NiL.JS.Expressions;
using System.CommandLine;
using System.CommandLine.Binding;
using System.CommandLine.Builder;
using System.CommandLine.Parsing;
using System.Globalization;
using System.Net;
using System.Text.RegularExpressions;
namespace N_m3u8DL_RE.CommandLine
{
internal partial class CommandInvoker
{
public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20221206";
[GeneratedRegex("((best|worst)\\d*|all)")]
private static partial Regex ForStrRegex();
private readonly static Argument<string> Input = new(name: "input", description: ResString.cmd_Input);
private readonly static Option<string?> TmpDir = new(new string[] { "--tmp-dir" }, description: ResString.cmd_tmpDir);
private readonly static Option<string?> SaveDir = new(new string[] { "--save-dir" }, description: ResString.cmd_saveDir);
private readonly static Option<string?> SaveName = new(new string[] { "--save-name" }, description: ResString.cmd_saveName, parseArgument: ParseSaveName);
private readonly static Option<string?> SavePattern = new(new string[] { "--save-pattern" }, description: ResString.cmd_savePattern, getDefaultValue: () => "<SaveName>_<Id>_<Codecs>_<Language>_<Ext>");
private readonly static Option<string?> UILanguage = new Option<string?>(new string[] { "--ui-language" }, description: ResString.cmd_uiLanguage).FromAmong("en-US", "zh-CN", "zh-TW");
private readonly static Option<string?> UrlProcessorArgs = new(new string[] { "--urlprocessor-args" }, description: ResString.cmd_urlProcessorArgs);
private readonly static Option<string[]?> Keys = new(new string[] { "--key" }, description: ResString.cmd_keys) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false };
private readonly static Option<string> KeyTextFile = new(new string[] { "--key-text-file" }, description: ResString.cmd_keyText);
private readonly static Option<Dictionary<string, string>> Headers = new(new string[] { "-H", "--header" }, description: ResString.cmd_header, parseArgument: ParseHeaders) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false };
private readonly static Option<LogLevel> LogLevel = new(name: "--log-level", description: ResString.cmd_logLevel, getDefaultValue: () => Common.Log.LogLevel.INFO);
private readonly static Option<SubtitleFormat> SubtitleFormat = new(name: "--sub-format", description: ResString.cmd_subFormat, getDefaultValue: () => Enum.SubtitleFormat.SRT);
private readonly static Option<bool> AutoSelect = new(new string[] { "--auto-select" }, description: ResString.cmd_autoSelect, getDefaultValue: () => false);
private readonly static Option<bool> SubOnly = new(new string[] { "--sub-only" }, description: ResString.cmd_subOnly, getDefaultValue: () => false);
private readonly static Option<int> ThreadCount = new(new string[] { "--thread-count" }, description: ResString.cmd_threadCount, getDefaultValue: () => Environment.ProcessorCount) { ArgumentHelpName = "number" };
private readonly static Option<int> DownloadRetryCount = new(new string[] { "--download-retry-count" }, description: ResString.cmd_downloadRetryCount, getDefaultValue: () => 3) { ArgumentHelpName = "number" };
private readonly static Option<bool> SkipMerge = new(new string[] { "--skip-merge" }, description: ResString.cmd_skipMerge, getDefaultValue: () => false);
private readonly static Option<bool> SkipDownload = new(new string[] { "--skip-download" }, description: ResString.cmd_skipDownload, getDefaultValue: () => false);
private readonly static Option<bool> NoDateInfo = new(new string[] { "--no-date-info" }, description: ResString.cmd_noDateInfo, getDefaultValue: () => false);
private readonly static Option<bool> BinaryMerge = new(new string[] { "--binary-merge" }, description: ResString.cmd_binaryMerge, getDefaultValue: () => false);
private readonly static Option<bool> DelAfterDone = new(new string[] { "--del-after-done" }, description: ResString.cmd_delAfterDone, getDefaultValue: () => true);
private readonly static Option<bool> AutoSubtitleFix = new(new string[] { "--auto-subtitle-fix" }, description: ResString.cmd_subtitleFix, getDefaultValue: () => true);
private readonly static Option<bool> CheckSegmentsCount = new(new string[] { "--check-segments-count" }, description: ResString.cmd_checkSegmentsCount, getDefaultValue: () => true);
private readonly static Option<bool> WriteMetaJson = new(new string[] { "--write-meta-json" }, description: ResString.cmd_writeMetaJson, getDefaultValue: () => false);
private readonly static Option<bool> AppendUrlParams = new(new string[] { "--append-url-params" }, description: ResString.cmd_appendUrlParams, getDefaultValue: () => false);
private readonly static Option<bool> MP4RealTimeDecryption = new (new string[] { "--mp4-real-time-decryption" }, description: ResString.cmd_MP4RealTimeDecryption, getDefaultValue: () => false);
private readonly static Option<bool> UseShakaPackager = new (new string[] { "--use-shaka-packager" }, description: ResString.cmd_useShakaPackager, getDefaultValue: () => false);
private readonly static Option<string?> DecryptionBinaryPath = new(new string[] { "--decryption-binary-path" }, description: ResString.cmd_decryptionBinaryPath) { ArgumentHelpName = "PATH" };
private readonly static Option<string?> FFmpegBinaryPath = new(new string[] { "--ffmpeg-binary-path" }, description: ResString.cmd_ffmpegBinaryPath) { ArgumentHelpName = "PATH" };
private readonly static Option<string?> BaseUrl = new(new string[] { "--base-url" }, description: ResString.cmd_baseUrl);
private readonly static Option<bool> ConcurrentDownload = new(new string[] { "-mt", "--concurrent-download" }, description: ResString.cmd_concurrentDownload, getDefaultValue: () => false);
//代理选项
private readonly static Option<bool> UseSystemProxy = new(new string[] { "--use-system-proxy" }, description: ResString.cmd_useSystemProxy, getDefaultValue: () => true);
private readonly static Option<WebProxy?> CustomProxy = new(new string[] { "--custom-proxy" }, description: ResString.cmd_customProxy, parseArgument: ParseProxy) { ArgumentHelpName = "URL" };
//morehelp
private readonly static Option<string?> MoreHelp = new(new string[] { "--morehelp" }, description: ResString.cmd_moreHelp) { ArgumentHelpName = "OPTION" };
//自定义KEY等
private readonly static Option<EncryptMethod?> CustomHLSMethod = new(name: "--custom-hls-method", description: ResString.cmd_customHLSMethod) { ArgumentHelpName = "METHOD" };
private readonly static Option<byte[]?> CustomHLSKey = new(name: "--custom-hls-key", description: ResString.cmd_customHLSKey, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" };
private readonly static Option<byte[]?> CustomHLSIv = new(name: "--custom-hls-iv", description: ResString.cmd_customHLSIv, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" };
//任务开始时间
private readonly static Option<DateTime?> TaskStartAt = new(new string[] { "--task-start-at" }, description: ResString.cmd_taskStartAt, parseArgument: ParseStartTime) { ArgumentHelpName = "yyyyMMddHHmmss" };
//直播相关
private readonly static Option<bool> LivePerformAsVod = new(new string[] { "--live-perform-as-vod" }, description: ResString.cmd_livePerformAsVod, getDefaultValue: () => false);
private readonly static Option<bool> LiveRealTimeMerge = new(new string[] { "--live-real-time-merge" }, description: ResString.cmd_liveRealTimeMerge, getDefaultValue: () => false);
private readonly static Option<bool> LiveKeepSegments = new(new string[] { "--live-keep-segments" }, description: ResString.cmd_liveKeepSegments, getDefaultValue: () => true);
private readonly static Option<TimeSpan?> LiveRecordLimit = new(new string[] { "--live-record-limit" }, description: ResString.cmd_liveRecordLimit, parseArgument: ParseLiveLimit) { ArgumentHelpName = "HH:mm:ss" };
private readonly static Option<int?> LiveWaitTime = new(new string[] { "--live-wait-time" }, description: ResString.cmd_liveWaitTime) { ArgumentHelpName = "SEC" };
//复杂命令行如下
private readonly static Option<MuxOptions?> MuxAfterDone = new(new string[] { "-M", "--mux-after-done" }, description: ResString.cmd_muxAfterDone, parseArgument: ParseMuxAfterDone) { ArgumentHelpName = "OPTIONS" };
private readonly static Option<List<OutputFile>> MuxImports = new("--mux-import", description: ResString.cmd_muxImport, parseArgument: ParseImports) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false, ArgumentHelpName = "OPTIONS" };
private readonly static Option<StreamFilter?> VideoFilter = new(new string[] { "-sv", "--select-video" }, description: ResString.cmd_selectVideo, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
private readonly static Option<StreamFilter?> AudioFilter = new(new string[] { "-sa", "--select-audio" }, description: ResString.cmd_selectAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
private readonly static Option<StreamFilter?> SubtitleFilter = new(new string[] { "-ss", "--select-subtitle" }, description: ResString.cmd_selectSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
private readonly static Option<StreamFilter?> DropVideoFilter = new(new string[] { "-dv", "--drop-video" }, description: ResString.cmd_dropVideo, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
private readonly static Option<StreamFilter?> DropAudioFilter = new(new string[] { "-da", "--drop-audio" }, description: ResString.cmd_dropAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
private readonly static Option<StreamFilter?> DropSubtitleFilter = new(new string[] { "-ds", "--drop-subtitle" }, description: ResString.cmd_dropSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
/// <summary>
/// 解析用户代理
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
private static WebProxy? ParseProxy(ArgumentResult result)
{
var input = result.Tokens.First().Value;
try
{
if (string.IsNullOrEmpty(input))
return null;
var uri = new Uri(input);
var proxy = new WebProxy(uri, true);
if (!string.IsNullOrEmpty(uri.UserInfo))
{
var infos = uri.UserInfo.Split(':');
proxy.Credentials = new NetworkCredential(infos.First(), infos.Last());
}
return proxy;
}
catch (Exception ex)
{
result.ErrorMessage = $"error in parse proxy: " + ex.Message;
return null;
}
}
/// <summary>
/// 解析自定义KEY
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
private static byte[]? ParseHLSCustomKey(ArgumentResult result)
{
var input = result.Tokens.First().Value;
try
{
if (string.IsNullOrEmpty(input))
return null;
if (File.Exists(input))
return File.ReadAllBytes(input);
else if (HexUtil.TryParseHexString(input, out byte[]? bytes))
return bytes;
else
return Convert.FromBase64String(input);
}
catch (Exception)
{
result.ErrorMessage = "error in parse hls custom key: " + input;
return null;
}
}
/// <summary>
/// 解析录制直播时长限制
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
private static TimeSpan? ParseLiveLimit(ArgumentResult result)
{
var input = result.Tokens.First().Value;
try
{
return OtherUtil.ParseDur(input);
}
catch (Exception)
{
result.ErrorMessage = "error in parse LiveRecordLimit: " + input;
return null;
}
}
/// <summary>
/// 解析任务开始时间
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
private static DateTime? ParseStartTime(ArgumentResult result)
{
var input = result.Tokens.First().Value;
try
{
CultureInfo provider = CultureInfo.InvariantCulture;
return DateTime.ParseExact(input, "yyyyMMddHHmmss", provider);
}
catch (Exception)
{
result.ErrorMessage = "error in parse TaskStartTime: " + input;
return null;
}
}
private static string? ParseSaveName(ArgumentResult result)
{
var input = result.Tokens.First().Value;
var newName = OtherUtil.GetValidFileName(input);
if (string.IsNullOrEmpty(newName))
{
result.ErrorMessage = "Invalid save name!";
return null;
}
return newName;
}
/// <summary>
/// 流过滤器
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
private static StreamFilter? ParseStreamFilter(ArgumentResult result)
{
var streamFilter = new StreamFilter();
var input = result.Tokens.First().Value;
var p = new ComplexParamParser(input);
//目标范围
var forStr = "";
if (input == ForStrRegex().Match(input).Value)
{
forStr = input;
}
else
{
forStr = p.GetValue("for") ?? "best";
if (forStr != ForStrRegex().Match(forStr).Value)
{
result.ErrorMessage = $"for={forStr} not valid";
return null;
}
}
streamFilter.For = forStr;
var id = p.GetValue("id");
if (!string.IsNullOrEmpty(id))
streamFilter.GroupIdReg = new Regex(id);
var lang = p.GetValue("lang");
if (!string.IsNullOrEmpty(lang))
streamFilter.LanguageReg = new Regex(lang);
var name = p.GetValue("name");
if (!string.IsNullOrEmpty(name))
streamFilter.NameReg = new Regex(name);
var codecs = p.GetValue("codecs");
if (!string.IsNullOrEmpty(codecs))
streamFilter.CodecsReg = new Regex(codecs);
var res = p.GetValue("res");
if (!string.IsNullOrEmpty(res))
streamFilter.ResolutionReg = new Regex(res);
var frame = p.GetValue("frame");
if (!string.IsNullOrEmpty(frame))
streamFilter.FrameRateReg = new Regex(frame);
var channel = p.GetValue("channel");
if (!string.IsNullOrEmpty(channel))
streamFilter.ChannelsReg = new Regex(channel);
var range = p.GetValue("range");
if (!string.IsNullOrEmpty(range))
streamFilter.VideoRangeReg = new Regex(range);
var url = p.GetValue("url");
if (!string.IsNullOrEmpty(url))
streamFilter.UrlReg = new Regex(url);
var segsMin = p.GetValue("segsMin");
if (!string.IsNullOrEmpty(segsMin))
streamFilter.SegmentsMinCount = long.Parse(segsMin);
var segsMax = p.GetValue("segsMax");
if (!string.IsNullOrEmpty(segsMax))
streamFilter.SegmentsMaxCount = long.Parse(segsMax);
return streamFilter;
}
/// <summary>
/// 分割Header
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
private static Dictionary<string, string> ParseHeaders(ArgumentResult result)
{
var array = result.Tokens.Select(t => t.Value).ToArray();
return OtherUtil.SplitHeaderArrayToDic(array);
}
/// <summary>
/// 解析混流引入的外部文件
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
private static List<OutputFile> ParseImports(ArgumentResult result)
{
var imports = new List<OutputFile>();
foreach (var item in result.Tokens)
{
var p = new ComplexParamParser(item.Value);
var path = p.GetValue("path") ?? item.Value; //若未获取到直接整个字符串作为path
var lang = p.GetValue("lang");
var name = p.GetValue("name");
if (string.IsNullOrEmpty(path) || !File.Exists(path))
{
result.ErrorMessage = "path empty or file not exists!";
return imports;
}
imports.Add(new OutputFile()
{
Index = 999,
FilePath = path,
LangCode = lang,
Description = name
});
}
return imports;
}
/// <summary>
/// 解析混流选项
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
private static MuxOptions? ParseMuxAfterDone(ArgumentResult result)
{
var v = result.Tokens.First().Value;
var p = new ComplexParamParser(v);
//混流格式
var format = p.GetValue("format") ?? v.Split(':')[0]; //若未获取到,直接:前的字符串作为format解析
if (format != "mp4" && format != "mkv")
{
result.ErrorMessage = $"format={format} not valid";
return null;
}
//混流器
var muxer = p.GetValue("muxer") ?? "ffmpeg";
if (muxer != "ffmpeg" && muxer != "mkvmerge")
{
result.ErrorMessage = $"muxer={muxer} not valid";
return null;
}
//混流器路径
var bin_path = p.GetValue("bin_path") ?? "auto";
if (string.IsNullOrEmpty(bin_path))
{
result.ErrorMessage = $"bin_path={bin_path} not valid";
return null;
}
//是否删除
var keep = p.GetValue("keep") ?? "false";
if (keep != "true" && keep != "false")
{
result.ErrorMessage = $"keep={keep} not valid";
return null;
}
//冲突检测
if (muxer == "mkvmerge" && format == "mp4")
{
result.ErrorMessage = $"mkvmerge can not do mp4";
return null;
}
return new MuxOptions()
{
UseMkvmerge = muxer == "mkvmerge",
MuxToMp4 = format == "mp4",
KeepFiles = keep == "true",
BinPath = bin_path == "auto" ? null : bin_path
};
}
class MyOptionBinder : BinderBase<MyOption>
{
protected override MyOption GetBoundValue(BindingContext bindingContext)
{
var option = new MyOption
{
Input = bindingContext.ParseResult.GetValueForArgument(Input),
LogLevel = bindingContext.ParseResult.GetValueForOption(LogLevel),
AutoSelect = bindingContext.ParseResult.GetValueForOption(AutoSelect),
SkipMerge = bindingContext.ParseResult.GetValueForOption(SkipMerge),
BinaryMerge = bindingContext.ParseResult.GetValueForOption(BinaryMerge),
DelAfterDone = bindingContext.ParseResult.GetValueForOption(DelAfterDone),
AutoSubtitleFix = bindingContext.ParseResult.GetValueForOption(AutoSubtitleFix),
CheckSegmentsCount = bindingContext.ParseResult.GetValueForOption(CheckSegmentsCount),
SubtitleFormat = bindingContext.ParseResult.GetValueForOption(SubtitleFormat),
SubOnly = bindingContext.ParseResult.GetValueForOption(SubOnly),
TmpDir = bindingContext.ParseResult.GetValueForOption(TmpDir),
SaveDir = bindingContext.ParseResult.GetValueForOption(SaveDir),
SaveName = bindingContext.ParseResult.GetValueForOption(SaveName),
ThreadCount = bindingContext.ParseResult.GetValueForOption(ThreadCount),
UILanguage = bindingContext.ParseResult.GetValueForOption(UILanguage),
SkipDownload = bindingContext.ParseResult.GetValueForOption(SkipDownload),
WriteMetaJson = bindingContext.ParseResult.GetValueForOption(WriteMetaJson),
AppendUrlParams = bindingContext.ParseResult.GetValueForOption(AppendUrlParams),
SavePattern = bindingContext.ParseResult.GetValueForOption(SavePattern),
Keys = bindingContext.ParseResult.GetValueForOption(Keys),
UrlProcessorArgs = bindingContext.ParseResult.GetValueForOption(UrlProcessorArgs),
MP4RealTimeDecryption = bindingContext.ParseResult.GetValueForOption(MP4RealTimeDecryption),
UseShakaPackager = bindingContext.ParseResult.GetValueForOption(UseShakaPackager),
DecryptionBinaryPath = bindingContext.ParseResult.GetValueForOption(DecryptionBinaryPath),
FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath),
KeyTextFile = bindingContext.ParseResult.GetValueForOption(KeyTextFile),
DownloadRetryCount = bindingContext.ParseResult.GetValueForOption(DownloadRetryCount),
BaseUrl = bindingContext.ParseResult.GetValueForOption(BaseUrl),
MuxImports = bindingContext.ParseResult.GetValueForOption(MuxImports),
ConcurrentDownload = bindingContext.ParseResult.GetValueForOption(ConcurrentDownload),
VideoFilter = bindingContext.ParseResult.GetValueForOption(VideoFilter),
AudioFilter = bindingContext.ParseResult.GetValueForOption(AudioFilter),
SubtitleFilter = bindingContext.ParseResult.GetValueForOption(SubtitleFilter),
DropVideoFilter = bindingContext.ParseResult.GetValueForOption(DropVideoFilter),
DropAudioFilter = bindingContext.ParseResult.GetValueForOption(DropAudioFilter),
DropSubtitleFilter = bindingContext.ParseResult.GetValueForOption(DropSubtitleFilter),
LiveRealTimeMerge = bindingContext.ParseResult.GetValueForOption(LiveRealTimeMerge),
LiveKeepSegments = bindingContext.ParseResult.GetValueForOption(LiveKeepSegments),
LiveRecordLimit = bindingContext.ParseResult.GetValueForOption(LiveRecordLimit),
TaskStartAt = bindingContext.ParseResult.GetValueForOption(TaskStartAt),
LivePerformAsVod = bindingContext.ParseResult.GetValueForOption(LivePerformAsVod),
UseSystemProxy = bindingContext.ParseResult.GetValueForOption(UseSystemProxy),
CustomProxy = bindingContext.ParseResult.GetValueForOption(CustomProxy),
LiveWaitTime = bindingContext.ParseResult.GetValueForOption(LiveWaitTime),
NoDateInfo = bindingContext.ParseResult.GetValueForOption(NoDateInfo),
};
if (bindingContext.ParseResult.HasOption(CustomHLSMethod)) option.CustomHLSMethod = bindingContext.ParseResult.GetValueForOption(CustomHLSMethod);
if (bindingContext.ParseResult.HasOption(CustomHLSKey)) option.CustomHLSKey = bindingContext.ParseResult.GetValueForOption(CustomHLSKey);
if (bindingContext.ParseResult.HasOption(CustomHLSIv)) option.CustomHLSIv = bindingContext.ParseResult.GetValueForOption(CustomHLSIv);
var parsedHeaders = bindingContext.ParseResult.GetValueForOption(Headers);
if (parsedHeaders != null)
option.Headers = parsedHeaders;
//以用户选择语言为准优先
if (option.UILanguage != null)
{
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(option.UILanguage);
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(option.UILanguage);
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(option.UILanguage);
}
//混流设置
var muxAfterDoneValue = bindingContext.ParseResult.GetValueForOption(MuxAfterDone);
if (muxAfterDoneValue != null)
{
option.MuxAfterDone = true;
option.MuxKeepFiles = muxAfterDoneValue.KeepFiles;
option.MuxToMp4 = muxAfterDoneValue.MuxToMp4;
option.UseMkvmerge = muxAfterDoneValue.UseMkvmerge;
if (option.UseMkvmerge) option.MkvmergeBinaryPath = muxAfterDoneValue.BinPath;
else option.FFmpegBinaryPath = muxAfterDoneValue.BinPath;
}
return option;
}
}
public static async Task<int> InvokeArgs(string[] args, Func<MyOption, Task> action)
{
var argList = new List<string>(args);
var index = -1;
if ((index = argList.IndexOf("--morehelp")) >= 0 && argList.Count > index + 1)
{
var option = argList[index + 1];
var msg = option switch
{
"mux-after-done" => ResString.cmd_muxAfterDone_more,
"mux-import" => ResString.cmd_muxImport_more,
"select-video" => ResString.cmd_selectVideo_more,
"select-audio" => ResString.cmd_selectAudio_more,
"select-subtitle" => ResString.cmd_selectSubtitle_more,
_ => $"Option=\"{option}\" not found"
};
Console.WriteLine($"More Help:\r\n\r\n --{option}\r\n\r\n" + msg);
Environment.Exit(0);
}
var rootCommand = new RootCommand(VERSION_INFO)
{
Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
BinaryMerge, DelAfterDone, NoDateInfo, WriteMetaJson, AppendUrlParams, ConcurrentDownload, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
FFmpegBinaryPath,
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
MuxAfterDone,
CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, TaskStartAt,
LivePerformAsVod, LiveRealTimeMerge, LiveKeepSegments, LiveRecordLimit, LiveWaitTime,
MuxImports, VideoFilter, AudioFilter, SubtitleFilter, DropVideoFilter, DropAudioFilter, DropSubtitleFilter, MoreHelp
};
rootCommand.TreatUnmatchedTokensAsErrors = true;
rootCommand.SetHandler(async (myOption) => await action(myOption), new MyOptionBinder());
var parser = new CommandLineBuilder(rootCommand)
.UseDefaults()
.EnablePosixBundling(false)
.UseExceptionHandler((ex, context) =>
{
try { Console.CursorVisible = true; } catch { }
string msg = Logger.LogLevel == Common.Log.LogLevel.DEBUG ? ex.ToString() : ex.Message;
#if DEBUG
msg = ex.ToString();
#endif
Logger.Error(msg);
Thread.Sleep(3000);
}, 1)
.Build();
return await parser.InvokeAsync(args);
}
}
}