支持命令行选择视频、音频和字幕
This commit is contained in:
parent
983599b61f
commit
9bf57058cf
|
@ -28,6 +28,9 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
public static string cmd_baseUrl { get => GetText("cmd_baseUrl"); }
|
||||
public static string cmd_header { get => GetText("cmd_header"); }
|
||||
public static string cmd_muxImport { get => GetText("cmd_muxImport"); }
|
||||
public static string cmd_selectVideo { get => GetText("cmd_selectVideo"); }
|
||||
public static string cmd_selectAudio { get => GetText("cmd_selectAudio"); }
|
||||
public static string cmd_selectSubtitle { get => GetText("cmd_selectSubtitle"); }
|
||||
public static string cmd_Input { get => GetText("cmd_Input"); }
|
||||
public static string cmd_keys { get => GetText("cmd_keys"); }
|
||||
public static string cmd_keyText { get => GetText("cmd_keyText"); }
|
||||
|
@ -49,9 +52,7 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
public static string cmd_urlProcessorArgs { get => GetText("cmd_urlProcessorArgs"); }
|
||||
public static string cmd_useShakaPackager { get => GetText("cmd_useShakaPackager"); }
|
||||
public static string cmd_concurrentDownload { get => GetText("cmd_concurrentDownload"); }
|
||||
public static string cmd_useMkvmerge { get => GetText("cmd_useMkvmerge"); }
|
||||
public static string cmd_muxAfterDone { get => GetText("cmd_muxAfterDone"); }
|
||||
public static string cmd_muxToMp4 { get => GetText("cmd_muxToMp4"); }
|
||||
public static string cmd_writeMetaJson { get => GetText("cmd_writeMetaJson"); }
|
||||
public static string fetch { get => GetText("fetch"); }
|
||||
public static string ffmpegMerge { get => GetText("ffmpegMerge"); }
|
||||
|
@ -78,6 +79,7 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
public static string startDownloading { get => GetText("startDownloading"); }
|
||||
public static string streamsInfo { get => GetText("streamsInfo"); }
|
||||
public static string writeJson { get => GetText("writeJson"); }
|
||||
public static string noStreamsToDownload { get => GetText("noStreamsToDownload"); }
|
||||
|
||||
private static string GetText(string key)
|
||||
{
|
||||
|
|
|
@ -238,18 +238,90 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
zhTW: "解密时使用shaka-packager替代mp4decrypt",
|
||||
enUS: "Use shaka-packager instead of mp4decrypt to decrypt"
|
||||
),
|
||||
["cmd_useMkvmerge"] = new TextContainer
|
||||
(
|
||||
zhCN: "混流时使用mkvmerge替代ffmpeg",
|
||||
zhTW: "混流时使用mkvmerge替代ffmpeg",
|
||||
enUS: "Use mkvmerge instead of ffmpeg to mux"
|
||||
),
|
||||
["cmd_concurrentDownload"] = new TextContainer
|
||||
(
|
||||
zhCN: "并发下载已选择的音频、视频和字幕",
|
||||
zhTW: "並發下載已選擇的音訊、影片和字幕",
|
||||
enUS: "Concurrently download the selected audio, video and subtitles"
|
||||
),
|
||||
["cmd_selectVideo"] = new TextContainer
|
||||
(
|
||||
zhCN: "通过正则表达式选择符合要求的视频流. 你能够以:分隔形式指定如下参数:\r\n\r\n" +
|
||||
"id=REGEX:lang=REGEX:name=REGEX:codec=REGEX:res=REGEX\r\n" +
|
||||
"frame=REGEX:ch=REGEX:range=REGEX:url=REGEX:for=FOR\r\n\r\n" +
|
||||
"* for=FOR: 选择方式. best[number], worst[number], all (默认: best)\r\n\r\n" +
|
||||
"例如: \r\n" +
|
||||
"# 选择最佳视频\r\n" +
|
||||
"-sv best\r\n" +
|
||||
"# 选择4K+HEVC视频\r\n" +
|
||||
"-sv res=\"3840*\":codec=hvc1:for=best\r\n",
|
||||
zhTW: "通過正則表達式選擇符合要求的影片軌. 你能夠以:分隔形式指定如下參數:\r\n\r\n" +
|
||||
"id=REGEX:lang=REGEX:name=REGEX:codec=REGEX:res=REGEX\r\n" +
|
||||
"frame=REGEX:ch=REGEX:range=REGEX:url=REGEX:for=FOR\r\n\r\n" +
|
||||
"* for=FOR: 選擇方式. best[number], worst[number], all (默認: best)\r\n\r\n" +
|
||||
"例如: \r\n" +
|
||||
"# 選擇最佳影片\r\n" +
|
||||
"-sv best\r\n" +
|
||||
"# 選擇4K+HEVC影片\r\n" +
|
||||
"-sv res=\"3840*\":codec=hvc1:for=best\r\n",
|
||||
enUS: "Select video streams by regular expressions. OPTIONS is a colon separated list of:\r\n\r\n" +
|
||||
"id=REGEX:lang=REGEX:name=REGEX:codec=REGEX:res=REGEX\r\n" +
|
||||
"frame=REGEX:ch=REGEX:range=REGEX:url=REGEX:for=FOR\r\n\r\n" +
|
||||
"* for=FOR: Select type. best[number], worst[number], all (Default: best)\r\n\r\n" +
|
||||
"Examples: \r\n" +
|
||||
"# select best video\r\n" +
|
||||
"-sv best\r\n" +
|
||||
"# select 4K+HEVC video\r\n" +
|
||||
"-sv res=\"3840*\":codec=hvc1:for=best\r\n"
|
||||
),
|
||||
["cmd_selectAudio"] = new TextContainer
|
||||
(
|
||||
zhCN: "通过正则表达式选择符合要求的音频流. 参考 --select-video\r\n\r\n" +
|
||||
"例如: \r\n" +
|
||||
"# 选择所有音频\r\n" +
|
||||
"-sa all\r\n" +
|
||||
"# 选择最佳英语音轨\r\n" +
|
||||
"-sa lang=en:for=best\r\n" +
|
||||
"# 选择最佳的2条英语(或日语)音轨\r\n" +
|
||||
"-sa lang=\"ja|en\":for=best2\r\n",
|
||||
zhTW: "通過正則表達式選擇符合要求的音軌. 參考 --select-video\r\n\r\n" +
|
||||
"例如: \r\n" +
|
||||
"# 選擇所有音訊\r\n" +
|
||||
"-sa all\r\n" +
|
||||
"# 選擇最佳英語音軌\r\n" +
|
||||
"-sa lang=en:for=best\r\n" +
|
||||
"# 選擇最佳的2條英語(或日語)音軌\r\n" +
|
||||
"-sa lang=\"ja|en\":for=best2\r\n",
|
||||
enUS: "Select audio streams by regular expressions. ref --select-video\r\n\r\n" +
|
||||
"Examples: \r\n" +
|
||||
"# select all\r\n" +
|
||||
"-sa all\r\n" +
|
||||
"# select best eng audio\r\n" +
|
||||
"-sa lang=en:for=best\r\n" +
|
||||
"# select best 2, and language is ja or en\r\n" +
|
||||
"-sa lang=\"ja|en\":for=best2\r\n"
|
||||
),
|
||||
["cmd_selectSubtitle"] = new TextContainer
|
||||
(
|
||||
zhCN: "通过正则表达式选择符合要求的字幕流. 参考 --select-video\r\n\r\n" +
|
||||
"例如: \r\n" +
|
||||
"# 选择所有字幕\r\n" +
|
||||
"-ss all\r\n" +
|
||||
"# 选择所有带有\"中文\"的字幕\r\n" +
|
||||
"-ss name=\"中文\":for=all\r\n",
|
||||
zhTW: "通過正則表達式選擇符合要求的字幕流. 參考 --select-video\r\n\r\n" +
|
||||
"例如: \r\n" +
|
||||
"# 選擇所有字幕\r\n" +
|
||||
"-ss all\r\n" +
|
||||
"# 選擇所有帶有\"中文\"的字幕\r\n" +
|
||||
"-ss name=\"中文\":for=all\r\n",
|
||||
enUS: "Select subtitle streams by regular expressions. ref --select-video\r\n\r\n" +
|
||||
"Examples: \r\n" +
|
||||
"# select all subs\r\n" +
|
||||
"-ss all\r\n" +
|
||||
"# select all subs containing \"English\"\r\n" +
|
||||
"-ss name=\"English\":for=all\r\n"
|
||||
),
|
||||
["cmd_muxAfterDone"] = new TextContainer
|
||||
(
|
||||
zhCN: "所有工作完成时尝试混流分离的音视频. 你能够以:分隔形式指定如下参数:\r\n\r\n" +
|
||||
|
@ -258,11 +330,11 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
"* bin_path=PATH: 指定程序路径 (默认: 自动寻找)\r\n" +
|
||||
"* keep=BOOL: 混流完成是否删除文件 true, false (默认: true)\r\n\r\n" +
|
||||
"例如: \r\n" +
|
||||
"\r\n#混流为mp4容器\r\n" +
|
||||
"# 混流为mp4容器\r\n" +
|
||||
"-M format=mp4\r\n" +
|
||||
"\r\n#使用mkvmerge, 自动寻找程序\r\n" +
|
||||
"# 使用mkvmerge, 自动寻找程序\r\n" +
|
||||
"-M format=mkv:muxer=mkvmerge\r\n" +
|
||||
"\r\n#使用mkvmerge, 自定义程序路径\r\n" +
|
||||
"# 使用mkvmerge, 自定义程序路径\r\n" +
|
||||
"-M format=mkv:muxer=mkvmerge:bin_path=\"C\\:\\Program Files\\MKVToolNix\\mkvmerge.exe\"\r\n",
|
||||
zhTW: "所有工作完成時嘗試混流分離的影音. 你能夠以:分隔形式指定如下參數:\r\n\r\n" +
|
||||
"* format=FORMAT: 指定混流容器 mkv, mp4\r\n" +
|
||||
|
@ -270,11 +342,11 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
"* bin_path=PATH: 指定程序路徑 (默認: 自動尋找)\r\n" +
|
||||
"* keep=BOOL: 混流完成是否刪除文件 true, false (默認: true)\r\n\r\n" +
|
||||
"例如: \r\n" +
|
||||
"\r\n#混流為mp4容器\r\n" +
|
||||
"# 混流為mp4容器\r\n" +
|
||||
"-M format=mp4\r\n" +
|
||||
"\r\n#使用mkvmerge, 自動尋找程序\r\n" +
|
||||
"# 使用mkvmerge, 自動尋找程序\r\n" +
|
||||
"-M format=mkv:muxer=mkvmerge\r\n" +
|
||||
"\r\n#使用mkvmerge, 自訂程序路徑\r\n" +
|
||||
"# 使用mkvmerge, 自訂程序路徑\r\n" +
|
||||
"-M format=mkv:muxer=mkvmerge:bin_path=\"C\\:\\Program Files\\MKVToolNix\\mkvmerge.exe\"\r\n",
|
||||
enUS: "When all works is done, try to mux the downloaded streams. OPTIONS is a colon separated list of:\r\n\r\n" +
|
||||
"* format=FORMAT: set container. mkv, mp4\r\n" +
|
||||
|
@ -282,11 +354,11 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
"* bin_path=PATH: set binary file path. (Default: auto)\r\n" +
|
||||
"* keep=BOOL: set whether or not delete files. true, false (Default: true)\r\n\r\n" +
|
||||
"Examples: \r\n" +
|
||||
"\r\n#mux to mp4\r\n" +
|
||||
"# mux to mp4\r\n" +
|
||||
"-M format=mp4\r\n" +
|
||||
"\r\n#use mkvmerge, auto detect bin path\r\n" +
|
||||
"# use mkvmerge, auto detect bin path\r\n" +
|
||||
"-M format=mkv:muxer=mkvmerge\r\n" +
|
||||
"\r\n#use mkvmerge, set bin path\r\n" +
|
||||
"# use mkvmerge, set bin path\r\n" +
|
||||
"-M format=mkv:muxer=mkvmerge:bin_path=\"C\\:\\Program Files\\MKVToolNix\\mkvmerge.exe\"\r\n"
|
||||
),
|
||||
["cmd_muxImport"] = new TextContainer
|
||||
|
@ -296,35 +368,29 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
"* lang=CODE: 指定媒体文件语言代码 (非必须)\r\n" +
|
||||
"* name=NAME: 指定媒体文件描述信息 (非必须)\r\n\r\n" +
|
||||
"例如: \r\n" +
|
||||
"\r\n#引入外部字幕\r\n" +
|
||||
"# 引入外部字幕\r\n" +
|
||||
"--mux-import path=zh-Hans.srt:lang=chi:name=\"中文 (简体)\"\r\n" +
|
||||
"\r\n#引入外部音轨+字幕\r\n" +
|
||||
"# 引入外部音轨+字幕\r\n" +
|
||||
"--mux-import path=\"D\\:\\media\\atmos.m4a\":lang=eng:name=\"English Description Audio\" --mux-import path=\"D\\:\\media\\eng.vtt\":lang=eng:name=\"English (Description)\"",
|
||||
zhTW: "混流時引入外部媒體檔案. 你能夠以:分隔形式指定如下參數:\r\n\r\n" +
|
||||
"* path=PATH: 指定媒體檔案路徑\r\n" +
|
||||
"* lang=CODE: 指定媒體檔案語言代碼 (非必須)\r\n" +
|
||||
"* name=NAME: 指定媒體檔案描述訊息 (非必須)\r\n\r\n" +
|
||||
"例如: \r\n" +
|
||||
"\r\n#引入外部字幕\r\n" +
|
||||
"# 引入外部字幕\r\n" +
|
||||
"--mux-import path=zh-Hant.srt:lang=chi:name=\"中文 (繁體)\"\r\n" +
|
||||
"\r\n#引入外部音軌+字幕\r\n" +
|
||||
"# 引入外部音軌+字幕\r\n" +
|
||||
"--mux-import path=\"D\\:\\media\\atmos.m4a\":lang=eng:name=\"English Description Audio\" --mux-import path=\"D\\:\\media\\eng.vtt\":lang=eng:name=\"English (Description)\"",
|
||||
enUS: "When MuxAfterDone enabled, allow to import local media files. OPTIONS is a colon separated list of:\r\n\r\n" +
|
||||
"* path=PATH: set file path\r\n" +
|
||||
"* lang=CODE: set media language code (not required)\r\n" +
|
||||
"* name=NAME: set description (not required)\r\n\r\n" +
|
||||
"Examples: \r\n" +
|
||||
"\r\n#import subtitle\r\n" +
|
||||
"# import subtitle\r\n" +
|
||||
"--mux-import path=en-US.srt:lang=eng:name=\"English (Original)\"\r\n" +
|
||||
"\r\n#import audio and subtitle\r\n" +
|
||||
"# import audio and subtitle\r\n" +
|
||||
"--mux-import path=\"D\\:\\media\\atmos.m4a\":lang=eng:name=\"English Description Audio\" --mux-import path=\"D\\:\\media\\eng.vtt\":lang=eng:name=\"English (Description)\""
|
||||
),
|
||||
["cmd_muxToMp4"] = new TextContainer
|
||||
(
|
||||
zhCN: "混流时使用mp4容器而非mkv",
|
||||
zhTW: "混流時使用mp4容器而非mkv",
|
||||
enUS: "Use mp4 container instead of mkv when muxing"
|
||||
),
|
||||
["cmd_writeMetaJson"] = new TextContainer
|
||||
(
|
||||
zhCN: "解析后的信息是否输出json文件",
|
||||
|
@ -481,6 +547,12 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
zhTW: "寫出meta json",
|
||||
enUS: "Writing meta json"
|
||||
),
|
||||
["noStreamsToDownload"] = new TextContainer
|
||||
(
|
||||
zhCN: "没有找到需要下载的流",
|
||||
zhTW: "沒有找到需要下載的流",
|
||||
enUS: "No stream found to download"
|
||||
),
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,12 +7,15 @@ using System.CommandLine;
|
|||
using System.CommandLine.Binding;
|
||||
using System.CommandLine.Parsing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace N_m3u8DL_RE.CommandLine
|
||||
{
|
||||
internal class CommandInvoker
|
||||
internal partial class CommandInvoker
|
||||
{
|
||||
[RegexGenerator("((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);
|
||||
|
@ -27,8 +30,8 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
private readonly static Option<SubtitleFormat> SubtitleFormat = new(name: "--sub-format", description: ResString.cmd_subFormat, getDefaultValue: () => Enum.SubtitleFormat.VTT);
|
||||
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: () => 8);
|
||||
private readonly static Option<int> DownloadRetryCount = new(new string[] { "--download-retry-count" }, description: ResString.cmd_downloadRetryCount, getDefaultValue: () => 3);
|
||||
private readonly static Option<int> ThreadCount = new(new string[] { "--thread-count" }, description: ResString.cmd_threadCount, getDefaultValue: () => 8) { 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> BinaryMerge = new(new string[] { "--binary-merge" }, description: ResString.cmd_binaryMerge, getDefaultValue: () => false);
|
||||
|
@ -39,15 +42,85 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
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);
|
||||
private readonly static Option<string?> FFmpegBinaryPath = new(new string[] { "--ffmpeg-binary-path" }, description: ResString.cmd_ffmpegBinaryPath);
|
||||
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[] { "--concurrent-download" }, description: ResString.cmd_concurrentDownload, getDefaultValue: () => false);
|
||||
private readonly static Option<bool> ConcurrentDownload = new(new string[] { "-mt", "--concurrent-download" }, description: ResString.cmd_concurrentDownload, getDefaultValue: () => false);
|
||||
|
||||
//复杂命令行如下
|
||||
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" };
|
||||
|
||||
/// <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(input).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);
|
||||
|
||||
return streamFilter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分割Header
|
||||
|
@ -180,6 +253,9 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
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),
|
||||
};
|
||||
|
||||
var parsedHeaders = bindingContext.ParseResult.GetValueForOption(Headers);
|
||||
|
@ -215,13 +291,13 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
|
||||
public static async Task<int> InvokeArgs(string[] args, Func<MyOption, Task> action)
|
||||
{
|
||||
var rootCommand = new RootCommand("N_m3u8DL-RE (Beta version) 20220826")
|
||||
var rootCommand = new RootCommand("N_m3u8DL-RE (Beta version) 20220827")
|
||||
{
|
||||
Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
|
||||
BinaryMerge, DelAfterDone, WriteMetaJson, AppendUrlParams, ConcurrentDownload, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
|
||||
FFmpegBinaryPath,
|
||||
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
|
||||
MuxAfterDone, MuxImports
|
||||
MuxAfterDone, MuxImports, VideoFilter, AudioFilter, SubtitleFilter
|
||||
};
|
||||
rootCommand.TreatUnmatchedTokensAsErrors = true;
|
||||
rootCommand.SetHandler(async (myOption) => await action(myOption), new MyOptionBinder());
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
try
|
||||
{
|
||||
var index = _arg.IndexOf(key + "=");
|
||||
if (index == -1) return _arg.IndexOf(key) != -1 ? "true" : null;
|
||||
if (index == -1) return (_arg.Contains(key) && _arg.EndsWith(key)) ? "true" : null;
|
||||
|
||||
var chars = _arg[(index + key.Length + 1)..].ToCharArray();
|
||||
var result = new StringBuilder();
|
||||
|
|
|
@ -146,6 +146,18 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
/// See: <see cref="CommandInvoker.MuxImports"/>.
|
||||
/// </summary>
|
||||
public List<OutputFile>? MuxImports { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.VideoFilter"/>.
|
||||
/// </summary>
|
||||
public StreamFilter? VideoFilter { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.AudioFilter"/>.
|
||||
/// </summary>
|
||||
public StreamFilter? AudioFilter { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.SubtitleFilter"/>.
|
||||
/// </summary>
|
||||
public StreamFilter? SubtitleFilter { get; set; }
|
||||
public bool MuxKeepFiles { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using N_m3u8DL_RE.Common.Enum;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_RE.Entity
|
||||
{
|
||||
public class StreamFilter
|
||||
{
|
||||
public Regex? GroupIdReg { get; set; }
|
||||
public Regex? LanguageReg { get; set; }
|
||||
public Regex? NameReg { get; set; }
|
||||
public Regex? CodecsReg { get; set; }
|
||||
public Regex? ResolutionReg { get; set; }
|
||||
public Regex? FrameRateReg { get; set; }
|
||||
public Regex? ChannelsReg { get; set; }
|
||||
public Regex? VideoRangeReg { get; set; }
|
||||
public Regex? UrlReg { get; set; }
|
||||
|
||||
public string For { get; set; } = "best";
|
||||
}
|
||||
}
|
|
@ -44,6 +44,16 @@ namespace N_m3u8DL_RE
|
|||
await CommandInvoker.InvokeArgs(args, DoWorkAsync);
|
||||
}
|
||||
|
||||
static int GetOrder(StreamSpec streamSpec)
|
||||
{
|
||||
if (streamSpec.Channels == null) return 0;
|
||||
else
|
||||
{
|
||||
var str = streamSpec.Channels.Split('/')[0];
|
||||
return int.TryParse(str, out var order) ? order : 0;
|
||||
}
|
||||
}
|
||||
|
||||
static async Task DoWorkAsync(MyOption option)
|
||||
{
|
||||
Logger.LogLevel = option.LogLevel;
|
||||
|
@ -182,7 +192,7 @@ namespace N_m3u8DL_RE
|
|||
}
|
||||
|
||||
//全部媒体
|
||||
var lists = streams.OrderBy(p => p.MediaType).ThenByDescending(p => p.Bandwidth);
|
||||
var lists = streams.OrderBy(p => p.MediaType).ThenByDescending(p => p.Bandwidth).ThenByDescending(GetOrder);
|
||||
//基本流
|
||||
var basicStreams = lists.Where(x => x.MediaType == null || x.MediaType == MediaType.VIDEO);
|
||||
//可选音频轨道
|
||||
|
@ -203,8 +213,6 @@ namespace N_m3u8DL_RE
|
|||
Logger.InfoMarkUp(item.ToString());
|
||||
}
|
||||
|
||||
//展示交互式选择框
|
||||
//var selectedStreams = PromptUtil.SelectStreams(lists);
|
||||
var selectedStreams = new List<StreamSpec>();
|
||||
if (option.AutoSelect)
|
||||
{
|
||||
|
@ -213,7 +221,7 @@ namespace N_m3u8DL_RE
|
|||
var langs = audios.DistinctBy(a => a.Language).Select(a => a.Language);
|
||||
foreach (var lang in langs)
|
||||
{
|
||||
selectedStreams.Add(audios.Where(a => a.Language == lang).OrderByDescending(a => a.Bandwidth).First());
|
||||
selectedStreams.Add(audios.Where(a => a.Language == lang).OrderByDescending(a => a.Bandwidth).ThenByDescending(GetOrder).First());
|
||||
}
|
||||
selectedStreams.AddRange(subs);
|
||||
}
|
||||
|
@ -221,10 +229,22 @@ namespace N_m3u8DL_RE
|
|||
{
|
||||
selectedStreams.AddRange(subs);
|
||||
}
|
||||
else if (option.VideoFilter != null || option.AudioFilter != null || option.SubtitleFilter != null)
|
||||
{
|
||||
basicStreams = FilterUtil.DoFilter(basicStreams, option.VideoFilter);
|
||||
audios = FilterUtil.DoFilter(audios, option.AudioFilter);
|
||||
subs = FilterUtil.DoFilter(subs, option.SubtitleFilter);
|
||||
selectedStreams = basicStreams.Concat(audios).Concat(subs).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedStreams = PromptUtil.SelectStreams(lists);
|
||||
//展示交互式选择框
|
||||
selectedStreams = FilterUtil.SelectStreams(lists);
|
||||
}
|
||||
|
||||
if (!selectedStreams.Any())
|
||||
throw new Exception(ResString.noStreamsToDownload);
|
||||
|
||||
//一个以上的话,需要手动重新加载playlist
|
||||
if (lists.Count() > 1)
|
||||
await extractor.FetchPlayListAsync(selectedStreams);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using N_m3u8DL_RE.Common.Entity;
|
||||
using N_m3u8DL_RE.Common.Enum;
|
||||
using N_m3u8DL_RE.Common.Resource;
|
||||
using N_m3u8DL_RE.Entity;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -10,8 +11,47 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace N_m3u8DL_RE.Util
|
||||
{
|
||||
public class PromptUtil
|
||||
public class FilterUtil
|
||||
{
|
||||
public static List<StreamSpec> DoFilter(IEnumerable<StreamSpec> lists, StreamFilter? filter)
|
||||
{
|
||||
if (filter == null) return new List<StreamSpec>();
|
||||
|
||||
var inputs = lists.Where(_ => true);
|
||||
if (filter.GroupIdReg != null)
|
||||
inputs = inputs.Where(i => i.GroupId != null && filter.GroupIdReg.IsMatch(i.GroupId));
|
||||
if (filter.LanguageReg != null)
|
||||
inputs = inputs.Where(i => i.Language != null && filter.LanguageReg.IsMatch(i.Language));
|
||||
if (filter.NameReg != null)
|
||||
inputs = inputs.Where(i => i.Name != null && filter.NameReg.IsMatch(i.Name));
|
||||
if (filter.CodecsReg != null)
|
||||
inputs = inputs.Where(i => i.Codecs != null && filter.CodecsReg.IsMatch(i.Codecs));
|
||||
if (filter.ResolutionReg != null)
|
||||
inputs = inputs.Where(i => i.Resolution != null && filter.ResolutionReg.IsMatch(i.Resolution));
|
||||
if (filter.FrameRateReg != null)
|
||||
inputs = inputs.Where(i => i.FrameRate != null && filter.FrameRateReg.IsMatch($"{i.FrameRate}"));
|
||||
if (filter.ChannelsReg != null)
|
||||
inputs = inputs.Where(i => i.Channels != null && filter.ChannelsReg.IsMatch(i.Channels));
|
||||
if (filter.VideoRangeReg != null)
|
||||
inputs = inputs.Where(i => i.VideoRange != null && filter.VideoRangeReg.IsMatch(i.VideoRange));
|
||||
if (filter.UrlReg != null)
|
||||
inputs = inputs.Where(i => i.Url != null && filter.UrlReg.IsMatch(i.Url));
|
||||
|
||||
var bestNumberStr = filter.For.Replace("best", "");
|
||||
var worstNumberStr = filter.For.Replace("worst", "");
|
||||
|
||||
if (filter.For == "best" && inputs.Count() > 0)
|
||||
inputs = inputs.Take(1).ToList();
|
||||
else if (filter.For == "worst" && inputs.Count() > 0)
|
||||
inputs = inputs.TakeLast(1).ToList();
|
||||
else if (int.TryParse(bestNumberStr, out int bestNumber) && inputs.Count() > 0)
|
||||
inputs = inputs.Take(bestNumber).ToList();
|
||||
else if (int.TryParse(worstNumberStr, out int worstNumber) && inputs.Count() > 0)
|
||||
inputs = inputs.TakeLast(worstNumber).ToList();
|
||||
|
||||
return inputs.ToList();
|
||||
}
|
||||
|
||||
public static List<StreamSpec> SelectStreams(IEnumerable<StreamSpec> lists)
|
||||
{
|
||||
if (lists.Count() == 1)
|
Loading…
Reference in New Issue