增加复杂混流选项配置;增加支持引入外部文件混流

This commit is contained in:
nilaoda 2022-08-25 21:44:13 +08:00
parent df63b42356
commit 78473cebb8
12 changed files with 360 additions and 212 deletions

View File

@ -27,6 +27,7 @@ namespace N_m3u8DL_RE.Common.Resource
public static string cmd_mkvmergeBinaryPath { get => GetText("cmd_mkvmergeBinaryPath"); } public static string cmd_mkvmergeBinaryPath { get => GetText("cmd_mkvmergeBinaryPath"); }
public static string cmd_baseUrl { get => GetText("cmd_baseUrl"); } public static string cmd_baseUrl { get => GetText("cmd_baseUrl"); }
public static string cmd_header { get => GetText("cmd_header"); } 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_Input { get => GetText("cmd_Input"); }
public static string cmd_keys { get => GetText("cmd_keys"); } public static string cmd_keys { get => GetText("cmd_keys"); }
public static string cmd_keyText { get => GetText("cmd_keyText"); } public static string cmd_keyText { get => GetText("cmd_keyText"); }

View File

@ -246,9 +246,72 @@ namespace N_m3u8DL_RE.Common.Resource
), ),
["cmd_muxAfterDone"] = new TextContainer ["cmd_muxAfterDone"] = new TextContainer
( (
zhCN: "所有工作完成时尝试使用ffmpeg混流分离的音视频(默认容器: mkv)", zhCN: "所有工作完成时尝试混流分离的音视频. 你能够以:分隔形式指定如下参数:\r\n\r\n" +
zhTW: "所有工作完成時嘗試使用ffmpeg混流分離的影音(默認容器: mkv)", "* format=FORMAT: 指定混流容器 mkv, mp4\r\n" +
enUS: "When all works is done, try to use ffmpeg to mux the separated streams.(Default container: mkv)" "* 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 ["cmd_muxToMp4"] = new TextContainer
( (

View File

@ -11,10 +11,7 @@ namespace N_m3u8DL_RE.Parser.Config
public string BaseUrl { get; set; } public string BaseUrl { get; set; }
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>() 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> /// <summary>
/// 内容前置处理器. 调用顺序与列表顺序相同 /// 内容前置处理器. 调用顺序与列表顺序相同

View File

@ -1,8 +1,11 @@
using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Common.Resource; using N_m3u8DL_RE.Common.Resource;
using N_m3u8DL_RE.Entity;
using N_m3u8DL_RE.Enum; using N_m3u8DL_RE.Enum;
using N_m3u8DL_RE.Util;
using System.CommandLine; using System.CommandLine;
using System.CommandLine.Binding; using System.CommandLine.Binding;
using System.CommandLine.Parsing;
using System.Globalization; using System.Globalization;
using System.Linq; 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?> 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?> 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?> 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> 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<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<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> 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> 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> 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> 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?> 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?> 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<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> class MyOptionBinder : BinderBase<MyOption>
{ {
protected override MyOption GetBoundValue(BindingContext bindingContext) protected override MyOption GetBoundValue(BindingContext bindingContext)
@ -51,7 +158,7 @@ namespace N_m3u8DL_RE.CommandLine
var option = new MyOption var option = new MyOption
{ {
Input = bindingContext.ParseResult.GetValueForArgument(Input), Input = bindingContext.ParseResult.GetValueForArgument(Input),
Headers = bindingContext.ParseResult.GetValueForOption(Headers), Headers = bindingContext.ParseResult.GetValueForOption(Headers)!,
LogLevel = bindingContext.ParseResult.GetValueForOption(LogLevel), LogLevel = bindingContext.ParseResult.GetValueForOption(LogLevel),
AutoSelect = bindingContext.ParseResult.GetValueForOption(AutoSelect), AutoSelect = bindingContext.ParseResult.GetValueForOption(AutoSelect),
SkipMerge = bindingContext.ParseResult.GetValueForOption(SkipMerge), SkipMerge = bindingContext.ParseResult.GetValueForOption(SkipMerge),
@ -78,10 +185,8 @@ namespace N_m3u8DL_RE.CommandLine
FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath), FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath),
KeyTextFile = bindingContext.ParseResult.GetValueForOption(KeyTextFile), KeyTextFile = bindingContext.ParseResult.GetValueForOption(KeyTextFile),
DownloadRetryCount = bindingContext.ParseResult.GetValueForOption(DownloadRetryCount), DownloadRetryCount = bindingContext.ParseResult.GetValueForOption(DownloadRetryCount),
MuxAfterDone = bindingContext.ParseResult.GetValueForOption(MuxAfterDone),
UseMkvmerge = bindingContext.ParseResult.GetValueForOption(UseMkvmerge),
BaseUrl = bindingContext.ParseResult.GetValueForOption(BaseUrl), 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); 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; return option;
} }
} }
@ -100,12 +218,13 @@ namespace N_m3u8DL_RE.CommandLine
public static async Task<int> InvokeArgs(string[] args, Func<MyOption, Task> action) 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, Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
BinaryMerge, DelAfterDone, WriteMetaJson, MuxAfterDone, MuxToMp4, UseMkvmerge, AppendUrlParams, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix, BinaryMerge, DelAfterDone, WriteMetaJson, AppendUrlParams, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
FFmpegBinaryPath, MkvmergeBinaryPath, FFmpegBinaryPath,
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
MuxAfterDone, MuxImports
}; };
rootCommand.TreatUnmatchedTokensAsErrors = true; rootCommand.TreatUnmatchedTokensAsErrors = true;
rootCommand.SetHandler(async (myOption) => await action(myOption), new MyOptionBinder()); rootCommand.SetHandler(async (myOption) => await action(myOption), new MyOptionBinder());

View File

@ -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!");
}
}
}
}

View File

@ -1,4 +1,5 @@
using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Entity;
using N_m3u8DL_RE.Enum; using N_m3u8DL_RE.Enum;
namespace N_m3u8DL_RE.CommandLine namespace N_m3u8DL_RE.CommandLine
@ -12,7 +13,7 @@ namespace N_m3u8DL_RE.CommandLine
/// <summary> /// <summary>
/// See: <see cref="CommandInvoker.Headers"/>. /// See: <see cref="CommandInvoker.Headers"/>.
/// </summary> /// </summary>
public string[]? Headers { get; set; } public Dictionary<string,string> Headers { get; set; } = new Dictionary<string,string>();
/// <summary> /// <summary>
/// See: <see cref="CommandInvoker.Keys"/>. /// See: <see cref="CommandInvoker.Keys"/>.
/// </summary> /// </summary>
@ -137,5 +138,10 @@ namespace N_m3u8DL_RE.CommandLine
/// See: <see cref="CommandInvoker.MkvmergeBinaryPath"/>. /// See: <see cref="CommandInvoker.MkvmergeBinaryPath"/>.
/// </summary> /// </summary>
public string? MkvmergeBinaryPath { get; set; } public string? MkvmergeBinaryPath { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.MuxImports"/>.
/// </summary>
public List<OutputFile>? MuxImports { get; set; }
public bool MuxKeepFiles { get; set; }
} }
} }

View File

@ -11,132 +11,19 @@ namespace N_m3u8DL_RE.Config
{ {
internal class DownloaderConfig 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>
/// 文件名模板 /// 文件名模板
/// </summary> /// </summary>
public string? SavePattern { get; set; } public string? SavePattern { get; set; }
/// <summary> /// <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> /// </summary>
public bool CheckContentLength { get; set; } = true; public bool CheckContentLength { get; set; } = true;
/// <summary> /// <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> /// </summary>
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>() 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; }
} }
} }

View File

@ -69,27 +69,27 @@ namespace N_m3u8DL_RE.DownloadManager
//从文件读取KEY //从文件读取KEY
private async Task SearchKeyAsync(string? currentKID) 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 (_key != null)
{ {
if (DownloaderConfig.Keys == null) if (DownloaderConfig.MyOptions.Keys == null)
DownloaderConfig.Keys = new string[] { _key }; DownloaderConfig.MyOptions.Keys = new string[] { _key };
else 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) 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}[/]"); 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}[/]"); Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge5}[/]");
} }
@ -121,16 +121,16 @@ namespace N_m3u8DL_RE.DownloadManager
if (segments.Count() == 1) speedContainer.SingleSegment = true; if (segments.Count() == 1) speedContainer.SingleSegment = true;
var type = streamSpec.MediaType ?? Common.Enum.MediaType.VIDEO; 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); dirName = ConvertUtil.GetValidFileName(dirName, filterSlash: true);
var tmpDir = Path.Combine(DownloaderConfig.TmpDir ?? Environment.CurrentDirectory, dirName); var tmpDir = Path.Combine(DownloaderConfig.MyOptions.TmpDir ?? Environment.CurrentDirectory, dirName);
var saveDir = DownloaderConfig.SaveDir ?? Environment.CurrentDirectory; var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
var saveName = DownloaderConfig.SaveName != null ? $"{DownloaderConfig.SaveName}.{type}.{streamSpec.Language}".TrimEnd('.') : dirName; var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{type}.{streamSpec.Language}".TrimEnd('.') : dirName;
var headers = DownloaderConfig.Headers; var headers = DownloaderConfig.Headers;
//mp4decrypt //mp4decrypt
var mp4decrypt = DownloaderConfig.DecryptionBinaryPath!; var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
var mp4InitFile = ""; var mp4InitFile = "";
var currentKID = ""; var currentKID = "";
var readInfo = false; //是否读取过 var readInfo = false; //是否读取过
@ -154,9 +154,9 @@ namespace N_m3u8DL_RE.DownloadManager
Logger.InfoMarkUp(ResString.startDownloading + streamSpec.ToShortString()); Logger.InfoMarkUp(ResString.startDownloading + streamSpec.ToShortString());
//对于CENC全部自动开启二进制合并 //对于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}[/]"); Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge4}[/]");
} }
@ -164,9 +164,9 @@ namespace N_m3u8DL_RE.DownloadManager
if (streamSpec.Playlist?.MediaInit != null) if (streamSpec.Playlist?.MediaInit != null)
{ {
//对于fMP4自动开启二进制合并 //对于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}[/]"); Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge}[/]");
} }
@ -188,11 +188,11 @@ namespace N_m3u8DL_RE.DownloadManager
//从文件读取KEY //从文件读取KEY
await SearchKeyAsync(currentKID); 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 enc = result.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); 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) if (dResult)
{ {
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec; FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
@ -202,7 +202,7 @@ namespace N_m3u8DL_RE.DownloadManager
if (!readInfo) if (!readInfo)
{ {
Logger.WarnMarkUp(ResString.readingInfo); 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())); mediainfos.ForEach(info => Logger.InfoMarkUp(info.ToStringMarkUp()));
ChangeSpecInfo(streamSpec, mediainfos, ref useAACFilter); ChangeSpecInfo(streamSpec, mediainfos, ref useAACFilter);
readInfo = true; readInfo = true;
@ -225,7 +225,7 @@ namespace N_m3u8DL_RE.DownloadManager
FileDic[seg] = result; FileDic[seg] = result;
task.Increment(1); 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信息 //读取init信息
if (string.IsNullOrEmpty(currentKID)) if (string.IsNullOrEmpty(currentKID))
@ -236,7 +236,7 @@ namespace N_m3u8DL_RE.DownloadManager
await SearchKeyAsync(currentKID); await SearchKeyAsync(currentKID);
var enc = result.ActualFilePath; var enc = result.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); 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) if (dResult)
{ {
File.Delete(enc); File.Delete(enc);
@ -245,7 +245,7 @@ namespace N_m3u8DL_RE.DownloadManager
} }
//ffmpeg读取信息 //ffmpeg读取信息
Logger.WarnMarkUp(ResString.readingInfo); 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())); mediainfos.ForEach(info => Logger.InfoMarkUp(info.ToStringMarkUp()));
ChangeSpecInfo(streamSpec, mediainfos, ref useAACFilter); ChangeSpecInfo(streamSpec, mediainfos, ref useAACFilter);
readInfo = true; readInfo = true;
@ -254,7 +254,7 @@ namespace N_m3u8DL_RE.DownloadManager
//开始下载 //开始下载
var options = new ParallelOptions() var options = new ParallelOptions()
{ {
MaxDegreeOfParallelism = DownloaderConfig.ThreadCount MaxDegreeOfParallelism = DownloaderConfig.MyOptions.ThreadCount
}; };
await Parallel.ForEachAsync(segments, options, async (seg, _) => await Parallel.ForEachAsync(segments, options, async (seg, _) =>
{ {
@ -264,11 +264,11 @@ namespace N_m3u8DL_RE.DownloadManager
FileDic[seg] = result; FileDic[seg] = result;
task.Increment(1); 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 enc = result.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); 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) if (dResult)
{ {
File.Delete(enc); 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)))}"); 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); File.Delete(mp4InitFile);
//shaka实时解密不需要init文件用于合并 //shaka实时解密不需要init文件用于合并
if (DownloaderConfig.UseShakaPackager) if (DownloaderConfig.MyOptions.UseShakaPackager)
{ {
FileDic!.Remove(streamSpec.Playlist!.MediaInit, out _); 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()); Logger.WarnMarkUp(ResString.segmentCountCheckNotPass, totalCount, FileDic.Values.Where(s => s != null).Count());
return false; return false;
@ -322,7 +322,7 @@ namespace N_m3u8DL_RE.DownloadManager
} }
//自动修复VTT raw字幕 //自动修复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")) && streamSpec.Extension != null && streamSpec.Extension.Contains("vtt"))
{ {
Logger.WarnMarkUp(ResString.fixingVTT); Logger.WarnMarkUp(ResString.fixingVTT);
@ -357,10 +357,10 @@ namespace N_m3u8DL_RE.DownloadManager
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt"); var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
var subContentFixed = finalVtt.ToStringWithHeader(); var subContentFixed = finalVtt.ToStringWithHeader();
//转换字幕格式 //转换字幕格式
if (DownloaderConfig.SubtitleFormat != Enum.SubtitleFormat.VTT) if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
{ {
path = Path.ChangeExtension(path, ".srt"); path = Path.ChangeExtension(path, ".srt");
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.SubtitleFormat); subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
output = Path.ChangeExtension(output, ".srt"); output = Path.ChangeExtension(output, ".srt");
} }
await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false)); await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false));
@ -372,7 +372,7 @@ namespace N_m3u8DL_RE.DownloadManager
} }
//自动修复VTT mp4字幕 //自动修复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")) && streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s"))
{ {
var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault(); 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 path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
var subContentFixed = finalVtt.ToStringWithHeader(); var subContentFixed = finalVtt.ToStringWithHeader();
//转换字幕格式 //转换字幕格式
if (DownloaderConfig.SubtitleFormat != Enum.SubtitleFormat.VTT) if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
{ {
path = Path.ChangeExtension(path, ".srt"); path = Path.ChangeExtension(path, ".srt");
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.SubtitleFormat); subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
output = Path.ChangeExtension(output, ".srt"); output = Path.ChangeExtension(output, ".srt");
} }
await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false)); await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false));
@ -410,7 +410,7 @@ namespace N_m3u8DL_RE.DownloadManager
} }
//自动修复TTML raw字幕 //自动修复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")) && streamSpec.Extension != null && streamSpec.Extension.Contains("ttml"))
{ {
Logger.WarnMarkUp(ResString.fixingTTML); Logger.WarnMarkUp(ResString.fixingTTML);
@ -425,10 +425,10 @@ namespace N_m3u8DL_RE.DownloadManager
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt"); var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
var subContentFixed = finalVtt.ToStringWithHeader(); var subContentFixed = finalVtt.ToStringWithHeader();
//转换字幕格式 //转换字幕格式
if (DownloaderConfig.SubtitleFormat != Enum.SubtitleFormat.VTT) if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
{ {
path = Path.ChangeExtension(path, ".srt"); path = Path.ChangeExtension(path, ".srt");
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.SubtitleFormat); subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
output = Path.ChangeExtension(output, ".srt"); output = Path.ChangeExtension(output, ".srt");
} }
await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false)); await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false));
@ -442,7 +442,7 @@ namespace N_m3u8DL_RE.DownloadManager
} }
//自动修复TTML mp4字幕 //自动修复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.Extension != null && streamSpec.Extension.Contains("m4s")
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp")) && 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 path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
var subContentFixed = finalVtt.ToStringWithHeader(); var subContentFixed = finalVtt.ToStringWithHeader();
//转换字幕格式 //转换字幕格式
if (DownloaderConfig.SubtitleFormat != Enum.SubtitleFormat.VTT) if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
{ {
path = Path.ChangeExtension(path, ".srt"); path = Path.ChangeExtension(path, ".srt");
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.SubtitleFormat); subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
output = Path.ChangeExtension(output, ".srt"); output = Path.ChangeExtension(output, ".srt");
} }
await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false)); await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false));
@ -480,10 +480,10 @@ namespace N_m3u8DL_RE.DownloadManager
bool mergeSuccess = false; 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); Logger.InfoMarkUp(ResString.binaryMerge);
var files = FileDic.Values.Select(v => v!.ActualFilePath).OrderBy(s => s).ToArray(); 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)))}"); 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 (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); var files = FileDic.Values.Select(v => v!.ActualFilePath);
foreach (var file in files) foreach (var file in files)
@ -527,12 +527,12 @@ namespace N_m3u8DL_RE.DownloadManager
} }
//调用mp4decrypt解密 //调用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 enc = output;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
Logger.InfoMarkUp($"[grey]Decrypting...[/]"); 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) if (result)
{ {
File.Delete(enc); File.Delete(enc);
@ -585,20 +585,26 @@ namespace N_m3u8DL_RE.DownloadManager
var success = Results.Values.All(v => v == true); 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; if (DownloaderConfig.MyOptions.MuxImports != null)
var outName = $"{DownloaderConfig.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")}[/]");
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 (result)
{ {
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.MyOptions.MuxToMp4 ? "mp4" : "mkv")}[/]");
var result = false;
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 && !DownloaderConfig.MyOptions.MuxKeepFiles)
{
Logger.WarnMarkUp("[grey]Cleaning files...[/]");
OutputFiles.ForEach(f => File.Delete(f.FilePath)); OutputFiles.ForEach(f => File.Delete(f.FilePath));
var tmpDir = DownloaderConfig.TmpDir ?? Environment.CurrentDirectory; var tmpDir = DownloaderConfig.MyOptions.TmpDir ?? Environment.CurrentDirectory;
SafeDeleteDir(tmpDir); SafeDeleteDir(tmpDir);
} }
else Logger.ErrorMarkUp($"Mux failed"); else Logger.ErrorMarkUp($"Mux failed");

View File

@ -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) public async Task<DownloadResult?> DownloadSegmentAsync(MediaSegment segment, string savePath, SpeedContainer speedContainer, Dictionary<string, string>? headers = null)
{ {
var url = segment.Url; 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 (dResult != null && dResult.Success && segment.EncryptInfo != null)
{ {
if (segment.EncryptInfo.Method == EncryptMethod.AES_128) if (segment.EncryptInfo.Method == EncryptMethod.AES_128)

View File

@ -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; }
}
}

View File

@ -50,14 +50,10 @@ namespace N_m3u8DL_RE
try 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 //预先检查ffmpeg
@ -111,15 +107,10 @@ namespace N_m3u8DL_RE
{ {
AppendUrlParams = option.AppendUrlParams, AppendUrlParams = option.AppendUrlParams,
UrlProcessorArgs = option.UrlProcessorArgs, 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 //demo1
parserConfig.ContentProcessors.Insert(0, new DemoProcessor()); parserConfig.ContentProcessors.Insert(0, new DemoProcessor());
//demo2 //demo2
@ -254,9 +245,10 @@ namespace N_m3u8DL_RE
#endif #endif
//下载配置 //下载配置
var downloadConfig = new DownloaderConfig(option) var downloadConfig = new DownloaderConfig()
{ {
Headers = parserConfig.Headers, MyOptions = option,
Headers = parserConfig.Headers, //使用命令行解析得到的Headers
}; };
//开始下载 //开始下载
var sdm = new SimpleDownloadManager(downloadConfig); var sdm = new SimpleDownloadManager(downloadConfig);

View File

@ -18,7 +18,7 @@ namespace N_m3u8DL_RE.Util
var index = header.IndexOf(':'); var index = header.IndexOf(':');
if (index != -1) if (index != -1)
{ {
dic[header[..index].Trim()] = header[(index + 1)..].Trim(); dic[header[..index].Trim().ToLower()] = header[(index + 1)..].Trim();
} }
} }
} }