diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.cs b/src/N_m3u8DL-RE.Common/Resource/ResString.cs index fb7e44de..dd500784 100644 --- a/src/N_m3u8DL-RE.Common/Resource/ResString.cs +++ b/src/N_m3u8DL-RE.Common/Resource/ResString.cs @@ -28,6 +28,7 @@ public class ResString public static string cmd_appendUrlParams { get => GetText("cmd_appendUrlParams"); } public static string cmd_autoSelect { get => GetText("cmd_autoSelect"); } public static string cmd_binaryMerge { get => GetText("cmd_binaryMerge"); } + public static string cmd_useFFmpegConcatDemuxer { get => GetText("cmd_useFFmpegConcatDemuxer"); } public static string cmd_checkSegmentsCount { get => GetText("cmd_checkSegmentsCount"); } public static string cmd_decryptionBinaryPath { get => GetText("cmd_decryptionBinaryPath"); } public static string cmd_delAfterDone { get => GetText("cmd_delAfterDone"); } diff --git a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs index 5e12a52d..1de99b4c 100644 --- a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs +++ b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs @@ -172,6 +172,12 @@ internal class StaticText zhTW: "二進位制合併", enUS: "Binary merge" ), + ["cmd_useFFmpegConcatDemuxer"] = new TextContainer + ( + zhCN: "使用 ffmpeg 合并时,使用 concat 分离器而非 concat 协议", + zhTW: "使用 ffmpeg 合併時,使用 concat 分離器而非 concat 協議", + enUS: "When merging with ffmpeg, use the concat demuxer instead of the concat protocol" + ), ["cmd_checkSegmentsCount"] = new TextContainer ( zhCN: "检测实际下载的分片数量和预期数量是否匹配", diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs index 154839a8..2d345d72 100644 --- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs +++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs @@ -18,7 +18,7 @@ namespace N_m3u8DL_RE.CommandLine { internal partial class CommandInvoker { - public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20230730"; + public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20230820"; [GeneratedRegex("((best|worst)\\d*|all)")] private static partial Regex ForStrRegex(); @@ -45,6 +45,7 @@ internal partial class CommandInvoker private readonly static Option SkipDownload = new(new string[] { "--skip-download" }, description: ResString.cmd_skipDownload, getDefaultValue: () => false); private readonly static Option NoDateInfo = new(new string[] { "--no-date-info" }, description: ResString.cmd_noDateInfo, getDefaultValue: () => false); private readonly static Option BinaryMerge = new(new string[] { "--binary-merge" }, description: ResString.cmd_binaryMerge, getDefaultValue: () => false); + private readonly static Option UseFFmpegConcatDemuxer = new(new string[] { "--use-ffmpeg-concat-demuxer" }, description: ResString.cmd_useFFmpegConcatDemuxer, getDefaultValue: () => false); private readonly static Option DelAfterDone = new(new string[] { "--del-after-done" }, description: ResString.cmd_delAfterDone, getDefaultValue: () => true); private readonly static Option AutoSubtitleFix = new(new string[] { "--auto-subtitle-fix" }, description: ResString.cmd_subtitleFix, getDefaultValue: () => true); private readonly static Option CheckSegmentsCount = new(new string[] { "--check-segments-count" }, description: ResString.cmd_checkSegmentsCount, getDefaultValue: () => true); @@ -483,6 +484,7 @@ protected override MyOption GetBoundValue(BindingContext bindingContext) AutoSelect = bindingContext.ParseResult.GetValueForOption(AutoSelect), SkipMerge = bindingContext.ParseResult.GetValueForOption(SkipMerge), BinaryMerge = bindingContext.ParseResult.GetValueForOption(BinaryMerge), + UseFFmpegConcatDemuxer = bindingContext.ParseResult.GetValueForOption(UseFFmpegConcatDemuxer), DelAfterDone = bindingContext.ParseResult.GetValueForOption(DelAfterDone), AutoSubtitleFix = bindingContext.ParseResult.GetValueForOption(AutoSubtitleFix), CheckSegmentsCount = bindingContext.ParseResult.GetValueForOption(CheckSegmentsCount), @@ -589,7 +591,7 @@ public static async Task InvokeArgs(string[] args, Func act var rootCommand = new RootCommand(VERSION_INFO) { Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount, - BinaryMerge, DelAfterDone, NoDateInfo, NoLog, WriteMetaJson, AppendUrlParams, ConcurrentDownload, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix, + BinaryMerge, UseFFmpegConcatDemuxer, DelAfterDone, NoDateInfo, NoLog, WriteMetaJson, AppendUrlParams, ConcurrentDownload, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix, FFmpegBinaryPath, LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption, MaxSpeed, diff --git a/src/N_m3u8DL-RE/CommandLine/MyOption.cs b/src/N_m3u8DL-RE/CommandLine/MyOption.cs index 3303fd85..1bb44784 100644 --- a/src/N_m3u8DL-RE/CommandLine/MyOption.cs +++ b/src/N_m3u8DL-RE/CommandLine/MyOption.cs @@ -85,6 +85,10 @@ internal class MyOption /// public bool BinaryMerge { get; set; } /// + /// See: . + /// + public bool UseFFmpegConcatDemuxer { get; set; } + /// /// See: . /// public bool DelAfterDone { get; set; } diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs index 73af9bde..280d0c63 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs @@ -557,7 +557,7 @@ await Parallel.ForEachAsync(segments, options, async (seg, _) => }; } } - mergeSuccess = MergeUtil.MergeByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, files, Path.ChangeExtension(ffOut, null), ext, useAACFilter, writeDate: !DownloaderConfig.MyOptions.NoDateInfo); + mergeSuccess = MergeUtil.MergeByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, files, Path.ChangeExtension(ffOut, null), ext, useAACFilter, writeDate: !DownloaderConfig.MyOptions.NoDateInfo, useConcatDemuxer: DownloaderConfig.MyOptions.UseFFmpegConcatDemuxer); if (mergeSuccess) output = ffOut; } } diff --git a/src/N_m3u8DL-RE/Util/MergeUtil.cs b/src/N_m3u8DL-RE/Util/MergeUtil.cs index 0e49d3ac..716cf506 100644 --- a/src/N_m3u8DL-RE/Util/MergeUtil.cs +++ b/src/N_m3u8DL-RE/Util/MergeUtil.cs @@ -110,7 +110,7 @@ public static string[] PartialCombineMultipleFiles(string[] files) public static bool MergeByFFmpeg(string binary, string[] files, string outputPath, string muxFormat, bool useAACFilter, bool fastStart = false, - bool writeDate = true, string poster = "", string audioName = "", string title = "", + bool writeDate = true, bool useConcatDemuxer = false, string poster = "", string audioName = "", string title = "", string copyright = "", string comment = "", string encodingTool = "", string recTime = "") { //改为绝对路径 @@ -118,16 +118,29 @@ public static bool MergeByFFmpeg(string binary, string[] files, string outputPat string dateString = string.IsNullOrEmpty(recTime) ? DateTime.Now.ToString("o") : recTime; - StringBuilder command = new StringBuilder("-loglevel warning -nostdin -i concat:\""); + StringBuilder command = new StringBuilder("-loglevel warning -nostdin "); string ddpAudio = string.Empty; string addPoster = "-map 1 -c:v:1 copy -disposition:v:1 attached_pic"; ddpAudio = (File.Exists($"{Path.GetFileNameWithoutExtension(outputPath + ".mp4")}.txt") ? File.ReadAllText($"{Path.GetFileNameWithoutExtension(outputPath + ".mp4")}.txt") : ""); if (!string.IsNullOrEmpty(ddpAudio)) useAACFilter = false; - foreach (string t in files) + if (useConcatDemuxer) { - command.Append(Path.GetFileName(t) + "|"); + // 使用 concat demuxer合并 + var text = string.Join(Environment.NewLine, files.Select(f => $"file '{f}'")); + var tempFile = Path.GetTempFileName(); + File.WriteAllText(tempFile, text); + command.Append($" -f concat -safe 0 -i \"{tempFile}"); } + else + { + command.Append(" -i concat:\""); + foreach (string t in files) + { + command.Append(Path.GetFileName(t) + "|"); + } + } + switch (muxFormat.ToUpper()) {