增加复杂混流选项配置;增加支持引入外部文件混流
This commit is contained in:
parent
df63b42356
commit
78473cebb8
|
@ -27,6 +27,7 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
public static string cmd_mkvmergeBinaryPath { get => GetText("cmd_mkvmergeBinaryPath"); }
|
||||
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_Input { get => GetText("cmd_Input"); }
|
||||
public static string cmd_keys { get => GetText("cmd_keys"); }
|
||||
public static string cmd_keyText { get => GetText("cmd_keyText"); }
|
||||
|
|
|
@ -246,9 +246,72 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
),
|
||||
["cmd_muxAfterDone"] = new TextContainer
|
||||
(
|
||||
zhCN: "所有工作完成时尝试使用ffmpeg混流分离的音视频(默认容器: mkv)",
|
||||
zhTW: "所有工作完成時嘗試使用ffmpeg混流分離的影音(默認容器: mkv)",
|
||||
enUS: "When all works is done, try to use ffmpeg to mux the separated streams.(Default container: mkv)"
|
||||
zhCN: "所有工作完成时尝试混流分离的音视频. 你能够以:分隔形式指定如下参数:\r\n\r\n" +
|
||||
"* format=FORMAT: 指定混流容器 mkv, mp4\r\n" +
|
||||
"* muxer=MUXER: 指定混流程序 ffmpeg, mkvmerge (默认: ffmpeg)\r\n" +
|
||||
"* bin_path=PATH: 指定程序路径 (默认: 自动寻找)\r\n" +
|
||||
"* keep=BOOL: 混流完成是否删除文件 true, false (默认: true)\r\n\r\n" +
|
||||
"例如: \r\n" +
|
||||
"\r\n#混流为mp4容器\r\n" +
|
||||
"-M format=mp4\r\n" +
|
||||
"\r\n#使用mkvmerge, 自动寻找程序\r\n" +
|
||||
"-M format=mkv:muxer=mkvmerge\r\n" +
|
||||
"\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" +
|
||||
"* muxer=MUXER: 指定混流程序 ffmpeg, mkvmerge (默認: ffmpeg)\r\n" +
|
||||
"* bin_path=PATH: 指定程序路徑 (默認: 自動尋找)\r\n" +
|
||||
"* keep=BOOL: 混流完成是否刪除文件 true, false (默認: true)\r\n\r\n" +
|
||||
"例如: \r\n" +
|
||||
"\r\n#混流為mp4容器\r\n" +
|
||||
"-M format=mp4\r\n" +
|
||||
"\r\n#使用mkvmerge, 自動尋找程序\r\n" +
|
||||
"-M format=mkv:muxer=mkvmerge\r\n" +
|
||||
"\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" +
|
||||
"* muxer=MUXER: set muxer. ffmpeg, mkvmerge (Default: ffmpeg)\r\n" +
|
||||
"* 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" +
|
||||
"-M format=mp4\r\n" +
|
||||
"\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" +
|
||||
"-M format=mkv:muxer=mkvmerge:bin_path=\"C\\:\\Program Files\\MKVToolNix\\mkvmerge.exe\"\r\n"
|
||||
),
|
||||
["cmd_muxImport"] = new TextContainer
|
||||
(
|
||||
zhCN: "混流时引入外部媒体文件. 你能够以:分隔形式指定如下参数:\r\n\r\n" +
|
||||
"* path=PATH: 指定媒体文件路径\r\n" +
|
||||
"* lang=CODE: 指定媒体文件语言代码 (非必须)\r\n" +
|
||||
"* name=NAME: 指定媒体文件描述信息 (非必须)\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" +
|
||||
"--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" +
|
||||
"--mux-import path=zh-Hant.srt:lang=chi:name=\"中文 (繁體)\"\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" +
|
||||
"--mux-import path=en-US.srt:lang=eng:name=\"English (Original)\"\r\n" +
|
||||
"\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
|
||||
(
|
||||
|
|
|
@ -11,10 +11,7 @@ namespace N_m3u8DL_RE.Parser.Config
|
|||
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>()
|
||||
{
|
||||
["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
|
||||
};
|
||||
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// 内容前置处理器. 调用顺序与列表顺序相同
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
using N_m3u8DL_RE.Common.Log;
|
||||
using N_m3u8DL_RE.Common.Resource;
|
||||
using N_m3u8DL_RE.Entity;
|
||||
using N_m3u8DL_RE.Enum;
|
||||
using N_m3u8DL_RE.Util;
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Binding;
|
||||
using System.CommandLine.Parsing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
|
@ -17,9 +20,9 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
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.ZeroOrMore, AllowMultipleArgumentsPerToken = false };
|
||||
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<string[]?> Headers = new(new string[] { "-H", "--header" }, description: ResString.cmd_header) { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = false };
|
||||
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.VTT);
|
||||
private readonly static Option<bool> AutoSelect = new(new string[] { "--auto-select" }, description: ResString.cmd_autoSelect, getDefaultValue: () => false);
|
||||
|
@ -36,14 +39,118 @@ 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<bool> MuxAfterDone = new (new string[] { "--mux-after-done" }, description: ResString.cmd_muxAfterDone, getDefaultValue: () => false);
|
||||
private readonly static Option<bool> MuxToMp4 = new (new string[] { "--mux-to-mp4" }, description: ResString.cmd_muxToMp4, getDefaultValue: () => false);
|
||||
private readonly static Option<bool> UseMkvmerge = new(new string[] { "--use-mkvmerge" }, description: ResString.cmd_useMkvmerge, 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?> MkvmergeBinaryPath = new(new string[] { "--mkvmerge-binary-path" }, description: ResString.cmd_mkvmergeBinaryPath);
|
||||
private readonly static Option<string?> BaseUrl = new(new string[] { "--base-url" }, description: ResString.cmd_baseUrl);
|
||||
|
||||
//复杂命令行如下
|
||||
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" };
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 分割Header
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
private static Dictionary<string, string> ParseHeaders(ArgumentResult result)
|
||||
{
|
||||
//默认的Headers
|
||||
var headers = new Dictionary<string, string>()
|
||||
{
|
||||
["user-agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
|
||||
};
|
||||
var array = result.Tokens.Select(t => t.Value).ToArray();
|
||||
var otherHeaders = ConvertUtil.SplitHeaderArrayToDic(array);
|
||||
foreach (var h in otherHeaders)
|
||||
{
|
||||
headers[h.Key] = h.Value;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
/// <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");
|
||||
var lang = p.GetValue("lang") ?? "und";
|
||||
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()
|
||||
{
|
||||
FilePath = path,
|
||||
LangCode = lang,
|
||||
Description = name
|
||||
});
|
||||
}
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析混流选项
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
private static MuxOptions? ParseMuxAfterDone(ArgumentResult result)
|
||||
{
|
||||
var p = new ComplexParamParser(result.Tokens.First().Value);
|
||||
//混流格式
|
||||
var format = p.GetValue("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)
|
||||
|
@ -51,7 +158,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
var option = new MyOption
|
||||
{
|
||||
Input = bindingContext.ParseResult.GetValueForArgument(Input),
|
||||
Headers = bindingContext.ParseResult.GetValueForOption(Headers),
|
||||
Headers = bindingContext.ParseResult.GetValueForOption(Headers)!,
|
||||
LogLevel = bindingContext.ParseResult.GetValueForOption(LogLevel),
|
||||
AutoSelect = bindingContext.ParseResult.GetValueForOption(AutoSelect),
|
||||
SkipMerge = bindingContext.ParseResult.GetValueForOption(SkipMerge),
|
||||
|
@ -78,10 +185,8 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath),
|
||||
KeyTextFile = bindingContext.ParseResult.GetValueForOption(KeyTextFile),
|
||||
DownloadRetryCount = bindingContext.ParseResult.GetValueForOption(DownloadRetryCount),
|
||||
MuxAfterDone = bindingContext.ParseResult.GetValueForOption(MuxAfterDone),
|
||||
UseMkvmerge = bindingContext.ParseResult.GetValueForOption(UseMkvmerge),
|
||||
BaseUrl = bindingContext.ParseResult.GetValueForOption(BaseUrl),
|
||||
MuxToMp4 = bindingContext.ParseResult.GetValueForOption(MuxToMp4),
|
||||
MuxImports = bindingContext.ParseResult.GetValueForOption(MuxImports),
|
||||
};
|
||||
|
||||
|
||||
|
@ -93,6 +198,19 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
@ -100,12 +218,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) 20220823")
|
||||
var rootCommand = new RootCommand("N_m3u8DL-RE (Beta version) 20220825")
|
||||
{
|
||||
Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
|
||||
BinaryMerge, DelAfterDone, WriteMetaJson, MuxAfterDone, MuxToMp4, UseMkvmerge, AppendUrlParams, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
|
||||
FFmpegBinaryPath, MkvmergeBinaryPath,
|
||||
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption
|
||||
BinaryMerge, DelAfterDone, WriteMetaJson, AppendUrlParams, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
|
||||
FFmpegBinaryPath,
|
||||
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
|
||||
MuxAfterDone, MuxImports
|
||||
};
|
||||
rootCommand.TreatUnmatchedTokensAsErrors = true;
|
||||
rootCommand.SetHandler(async (myOption) => await action(myOption), new MyOptionBinder());
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_RE.CommandLine
|
||||
{
|
||||
internal class ComplexParamParser
|
||||
{
|
||||
private string _arg;
|
||||
public ComplexParamParser(string arg)
|
||||
{
|
||||
_arg = arg;
|
||||
}
|
||||
|
||||
public string? GetValue(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(_arg)) return null;
|
||||
|
||||
try
|
||||
{
|
||||
var index = _arg.IndexOf(key + "=");
|
||||
if (index == -1) return _arg.IndexOf(key) != -1 ? "true" : null;
|
||||
|
||||
var chars = _arg[(index + key.Length + 1)..].ToCharArray();
|
||||
var result = new StringBuilder();
|
||||
char last = '\0';
|
||||
for (int i = 0; i < chars.Length; i++)
|
||||
{
|
||||
if (chars[i] == ':')
|
||||
{
|
||||
if (last == '\\')
|
||||
{
|
||||
result.Replace("\\", "");
|
||||
last = chars[i];
|
||||
result.Append(chars[i]);
|
||||
}
|
||||
else break;
|
||||
}
|
||||
else
|
||||
{
|
||||
last = chars[i];
|
||||
result.Append(chars[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var resultStr = result.ToString().Trim().Trim('\"').Trim('\'');
|
||||
|
||||
//不应该有引号出现
|
||||
if (resultStr.Contains('\"') || resultStr.Contains('\'')) throw new Exception();
|
||||
|
||||
return resultStr;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ArgumentException($"Parse Argument [{key}] failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using N_m3u8DL_RE.Common.Log;
|
||||
using N_m3u8DL_RE.Entity;
|
||||
using N_m3u8DL_RE.Enum;
|
||||
|
||||
namespace N_m3u8DL_RE.CommandLine
|
||||
|
@ -12,7 +13,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.Headers"/>.
|
||||
/// </summary>
|
||||
public string[]? Headers { get; set; }
|
||||
public Dictionary<string,string> Headers { get; set; } = new Dictionary<string,string>();
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.Keys"/>.
|
||||
/// </summary>
|
||||
|
@ -137,5 +138,10 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
/// See: <see cref="CommandInvoker.MkvmergeBinaryPath"/>.
|
||||
/// </summary>
|
||||
public string? MkvmergeBinaryPath { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.MuxImports"/>.
|
||||
/// </summary>
|
||||
public List<OutputFile>? MuxImports { get; set; }
|
||||
public bool MuxKeepFiles { get; set; }
|
||||
}
|
||||
}
|
|
@ -11,132 +11,19 @@ namespace N_m3u8DL_RE.Config
|
|||
{
|
||||
internal class DownloaderConfig
|
||||
{
|
||||
public DownloaderConfig() { }
|
||||
public required MyOption MyOptions { get; set; }
|
||||
|
||||
public DownloaderConfig(MyOption option)
|
||||
{
|
||||
AutoSubtitleFix = option.AutoSubtitleFix;
|
||||
SkipMerge = option.SkipMerge;
|
||||
BinaryMerge = option.BinaryMerge;
|
||||
DelAfterDone = option.DelAfterDone;
|
||||
CheckSegmentsCount = option.CheckSegmentsCount;
|
||||
SubtitleFormat = option.SubtitleFormat;
|
||||
TmpDir = option.TmpDir;
|
||||
SaveName = option.SaveName;
|
||||
SaveDir = option.SaveDir;
|
||||
ThreadCount = option.ThreadCount;
|
||||
SavePattern = option.SavePattern;
|
||||
Keys = option.Keys;
|
||||
MP4RealTimeDecryption = option.MP4RealTimeDecryption;
|
||||
UseShakaPackager = option.UseShakaPackager;
|
||||
DecryptionBinaryPath = option.DecryptionBinaryPath;
|
||||
FFmpegBinaryPath = option.FFmpegBinaryPath;
|
||||
KeyTextFile = option.KeyTextFile;
|
||||
DownloadRetryCount = option.DownloadRetryCount;
|
||||
MuxAfterDone = option.MuxAfterDone;
|
||||
UseMkvmerge = option.UseMkvmerge;
|
||||
MkvmergeBinaryPath = option.MkvmergeBinaryPath;
|
||||
MuxToMp4 = option.MuxToMp4;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 临时文件存储目录
|
||||
/// </summary>
|
||||
public string? TmpDir { get; set; }
|
||||
/// <summary>
|
||||
/// 文件存储目录
|
||||
/// </summary>
|
||||
public string? SaveDir { get; set; }
|
||||
/// <summary>
|
||||
/// 文件名
|
||||
/// </summary>
|
||||
public string? SaveName { get; set; }
|
||||
/// <summary>
|
||||
/// 文件名模板
|
||||
/// </summary>
|
||||
public string? SavePattern { get; set; }
|
||||
/// <summary>
|
||||
/// 线程数
|
||||
/// </summary>
|
||||
public int ThreadCount { get; set; } = 8;
|
||||
/// <summary>
|
||||
/// 每个分片的重试次数
|
||||
/// </summary>
|
||||
public int DownloadRetryCount { get; set; } = 3;
|
||||
/// <summary>
|
||||
/// 跳过合并
|
||||
/// </summary>
|
||||
public bool SkipMerge { get; set; } = false;
|
||||
/// <summary>
|
||||
/// 二进制合并
|
||||
/// </summary>
|
||||
public bool BinaryMerge { get; set; } = false;
|
||||
/// <summary>
|
||||
/// 完成后是否删除临时文件
|
||||
/// </summary>
|
||||
public bool DelAfterDone { get; set; } = false;
|
||||
/// <summary>
|
||||
/// 校验有没有下完全部分片
|
||||
/// </summary>
|
||||
public bool CheckSegmentsCount { get; set; } = true;
|
||||
/// <summary>
|
||||
/// 校验响应头的文件大小和实际大小
|
||||
/// </summary>
|
||||
public bool CheckContentLength { get; set; } = true;
|
||||
/// <summary>
|
||||
/// 自动修复字幕
|
||||
/// </summary>
|
||||
public bool AutoSubtitleFix { get; set; } = true;
|
||||
/// <summary>
|
||||
/// MP4实时解密
|
||||
/// </summary>
|
||||
public bool MP4RealTimeDecryption { get; set; } = true;
|
||||
/// <summary>
|
||||
/// 使用shaka-packager替代mp4decrypt
|
||||
/// </summary>
|
||||
public bool UseShakaPackager { get; set; }
|
||||
/// <summary>
|
||||
/// 自动混流音视频
|
||||
/// </summary>
|
||||
public bool MuxAfterDone { get; set; }
|
||||
/// <summary>
|
||||
/// 自动混流音视频容器使用mp4
|
||||
/// </summary>
|
||||
public bool MuxToMp4 { get; set; }
|
||||
/// <summary>
|
||||
/// 使用mkvmerge混流
|
||||
/// </summary>
|
||||
public bool UseMkvmerge { get; set; }
|
||||
/// <summary>
|
||||
/// MP4解密所用工具的全路径
|
||||
/// </summary>
|
||||
public string? DecryptionBinaryPath { get; set; }
|
||||
/// <summary>
|
||||
/// 字幕格式
|
||||
/// </summary>
|
||||
public SubtitleFormat SubtitleFormat { get; set; } = SubtitleFormat.VTT;
|
||||
/// <summary>
|
||||
/// 请求头
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>()
|
||||
{
|
||||
["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
|
||||
};
|
||||
/// <summary>
|
||||
/// 解密KEYs
|
||||
/// </summary>
|
||||
public string[]? Keys { get; set; }
|
||||
/// <summary>
|
||||
/// KID-KEY文件
|
||||
/// </summary>
|
||||
public string? KeyTextFile { get; set; }
|
||||
/// <summary>
|
||||
/// ffmpeg路径
|
||||
/// </summary>
|
||||
public string? FFmpegBinaryPath { get; set; }
|
||||
/// <summary>
|
||||
/// mkvmerge路径
|
||||
/// </summary>
|
||||
public string? MkvmergeBinaryPath { get; set; }
|
||||
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,27 +69,27 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
//从文件读取KEY
|
||||
private async Task SearchKeyAsync(string? currentKID)
|
||||
{
|
||||
var _key = await MP4DecryptUtil.SearchKeyFromFile(DownloaderConfig.KeyTextFile, currentKID);
|
||||
var _key = await MP4DecryptUtil.SearchKeyFromFile(DownloaderConfig.MyOptions.KeyTextFile, currentKID);
|
||||
if (_key != null)
|
||||
{
|
||||
if (DownloaderConfig.Keys == null)
|
||||
DownloaderConfig.Keys = new string[] { _key };
|
||||
if (DownloaderConfig.MyOptions.Keys == null)
|
||||
DownloaderConfig.MyOptions.Keys = new string[] { _key };
|
||||
else
|
||||
DownloaderConfig.Keys = DownloaderConfig.Keys.Concat(new string[] { _key }).ToArray();
|
||||
DownloaderConfig.MyOptions.Keys = DownloaderConfig.MyOptions.Keys.Concat(new string[] { _key }).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private void ChangeSpecInfo(StreamSpec streamSpec, List<Mediainfo> mediainfos, ref bool useAACFilter)
|
||||
{
|
||||
if (!DownloaderConfig.BinaryMerge && mediainfos.Any(m => m.DolbyVison == true))
|
||||
if (!DownloaderConfig.MyOptions.BinaryMerge && mediainfos.Any(m => m.DolbyVison == true))
|
||||
{
|
||||
DownloaderConfig.BinaryMerge = true;
|
||||
DownloaderConfig.MyOptions.BinaryMerge = true;
|
||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge2}[/]");
|
||||
}
|
||||
|
||||
if (!DownloaderConfig.MuxAfterDone && mediainfos.Any(m => m.DolbyVison == true))
|
||||
if (!DownloaderConfig.MyOptions.MuxAfterDone && mediainfos.Any(m => m.DolbyVison == true))
|
||||
{
|
||||
DownloaderConfig.MuxAfterDone = false;
|
||||
DownloaderConfig.MyOptions.MuxAfterDone = false;
|
||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge5}[/]");
|
||||
}
|
||||
|
||||
|
@ -121,16 +121,16 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
if (segments.Count() == 1) speedContainer.SingleSegment = true;
|
||||
|
||||
var type = streamSpec.MediaType ?? Common.Enum.MediaType.VIDEO;
|
||||
var dirName = $"{DownloaderConfig.SaveName ?? NowDateTime.ToString("yyyy-MM-dd_HH-mm-ss")}_{streamSpec.GroupId}_{streamSpec.Codecs}_{streamSpec.Bandwidth}_{streamSpec.Language}";
|
||||
var dirName = $"{DownloaderConfig.MyOptions.SaveName ?? NowDateTime.ToString("yyyy-MM-dd_HH-mm-ss")}_{streamSpec.GroupId}_{streamSpec.Codecs}_{streamSpec.Bandwidth}_{streamSpec.Language}";
|
||||
//去除非法字符
|
||||
dirName = ConvertUtil.GetValidFileName(dirName, filterSlash: true);
|
||||
var tmpDir = Path.Combine(DownloaderConfig.TmpDir ?? Environment.CurrentDirectory, dirName);
|
||||
var saveDir = DownloaderConfig.SaveDir ?? Environment.CurrentDirectory;
|
||||
var saveName = DownloaderConfig.SaveName != null ? $"{DownloaderConfig.SaveName}.{type}.{streamSpec.Language}".TrimEnd('.') : dirName;
|
||||
var tmpDir = Path.Combine(DownloaderConfig.MyOptions.TmpDir ?? Environment.CurrentDirectory, dirName);
|
||||
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
||||
var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{type}.{streamSpec.Language}".TrimEnd('.') : dirName;
|
||||
var headers = DownloaderConfig.Headers;
|
||||
|
||||
//mp4decrypt
|
||||
var mp4decrypt = DownloaderConfig.DecryptionBinaryPath!;
|
||||
var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
|
||||
var mp4InitFile = "";
|
||||
var currentKID = "";
|
||||
var readInfo = false; //是否读取过
|
||||
|
@ -154,9 +154,9 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
Logger.InfoMarkUp(ResString.startDownloading + streamSpec.ToShortString());
|
||||
|
||||
//对于CENC,全部自动开启二进制合并
|
||||
if (!DownloaderConfig.BinaryMerge && totalCount >= 1 && streamSpec.Playlist!.MediaParts.First().MediaSegments.First().EncryptInfo.Method == Common.Enum.EncryptMethod.CENC)
|
||||
if (!DownloaderConfig.MyOptions.BinaryMerge && totalCount >= 1 && streamSpec.Playlist!.MediaParts.First().MediaSegments.First().EncryptInfo.Method == Common.Enum.EncryptMethod.CENC)
|
||||
{
|
||||
DownloaderConfig.BinaryMerge = true;
|
||||
DownloaderConfig.MyOptions.BinaryMerge = true;
|
||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge4}[/]");
|
||||
}
|
||||
|
||||
|
@ -164,9 +164,9 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
if (streamSpec.Playlist?.MediaInit != null)
|
||||
{
|
||||
//对于fMP4,自动开启二进制合并
|
||||
if (!DownloaderConfig.BinaryMerge && streamSpec.MediaType != MediaType.SUBTITLES)
|
||||
if (!DownloaderConfig.MyOptions.BinaryMerge && streamSpec.MediaType != MediaType.SUBTITLES)
|
||||
{
|
||||
DownloaderConfig.BinaryMerge = true;
|
||||
DownloaderConfig.MyOptions.BinaryMerge = true;
|
||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge}[/]");
|
||||
}
|
||||
|
||||
|
@ -188,11 +188,11 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
//从文件读取KEY
|
||||
await SearchKeyAsync(currentKID);
|
||||
//实时解密
|
||||
if (DownloaderConfig.MP4RealTimeDecryption && streamSpec.Playlist.MediaInit.EncryptInfo.Method == Common.Enum.EncryptMethod.CENC)
|
||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && streamSpec.Playlist.MediaInit.EncryptInfo.Method == Common.Enum.EncryptMethod.CENC)
|
||||
{
|
||||
var enc = result.ActualFilePath;
|
||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.UseShakaPackager, mp4decrypt, DownloaderConfig.Keys, enc, dec, currentKID);
|
||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
||||
if (dResult)
|
||||
{
|
||||
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
||||
|
@ -202,7 +202,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
if (!readInfo)
|
||||
{
|
||||
Logger.WarnMarkUp(ResString.readingInfo);
|
||||
var mediainfos = await MediainfoUtil.ReadInfoAsync(DownloaderConfig.FFmpegBinaryPath!, result.ActualFilePath);
|
||||
var mediainfos = await MediainfoUtil.ReadInfoAsync(DownloaderConfig.MyOptions.FFmpegBinaryPath!, result.ActualFilePath);
|
||||
mediainfos.ForEach(info => Logger.InfoMarkUp(info.ToStringMarkUp()));
|
||||
ChangeSpecInfo(streamSpec, mediainfos, ref useAACFilter);
|
||||
readInfo = true;
|
||||
|
@ -225,7 +225,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
FileDic[seg] = result;
|
||||
task.Increment(1);
|
||||
//实时解密
|
||||
if (DownloaderConfig.MP4RealTimeDecryption && seg.EncryptInfo.Method == Common.Enum.EncryptMethod.CENC && result != null)
|
||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && seg.EncryptInfo.Method == Common.Enum.EncryptMethod.CENC && result != null)
|
||||
{
|
||||
//读取init信息
|
||||
if (string.IsNullOrEmpty(currentKID))
|
||||
|
@ -236,7 +236,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
await SearchKeyAsync(currentKID);
|
||||
var enc = result.ActualFilePath;
|
||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.UseShakaPackager, mp4decrypt, DownloaderConfig.Keys, enc, dec, currentKID, mp4InitFile);
|
||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
|
||||
if (dResult)
|
||||
{
|
||||
File.Delete(enc);
|
||||
|
@ -245,7 +245,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
//ffmpeg读取信息
|
||||
Logger.WarnMarkUp(ResString.readingInfo);
|
||||
var mediainfos = await MediainfoUtil.ReadInfoAsync(DownloaderConfig.FFmpegBinaryPath!, result!.ActualFilePath);
|
||||
var mediainfos = await MediainfoUtil.ReadInfoAsync(DownloaderConfig.MyOptions.FFmpegBinaryPath!, result!.ActualFilePath);
|
||||
mediainfos.ForEach(info => Logger.InfoMarkUp(info.ToStringMarkUp()));
|
||||
ChangeSpecInfo(streamSpec, mediainfos, ref useAACFilter);
|
||||
readInfo = true;
|
||||
|
@ -254,7 +254,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
//开始下载
|
||||
var options = new ParallelOptions()
|
||||
{
|
||||
MaxDegreeOfParallelism = DownloaderConfig.ThreadCount
|
||||
MaxDegreeOfParallelism = DownloaderConfig.MyOptions.ThreadCount
|
||||
};
|
||||
await Parallel.ForEachAsync(segments, options, async (seg, _) =>
|
||||
{
|
||||
|
@ -264,11 +264,11 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
FileDic[seg] = result;
|
||||
task.Increment(1);
|
||||
//实时解密
|
||||
if (DownloaderConfig.MP4RealTimeDecryption && seg.EncryptInfo.Method == Common.Enum.EncryptMethod.CENC && result != null)
|
||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && seg.EncryptInfo.Method == Common.Enum.EncryptMethod.CENC && result != null)
|
||||
{
|
||||
var enc = result.ActualFilePath;
|
||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.UseShakaPackager, mp4decrypt, DownloaderConfig.Keys, enc, dec, currentKID, mp4InitFile);
|
||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
|
||||
if (dResult)
|
||||
{
|
||||
File.Delete(enc);
|
||||
|
@ -291,18 +291,18 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
Logger.WarnMarkUp($"{Path.GetFileName(output)} => {Path.GetFileName(output = Path.ChangeExtension(output, $"copy" + Path.GetExtension(output)))}");
|
||||
}
|
||||
|
||||
if (DownloaderConfig.MP4RealTimeDecryption && mp4InitFile != "")
|
||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && mp4InitFile != "")
|
||||
{
|
||||
File.Delete(mp4InitFile);
|
||||
//shaka实时解密不需要init文件用于合并
|
||||
if (DownloaderConfig.UseShakaPackager)
|
||||
if (DownloaderConfig.MyOptions.UseShakaPackager)
|
||||
{
|
||||
FileDic!.Remove(streamSpec.Playlist!.MediaInit, out _);
|
||||
}
|
||||
}
|
||||
|
||||
//校验分片数量
|
||||
if (DownloaderConfig.CheckSegmentsCount && FileDic.Values.Any(s => s == null))
|
||||
if (DownloaderConfig.MyOptions.CheckSegmentsCount && FileDic.Values.Any(s => s == null))
|
||||
{
|
||||
Logger.WarnMarkUp(ResString.segmentCountCheckNotPass, totalCount, FileDic.Values.Where(s => s != null).Count());
|
||||
return false;
|
||||
|
@ -322,7 +322,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
|
||||
//自动修复VTT raw字幕
|
||||
if (DownloaderConfig.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("vtt"))
|
||||
{
|
||||
Logger.WarnMarkUp(ResString.fixingVTT);
|
||||
|
@ -357,10 +357,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
||||
var subContentFixed = finalVtt.ToStringWithHeader();
|
||||
//转换字幕格式
|
||||
if (DownloaderConfig.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
{
|
||||
path = Path.ChangeExtension(path, ".srt");
|
||||
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.SubtitleFormat);
|
||||
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
output = Path.ChangeExtension(output, ".srt");
|
||||
}
|
||||
await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false));
|
||||
|
@ -372,7 +372,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
|
||||
//自动修复VTT mp4字幕
|
||||
if (DownloaderConfig.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||
&& streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s"))
|
||||
{
|
||||
var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault();
|
||||
|
@ -392,10 +392,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
||||
var subContentFixed = finalVtt.ToStringWithHeader();
|
||||
//转换字幕格式
|
||||
if (DownloaderConfig.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
{
|
||||
path = Path.ChangeExtension(path, ".srt");
|
||||
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.SubtitleFormat);
|
||||
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
output = Path.ChangeExtension(output, ".srt");
|
||||
}
|
||||
await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false));
|
||||
|
@ -410,7 +410,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
|
||||
//自动修复TTML raw字幕
|
||||
if (DownloaderConfig.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("ttml"))
|
||||
{
|
||||
Logger.WarnMarkUp(ResString.fixingTTML);
|
||||
|
@ -425,10 +425,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
||||
var subContentFixed = finalVtt.ToStringWithHeader();
|
||||
//转换字幕格式
|
||||
if (DownloaderConfig.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
{
|
||||
path = Path.ChangeExtension(path, ".srt");
|
||||
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.SubtitleFormat);
|
||||
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
output = Path.ChangeExtension(output, ".srt");
|
||||
}
|
||||
await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false));
|
||||
|
@ -442,7 +442,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
|
||||
//自动修复TTML mp4字幕
|
||||
if (DownloaderConfig.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")
|
||||
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp"))
|
||||
{
|
||||
|
@ -462,10 +462,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
||||
var subContentFixed = finalVtt.ToStringWithHeader();
|
||||
//转换字幕格式
|
||||
if (DownloaderConfig.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
{
|
||||
path = Path.ChangeExtension(path, ".srt");
|
||||
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.SubtitleFormat);
|
||||
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
output = Path.ChangeExtension(output, ".srt");
|
||||
}
|
||||
await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false));
|
||||
|
@ -480,10 +480,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
|
||||
bool mergeSuccess = false;
|
||||
//合并
|
||||
if (!DownloaderConfig.SkipMerge)
|
||||
if (!DownloaderConfig.MyOptions.SkipMerge)
|
||||
{
|
||||
//字幕也使用二进制合并
|
||||
if (DownloaderConfig.BinaryMerge || streamSpec.MediaType == MediaType.SUBTITLES)
|
||||
if (DownloaderConfig.MyOptions.BinaryMerge || streamSpec.MediaType == MediaType.SUBTITLES)
|
||||
{
|
||||
Logger.InfoMarkUp(ResString.binaryMerge);
|
||||
var files = FileDic.Values.Select(v => v!.ActualFilePath).OrderBy(s => s).ToArray();
|
||||
|
@ -502,13 +502,13 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
{
|
||||
Logger.WarnMarkUp($"{Path.GetFileName(ffOut)} => {Path.GetFileName(ffOut = Path.ChangeExtension(ffOut, $"copy" + Path.GetExtension(ffOut)))}");
|
||||
}
|
||||
mergeSuccess = MergeUtil.MergeByFFmpeg(DownloaderConfig.FFmpegBinaryPath!, files, Path.ChangeExtension(ffOut, null), ext, useAACFilter);
|
||||
mergeSuccess = MergeUtil.MergeByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, files, Path.ChangeExtension(ffOut, null), ext, useAACFilter);
|
||||
if (mergeSuccess) output = ffOut;
|
||||
}
|
||||
}
|
||||
|
||||
//删除临时文件夹
|
||||
if (!DownloaderConfig.SkipMerge && DownloaderConfig.DelAfterDone && mergeSuccess)
|
||||
if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && mergeSuccess)
|
||||
{
|
||||
var files = FileDic.Values.Select(v => v!.ActualFilePath);
|
||||
foreach (var file in files)
|
||||
|
@ -527,12 +527,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
|
||||
//调用mp4decrypt解密
|
||||
if (mergeSuccess && File.Exists(output) && !string.IsNullOrEmpty(currentKID) && !DownloaderConfig.MP4RealTimeDecryption && DownloaderConfig.Keys != null && DownloaderConfig.Keys.Length > 0)
|
||||
if (mergeSuccess && File.Exists(output) && !string.IsNullOrEmpty(currentKID) && !DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0)
|
||||
{
|
||||
var enc = output;
|
||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||
Logger.InfoMarkUp($"[grey]Decrypting...[/]");
|
||||
var result = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.UseShakaPackager, mp4decrypt, DownloaderConfig.Keys, enc, dec, currentKID);
|
||||
var result = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
||||
if (result)
|
||||
{
|
||||
File.Delete(enc);
|
||||
|
@ -585,20 +585,26 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
var success = Results.Values.All(v => v == true);
|
||||
|
||||
//混流
|
||||
if (success && DownloaderConfig.MuxAfterDone && OutputFiles.Count > 0)
|
||||
if (success && DownloaderConfig.MyOptions.MuxAfterDone && OutputFiles.Count > 0)
|
||||
{
|
||||
var saveDir = DownloaderConfig.SaveDir ?? Environment.CurrentDirectory;
|
||||
var outName = $"{DownloaderConfig.SaveName ?? NowDateTime.ToString("yyyy-MM-dd_HH-mm-ss")}";
|
||||
if (DownloaderConfig.MyOptions.MuxImports != null)
|
||||
{
|
||||
OutputFiles.AddRange(DownloaderConfig.MyOptions.MuxImports);
|
||||
}
|
||||
OutputFiles.ForEach(f => Logger.WarnMarkUp($"[grey]{Path.GetFileName(f.FilePath).EscapeMarkup()}[/]"));
|
||||
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
||||
var outName = $"{DownloaderConfig.MyOptions.SaveName ?? NowDateTime.ToString("yyyy-MM-dd_HH-mm-ss")}";
|
||||
var outPath = Path.Combine(saveDir, outName);
|
||||
Logger.WarnMarkUp($"Muxing to [grey]{outName.EscapeMarkup()}.{(DownloaderConfig.MuxToMp4 ? "mp4" : "mkv")}[/]");
|
||||
Logger.WarnMarkUp($"Muxing to [grey]{outName.EscapeMarkup()}.{(DownloaderConfig.MyOptions.MuxToMp4 ? "mp4" : "mkv")}[/]");
|
||||
var result = false;
|
||||
if (DownloaderConfig.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
|
||||
else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MuxToMp4);
|
||||
if (DownloaderConfig.MyOptions.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MyOptions.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
|
||||
else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MyOptions.MuxToMp4);
|
||||
//完成后删除各轨道文件
|
||||
if (result)
|
||||
if (result && !DownloaderConfig.MyOptions.MuxKeepFiles)
|
||||
{
|
||||
Logger.WarnMarkUp("[grey]Cleaning files...[/]");
|
||||
OutputFiles.ForEach(f => File.Delete(f.FilePath));
|
||||
var tmpDir = DownloaderConfig.TmpDir ?? Environment.CurrentDirectory;
|
||||
var tmpDir = DownloaderConfig.MyOptions.TmpDir ?? Environment.CurrentDirectory;
|
||||
SafeDeleteDir(tmpDir);
|
||||
}
|
||||
else Logger.ErrorMarkUp($"Mux failed");
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace N_m3u8DL_RE.Downloader
|
|||
public async Task<DownloadResult?> DownloadSegmentAsync(MediaSegment segment, string savePath, SpeedContainer speedContainer, Dictionary<string, string>? headers = null)
|
||||
{
|
||||
var url = segment.Url;
|
||||
var dResult = await DownClipAsync(url, savePath, speedContainer, segment.StartRange, segment.StopRange, headers, DownloaderConfig.DownloadRetryCount);
|
||||
var dResult = await DownClipAsync(url, savePath, speedContainer, segment.StartRange, segment.StopRange, headers, DownloaderConfig.MyOptions.DownloadRetryCount);
|
||||
if (dResult != null && dResult.Success && segment.EncryptInfo != null)
|
||||
{
|
||||
if (segment.EncryptInfo.Method == EncryptMethod.AES_128)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_RE.Entity
|
||||
{
|
||||
internal class MuxOptions
|
||||
{
|
||||
public bool UseMkvmerge { get; set; } = false;
|
||||
public bool MuxToMp4 { get; set; } = false;
|
||||
public bool KeepFiles { get; set; } = false;
|
||||
public string? BinPath { get; set; }
|
||||
}
|
||||
}
|
|
@ -50,14 +50,10 @@ namespace N_m3u8DL_RE
|
|||
try
|
||||
{
|
||||
//检查互斥的选项
|
||||
if (option.UseMkvmerge && option.MuxToMp4)
|
||||
{
|
||||
throw new ArgumentException("Can't use mkvmerge to make mp4!");
|
||||
}
|
||||
|
||||
if (option.MuxToMp4 && !option.MuxAfterDone)
|
||||
if (!option.MuxAfterDone && option.MuxImports != null && option.MuxImports.Count > 0)
|
||||
{
|
||||
throw new ArgumentException("Can't enable MuxToMp4 when MuxAfterDone is false!");
|
||||
throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!");
|
||||
}
|
||||
|
||||
//预先检查ffmpeg
|
||||
|
@ -111,15 +107,10 @@ namespace N_m3u8DL_RE
|
|||
{
|
||||
AppendUrlParams = option.AppendUrlParams,
|
||||
UrlProcessorArgs = option.UrlProcessorArgs,
|
||||
BaseUrl = option.BaseUrl!
|
||||
BaseUrl = option.BaseUrl!,
|
||||
Headers = option.Headers
|
||||
};
|
||||
|
||||
//设置Headers
|
||||
foreach (var item in ConvertUtil.SplitHeaderArrayToDic(option.Headers))
|
||||
{
|
||||
parserConfig.Headers[item.Key] = item.Value;
|
||||
}
|
||||
|
||||
//demo1
|
||||
parserConfig.ContentProcessors.Insert(0, new DemoProcessor());
|
||||
//demo2
|
||||
|
@ -254,9 +245,10 @@ namespace N_m3u8DL_RE
|
|||
#endif
|
||||
|
||||
//下载配置
|
||||
var downloadConfig = new DownloaderConfig(option)
|
||||
var downloadConfig = new DownloaderConfig()
|
||||
{
|
||||
Headers = parserConfig.Headers,
|
||||
MyOptions = option,
|
||||
Headers = parserConfig.Headers, //使用命令行解析得到的Headers
|
||||
};
|
||||
//开始下载
|
||||
var sdm = new SimpleDownloadManager(downloadConfig);
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace N_m3u8DL_RE.Util
|
|||
var index = header.IndexOf(':');
|
||||
if (index != -1)
|
||||
{
|
||||
dic[header[..index].Trim()] = header[(index + 1)..].Trim();
|
||||
dic[header[..index].Trim().ToLower()] = header[(index + 1)..].Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue