diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.cs b/src/N_m3u8DL-RE.Common/Resource/ResString.cs index 7d46ff9..a880a41 100644 --- a/src/N_m3u8DL-RE.Common/Resource/ResString.cs +++ b/src/N_m3u8DL-RE.Common/Resource/ResString.cs @@ -48,6 +48,7 @@ namespace N_m3u8DL_RE.Common.Resource 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_muxToMp4 { get => GetText("cmd_muxToMp4"); } public static string cmd_writeMetaJson { get => GetText("cmd_writeMetaJson"); } public static string fetch { get => GetText("fetch"); } public static string ffmpegMerge { get => GetText("ffmpegMerge"); } diff --git a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs index 2ee39c2..924b8af 100644 --- a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs +++ b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs @@ -240,9 +240,15 @@ 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 audio(s) and video.(mkv)" + zhCN: "所有工作完成时尝试使用ffmpeg混流分离的音视频(默认容器: mkv)", + zhTW: "所有工作完成時嘗試使用ffmpeg混流分離的影音(默認容器: mkv)", + enUS: "When all works is done, try to use ffmpeg to mux the separated streams.(Default: mkv)" + ), + ["cmd_muxToMp4"] = new TextContainer + ( + zhCN: "混流时使用mp4容器而非mkv", + zhTW: "混流時使用mp4容器而非mkv", + enUS: "Use mp4 container instead of mkv when muxing" ), ["cmd_writeMetaJson"] = new TextContainer ( diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs index 78a12da..735ba2c 100644 --- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs +++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs @@ -37,6 +37,7 @@ 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 MuxToMp4 = new (new string[] { "--mux-to-mp4" }, description: ResString.cmd_muxToMp4, 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); @@ -80,6 +81,7 @@ namespace N_m3u8DL_RE.CommandLine MuxAfterDone = bindingContext.ParseResult.GetValueForOption(MuxAfterDone), UseMkvmerge = bindingContext.ParseResult.GetValueForOption(UseMkvmerge), BaseUrl = bindingContext.ParseResult.GetValueForOption(BaseUrl), + MuxToMp4 = bindingContext.ParseResult.GetValueForOption(MuxToMp4), }; @@ -101,9 +103,9 @@ namespace N_m3u8DL_RE.CommandLine var rootCommand = new RootCommand("N_m3u8DL-RE (Beta version) 20220822") { Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount, - BinaryMerge, DelAfterDone, WriteMetaJson, MuxAfterDone, AppendUrlParams, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix, + BinaryMerge, DelAfterDone, WriteMetaJson, MuxAfterDone, MuxToMp4, UseMkvmerge, AppendUrlParams, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix, FFmpegBinaryPath, MkvmergeBinaryPath, - LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, UseMkvmerge, MP4RealTimeDecryption + LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, 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 086882d..f649bfe 100644 --- a/src/N_m3u8DL-RE/CommandLine/MyOption.cs +++ b/src/N_m3u8DL-RE/CommandLine/MyOption.cs @@ -94,6 +94,10 @@ namespace N_m3u8DL_RE.CommandLine /// public bool MuxAfterDone { get; set; } /// + /// See: . + /// + public bool MuxToMp4 { get; set; } + /// /// See: . /// public bool UseMkvmerge { get; set; } diff --git a/src/N_m3u8DL-RE/Config/DownloaderConfig.cs b/src/N_m3u8DL-RE/Config/DownloaderConfig.cs index 9b3b8f6..776c0c1 100644 --- a/src/N_m3u8DL-RE/Config/DownloaderConfig.cs +++ b/src/N_m3u8DL-RE/Config/DownloaderConfig.cs @@ -36,6 +36,7 @@ namespace N_m3u8DL_RE.Config MuxAfterDone = option.MuxAfterDone; UseMkvmerge = option.UseMkvmerge; MkvmergeBinaryPath = option.MkvmergeBinaryPath; + MuxToMp4 = option.MuxToMp4; } /// @@ -99,6 +100,10 @@ namespace N_m3u8DL_RE.Config /// public bool MuxAfterDone { get; set; } /// + /// 自动混流音视频容器使用mp4 + /// + public bool MuxToMp4 { get; set; } + /// /// 使用mkvmerge混流 /// public bool UseMkvmerge { get; set; } diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs index 249d13e..0b9ea2d 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs @@ -280,7 +280,7 @@ namespace N_m3u8DL_RE.DownloadManager //检测目标文件是否存在 while (File.Exists(output)) { - Logger.WarnMarkUp($"{output} => {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 != "") @@ -492,7 +492,7 @@ namespace N_m3u8DL_RE.DownloadManager //检测目标文件是否存在 while (File.Exists(ffOut)) { - Logger.WarnMarkUp($"{ffOut} => {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); if (mergeSuccess) output = ffOut; @@ -582,10 +582,10 @@ namespace N_m3u8DL_RE.DownloadManager var saveDir = DownloaderConfig.SaveDir ?? Environment.CurrentDirectory; 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[/]"); + 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); + else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MuxToMp4); //完成后删除各轨道文件 if (result) { diff --git a/src/N_m3u8DL-RE/Program.cs b/src/N_m3u8DL-RE/Program.cs index 6fde3c6..990a7db 100644 --- a/src/N_m3u8DL-RE/Program.cs +++ b/src/N_m3u8DL-RE/Program.cs @@ -13,6 +13,7 @@ using N_m3u8DL_RE.Config; using N_m3u8DL_RE.Util; using N_m3u8DL_RE.DownloadManager; using N_m3u8DL_RE.CommandLine; +using N_m3u8DL_RE.Entity; namespace N_m3u8DL_RE { @@ -20,6 +21,7 @@ namespace N_m3u8DL_RE { static async Task Main(string[] args) { + Console.CursorVisible = true; string loc = "en-US"; string currLoc = Thread.CurrentThread.CurrentUICulture.Name; if (currLoc == "zh-CN" || currLoc == "zh-SG") loc = "zh-CN"; @@ -47,6 +49,12 @@ namespace N_m3u8DL_RE try { + //检查互斥的选项 + if (option.UseMkvmerge && option.MuxToMp4) + { + throw new ArgumentException("Can't use mkvmerge to make mp4!"); + } + //预先检查ffmpeg if (option.FFmpegBinaryPath == null) option.FFmpegBinaryPath = GlobalUtil.FindExecutable("ffmpeg"); diff --git a/src/N_m3u8DL-RE/Util/MergeUtil.cs b/src/N_m3u8DL-RE/Util/MergeUtil.cs index d379c0d..63e0c28 100644 --- a/src/N_m3u8DL-RE/Util/MergeUtil.cs +++ b/src/N_m3u8DL-RE/Util/MergeUtil.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.CommandLine; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -142,8 +143,9 @@ namespace N_m3u8DL_RE.Util return false; } - public static bool MuxInputsByFFmpeg(string binary, OutputFile[] files, string outputPath) + public static bool MuxInputsByFFmpeg(string binary, OutputFile[] files, string outputPath, bool mp4) { + var ext = mp4 ? "mp4" : "mkv"; string dateString = DateTime.Now.ToString("o"); StringBuilder command = new StringBuilder("-loglevel warning -y "); @@ -159,22 +161,35 @@ namespace N_m3u8DL_RE.Util command.Append($" -map {i} "); } + if (mp4) + command.Append($" -c:a copy -c:v copy -c:s mov_text "); //mp4不支持vtt/srt字幕,必须转换格式 + else + command.Append($" -c copy "); + //CLEAN command.Append(" -map_metadata -1 "); //LANG and NAME for (int i = 0; i < files.Length; i++) { - command.Append($" -metadata:s:{i} language={files[i].LangCode ?? "und"} "); + //转换语言代码 + ConvertLangCodeAndDisplayName(files[i]); + 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} "); + { + if(mp4) + command.Append($" -metadata:s:{i} handler_name=\"{files[i].Description}\" -metadata:s:{i} handler=\"{files[i].Description}\" "); + else + command.Append($" -metadata:s:{i} title=\"{files[i].Description}\" "); + } } - command.Append($" -metadata date=\"{dateString}\" -ignore_unknown -copy_unknown -c copy \"{outputPath}.mkv\""); - + command.Append($" -metadata date=\"{dateString}\" -ignore_unknown -copy_unknown "); + command.Append($" \"{outputPath}.{ext}\""); + InvokeFFmpeg(binary, command.ToString(), Environment.CurrentDirectory); - if (File.Exists($"{outputPath}.mkv") && new FileInfo($"{outputPath}.mkv").Length > 1024) + if (File.Exists($"{outputPath}.{ext}") && new FileInfo($"{outputPath}.{ext}").Length > 1024) return true; return false; @@ -189,7 +204,9 @@ namespace N_m3u8DL_RE.Util //LANG and NAME for (int i = 0; i < files.Length; i++) { - command.Append($" --language 0:{files[i].LangCode ?? "und"} "); + //转换语言代码 + ConvertLangCodeAndDisplayName(files[i]); + command.Append($" --language 0:\"{files[i].LangCode ?? "und"}\" "); if (!string.IsNullOrEmpty(files[i].Description)) command.Append($" --track-name 0:\"{files[i].Description}\" "); command.Append($" \"{files[i].FilePath}\" "); @@ -202,5 +219,28 @@ namespace N_m3u8DL_RE.Util return false; } + + /// + /// 转换 ISO 639-1 => ISO 639-2 + /// 且当Description为空时将DisplayName写入 + /// + /// + private static void ConvertLangCodeAndDisplayName(OutputFile outputFile) + { + if (string.IsNullOrEmpty(outputFile.LangCode)) return; + CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures); + foreach (var c in cultures) + { + if (outputFile.LangCode.Split('-')[0] == c.TwoLetterISOLanguageName) + { + outputFile.LangCode = c.ThreeLetterISOLanguageName; + if (string.IsNullOrEmpty(outputFile.Description)) + { + outputFile.Description = c.DisplayName; + } + break; + } + } + } } }