From 279d2b0e5e0aad44fb0bbd7fc6f7383ec98becd1 Mon Sep 17 00:00:00 2001 From: nilaoda Date: Sun, 21 Aug 2022 20:55:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=94=AF=E6=8C=81=E4=BD=BF?= =?UTF-8?q?=E7=94=A8mkvmerge=E6=B7=B7=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs | 2 ++ src/N_m3u8DL-RE.Common/Resource/ResString.cs | 2 ++ src/N_m3u8DL-RE.Common/Resource/StaticText.cs | 18 +++++++++-- src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs | 7 +++-- src/N_m3u8DL-RE/CommandLine/MyOption.cs | 8 +++++ src/N_m3u8DL-RE/Config/DownloaderConfig.cs | 10 +++++++ .../DownloadManager/SimpleDownloadManager.cs | 4 ++- src/N_m3u8DL-RE/Program.cs | 13 +++++++- src/N_m3u8DL-RE/Util/MergeUtil.cs | 30 ++++++++++++++++--- 9 files changed, 83 insertions(+), 11 deletions(-) diff --git a/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs b/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs index 5f39dcb..b83d7fb 100644 --- a/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs +++ b/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs @@ -31,6 +31,8 @@ namespace N_m3u8DL_RE.Common.Entity public string? VideoId { get; set; } public string? SubtitleId { get; set; } + public string? PeriodId { get; set; } + public string Url { get; set; } public Playlist? Playlist { get; set; } diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.cs b/src/N_m3u8DL-RE.Common/Resource/ResString.cs index 667b5db..9322bfd 100644 --- a/src/N_m3u8DL-RE.Common/Resource/ResString.cs +++ b/src/N_m3u8DL-RE.Common/Resource/ResString.cs @@ -23,6 +23,7 @@ namespace N_m3u8DL_RE.Common.Resource public static string cmd_decryptionBinaryPath { get => GetText("cmd_decryptionBinaryPath"); } public static string cmd_delAfterDone { get => GetText("cmd_delAfterDone"); } public static string cmd_ffmpegBinaryPath { get => GetText("cmd_ffmpegBinaryPath"); } + public static string cmd_mkvmergeBinaryPath { get => GetText("cmd_mkvmergeBinaryPath"); } public static string cmd_header { get => GetText("cmd_header"); } public static string cmd_Input { get => GetText("cmd_Input"); } public static string cmd_keys { get => GetText("cmd_keys"); } @@ -44,6 +45,7 @@ namespace N_m3u8DL_RE.Common.Resource public static string cmd_uiLanguage { get => GetText("cmd_uiLanguage"); } public static string cmd_urlProcessorArgs { get => GetText("cmd_urlProcessorArgs"); } public static string cmd_useShakaPackager { get => GetText("cmd_useShakaPackager"); } + public static string cmd_useMkvmerge { get => GetText("cmd_useMkvmerge"); } public static string cmd_muxAfterDone { get => GetText("cmd_muxAfterDone"); } public static string cmd_writeMetaJson { get => GetText("cmd_writeMetaJson"); } public static string fetch { get => GetText("fetch"); } diff --git a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs index 8ea4a33..1ed61c0 100644 --- a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs +++ b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs @@ -100,6 +100,12 @@ namespace N_m3u8DL_RE.Common.Resource zhTW: "ffmpeg可執行程序全路徑, 例如 C:\\Tools\\ffmpeg.exe", enUS: "Full path to the ffmpeg binary, like C:\\Tools\\ffmpeg.exe" ), + ["cmd_mkvmergeBinaryPath"] = new TextContainer + ( + zhCN: "mkvmerge可执行程序全路径, 例如 C:\\Tools\\mkvmerge.exe", + zhTW: "mkvmerge可執行程序全路徑, 例如 C:\\Tools\\mkvmerge.exe", + enUS: "Full path to the mkvmerge binary, like C:\\Tools\\mkvmerge.exe" + ), ["cmd_header"] = new TextContainer ( zhCN: "为HTTP请求设置特定的请求头, 例如:\r\n-H \"Cookie: mycookie\" -H \"User-Agent: iOS\"", @@ -216,9 +222,15 @@ namespace N_m3u8DL_RE.Common.Resource ), ["cmd_useShakaPackager"] = new TextContainer ( - zhCN: "使用shaka-packager替代mp4decrypt", - zhTW: "使用shaka-packager替代mp4decrypt", - enUS: "Use shaka-packager instead of mp4decrypt" + zhCN: "解密时使用shaka-packager替代mp4decrypt", + zhTW: "解密时使用shaka-packager替代mp4decrypt", + enUS: "Use shaka-packager instead of mp4decrypt to decrypt" + ), + ["cmd_useMkvmerge"] = new TextContainer + ( + zhCN: "混流时使用mkvmerge替代ffmpeg", + zhTW: "混流时使用mkvmerge替代ffmpeg", + enUS: "Use mkvmerge instead of ffmpeg to mux" ), ["cmd_muxAfterDone"] = new TextContainer ( diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs index b5a39a4..96bd571 100644 --- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs +++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs @@ -37,8 +37,10 @@ namespace N_m3u8DL_RE.CommandLine private readonly static Option MP4RealTimeDecryption = new (new string[] { "--mp4-real-time-decryption" }, description: ResString.cmd_MP4RealTimeDecryption, getDefaultValue: () => false); private readonly static Option UseShakaPackager = new (new string[] { "--use-shaka-packager" }, description: ResString.cmd_useShakaPackager, getDefaultValue: () => false); private readonly static Option MuxAfterDone = new (new string[] { "--mux-after-done" }, description: ResString.cmd_muxAfterDone, getDefaultValue: () => false); + private readonly static Option UseMkvmerge = new(new string[] { "--use-mkvmerge" }, description: ResString.cmd_useMkvmerge, getDefaultValue: () => false); private readonly static Option DecryptionBinaryPath = new(new string[] { "--decryption-binary-path" }, description: ResString.cmd_decryptionBinaryPath); private readonly static Option FFmpegBinaryPath = new(new string[] { "--ffmpeg-binary-path" }, description: ResString.cmd_ffmpegBinaryPath); + private readonly static Option MkvmergeBinaryPath = new(new string[] { "--mkvmerge-binary-path" }, description: ResString.cmd_mkvmergeBinaryPath); class MyOptionBinder : BinderBase { @@ -75,6 +77,7 @@ namespace N_m3u8DL_RE.CommandLine KeyTextFile = bindingContext.ParseResult.GetValueForOption(KeyTextFile), DownloadRetryCount = bindingContext.ParseResult.GetValueForOption(DownloadRetryCount), MuxAfterDone = bindingContext.ParseResult.GetValueForOption(MuxAfterDone), + UseMkvmerge = bindingContext.ParseResult.GetValueForOption(UseMkvmerge), }; @@ -97,8 +100,8 @@ namespace N_m3u8DL_RE.CommandLine { Input, TmpDir, SaveDir, SaveName, ThreadCount, DownloadRetryCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount, BinaryMerge, DelAfterDone, WriteMetaJson, MuxAfterDone, AppendUrlParams, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix, - FFmpegBinaryPath, - LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption + FFmpegBinaryPath, MkvmergeBinaryPath, + LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, UseMkvmerge, MP4RealTimeDecryption }; rootCommand.TreatUnmatchedTokensAsErrors = true; rootCommand.SetHandler(async (myOption) => await action(myOption), new MyOptionBinder()); diff --git a/src/N_m3u8DL-RE/CommandLine/MyOption.cs b/src/N_m3u8DL-RE/CommandLine/MyOption.cs index 54d00b4..870258c 100644 --- a/src/N_m3u8DL-RE/CommandLine/MyOption.cs +++ b/src/N_m3u8DL-RE/CommandLine/MyOption.cs @@ -90,6 +90,10 @@ namespace N_m3u8DL_RE.CommandLine /// public bool MuxAfterDone { get; set; } /// + /// See: . + /// + public bool UseMkvmerge { get; set; } + /// /// See: . /// public SubtitleFormat SubtitleFormat { get; set; } @@ -121,5 +125,9 @@ namespace N_m3u8DL_RE.CommandLine /// See: . /// public string? FFmpegBinaryPath { get; set; } + /// + /// See: . + /// + public string? MkvmergeBinaryPath { get; set; } } } \ No newline at end of file diff --git a/src/N_m3u8DL-RE/Config/DownloaderConfig.cs b/src/N_m3u8DL-RE/Config/DownloaderConfig.cs index 0e65156..9b3b8f6 100644 --- a/src/N_m3u8DL-RE/Config/DownloaderConfig.cs +++ b/src/N_m3u8DL-RE/Config/DownloaderConfig.cs @@ -34,6 +34,8 @@ namespace N_m3u8DL_RE.Config KeyTextFile = option.KeyTextFile; DownloadRetryCount = option.DownloadRetryCount; MuxAfterDone = option.MuxAfterDone; + UseMkvmerge = option.UseMkvmerge; + MkvmergeBinaryPath = option.MkvmergeBinaryPath; } /// @@ -97,6 +99,10 @@ namespace N_m3u8DL_RE.Config /// public bool MuxAfterDone { get; set; } /// + /// 使用mkvmerge混流 + /// + public bool UseMkvmerge { get; set; } + /// /// MP4解密所用工具的全路径 /// public string? DecryptionBinaryPath { get; set; } @@ -123,5 +129,9 @@ namespace N_m3u8DL_RE.Config /// ffmpeg路径 /// public string? FFmpegBinaryPath { get; set; } + /// + /// mkvmerge路径 + /// + public string? MkvmergeBinaryPath { get; set; } } } diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs index 457f264..39d3fad 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs @@ -568,7 +568,9 @@ namespace N_m3u8DL_RE.DownloadManager 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()}.mkv[/]"); - var result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath); + var result = false; + if (DownloaderConfig.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath); + else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath); //完成后删除各轨道文件 if (result) { diff --git a/src/N_m3u8DL-RE/Program.cs b/src/N_m3u8DL-RE/Program.cs index c6d7e10..8bda2b4 100644 --- a/src/N_m3u8DL-RE/Program.cs +++ b/src/N_m3u8DL-RE/Program.cs @@ -51,11 +51,22 @@ namespace N_m3u8DL_RE if (option.FFmpegBinaryPath == null) option.FFmpegBinaryPath = GlobalUtil.FindExecutable("ffmpeg"); - if (string.IsNullOrEmpty(option.FFmpegBinaryPath)) + if (string.IsNullOrEmpty(option.FFmpegBinaryPath) || !File.Exists(option.FFmpegBinaryPath)) { throw new FileNotFoundException(ResString.ffmpegNotFound); } + //预先检查mkvmerge + if (option.UseMkvmerge && option.MuxAfterDone) + { + if (option.MkvmergeBinaryPath == null) + option.MkvmergeBinaryPath = GlobalUtil.FindExecutable("mkvmerge"); + if (string.IsNullOrEmpty(option.MkvmergeBinaryPath) || !File.Exists(option.MkvmergeBinaryPath)) + { + throw new FileNotFoundException("mkvmerge not found"); + } + } + //预先检查 if ((option.Keys != null && option.Keys.Length > 0) || option.KeyTextFile != null) { diff --git a/src/N_m3u8DL-RE/Util/MergeUtil.cs b/src/N_m3u8DL-RE/Util/MergeUtil.cs index c192edd..39a35a0 100644 --- a/src/N_m3u8DL-RE/Util/MergeUtil.cs +++ b/src/N_m3u8DL-RE/Util/MergeUtil.cs @@ -50,6 +50,8 @@ namespace N_m3u8DL_RE.Util private static void InvokeFFmpeg(string binary, string command, string workingDirectory) { + Logger.DebugMarkUp($"{binary}: {command}"); + using var p = new Process(); p.StartInfo = new ProcessStartInfo() { @@ -129,8 +131,6 @@ namespace N_m3u8DL_RE.Util break; } - Logger.DebugMarkUp($"{binary}: {command}"); - InvokeFFmpeg(binary, command.ToString(), Path.GetDirectoryName(files[0])!); if (File.Exists($"{outputPath}.{muxFormat}") && new FileInfo($"{outputPath}.{muxFormat}").Length > 0) @@ -162,8 +162,7 @@ namespace N_m3u8DL_RE.Util //LANG and NAME for (int i = 0; i < files.Length; i++) { - if (!string.IsNullOrEmpty(files[i].LangCode)) - command.Append($" -metadata:s:{i} language={files[i].LangCode} "); + command.Append($" -metadata:s:{i} language={files[i].LangCode ?? "und"} "); if (!string.IsNullOrEmpty(files[i].Description)) command.Append($" -metadata:s:{i} title={files[i].Description} "); } @@ -177,5 +176,28 @@ namespace N_m3u8DL_RE.Util return false; } + + public static bool MuxInputsByMkvmerge(string binary, OutputFile[] files, string outputPath) + { + StringBuilder command = new StringBuilder($"-q --output \"{outputPath}.mkv\" "); + + command.Append(" --no-chapters "); + + //LANG and NAME + for (int i = 0; i < files.Length; i++) + { + command.Append($" --language 0:{files[i].LangCode ?? "und"} "); + if (!string.IsNullOrEmpty(files[i].Description)) + command.Append($" --track-name {i}:\"{files[i].Description}\" "); + command.Append($" \"{files[i].FilePath}\" "); + } + + InvokeFFmpeg(binary, command.ToString(), Environment.CurrentDirectory); + + if (File.Exists($"{outputPath}.mkv") && new FileInfo($"{outputPath}.mkv").Length > 1024) + return true; + + return false; + } } }