支持下载完成后自动封装音视频
This commit is contained in:
parent
01de8a53ad
commit
8d2b1d6faa
|
@ -44,6 +44,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_muxAfterDone { get => GetText("cmd_muxAfterDone"); }
|
||||
public static string cmd_writeMetaJson { get => GetText("cmd_writeMetaJson"); }
|
||||
public static string fetch { get => GetText("fetch"); }
|
||||
public static string ffmpegMerge { get => GetText("ffmpegMerge"); }
|
||||
|
|
|
@ -220,6 +220,12 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
zhTW: "使用shaka-packager替代mp4decrypt",
|
||||
enUS: "Use shaka-packager instead of mp4decrypt"
|
||||
),
|
||||
["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)"
|
||||
),
|
||||
["cmd_writeMetaJson"] = new TextContainer
|
||||
(
|
||||
zhCN: "解析后的信息是否输出json文件",
|
||||
|
|
|
@ -36,6 +36,7 @@ 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<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);
|
||||
|
||||
|
@ -73,6 +74,7 @@ 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),
|
||||
};
|
||||
|
||||
|
||||
|
@ -91,10 +93,10 @@ 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) 20220819")
|
||||
var rootCommand = new RootCommand("N_m3u8DL-RE (Beta version) 20220821")
|
||||
{
|
||||
Input, TmpDir, SaveDir, SaveName, ThreadCount, DownloadRetryCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
|
||||
BinaryMerge, DelAfterDone, WriteMetaJson, AppendUrlParams, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
|
||||
BinaryMerge, DelAfterDone, WriteMetaJson, MuxAfterDone, AppendUrlParams, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
|
||||
FFmpegBinaryPath,
|
||||
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption
|
||||
};
|
||||
|
|
|
@ -86,6 +86,10 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
/// </summary>
|
||||
public bool UseShakaPackager { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.MuxAfterDone"/>.
|
||||
/// </summary>
|
||||
public bool MuxAfterDone { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.SubtitleFormat"/>.
|
||||
/// </summary>
|
||||
public SubtitleFormat SubtitleFormat { get; set; }
|
||||
|
|
|
@ -33,6 +33,7 @@ namespace N_m3u8DL_RE.Config
|
|||
FFmpegBinaryPath = option.FFmpegBinaryPath;
|
||||
KeyTextFile = option.KeyTextFile;
|
||||
DownloadRetryCount = option.DownloadRetryCount;
|
||||
MuxAfterDone = option.MuxAfterDone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -92,6 +93,10 @@ namespace N_m3u8DL_RE.Config
|
|||
/// </summary>
|
||||
public bool UseShakaPackager { get; set; }
|
||||
/// <summary>
|
||||
/// 自动混流音视频
|
||||
/// </summary>
|
||||
public bool MuxAfterDone { get; set; }
|
||||
/// <summary>
|
||||
/// MP4解密所用工具的全路径
|
||||
/// </summary>
|
||||
public string? DecryptionBinaryPath { get; set; }
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
IDownloader Downloader;
|
||||
DownloaderConfig DownloaderConfig;
|
||||
DateTime NowDateTime;
|
||||
List<OutputFile> OutputFiles = new();
|
||||
|
||||
public SimpleDownloadManager(DownloaderConfig downloaderConfig)
|
||||
{
|
||||
|
@ -94,7 +95,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
if (segments == null) return false;
|
||||
|
||||
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.Language}";
|
||||
var dirName = $"{DownloaderConfig.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);
|
||||
|
@ -250,19 +251,20 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
});
|
||||
|
||||
var output = Path.Combine(saveDir, saveName + $".{streamSpec.Extension ?? "ts"}");
|
||||
//修改输出后缀
|
||||
var outputExt = "." + streamSpec.Extension;
|
||||
if (streamSpec.Extension == null) outputExt = ".ts";
|
||||
else if (streamSpec.MediaType == MediaType.AUDIO) outputExt = ".m4a";
|
||||
else if (streamSpec.MediaType != MediaType.SUBTITLES) outputExt = ".mp4";
|
||||
|
||||
var output = Path.Combine(saveDir, saveName + outputExt);
|
||||
|
||||
//检测目标文件是否存在
|
||||
while (File.Exists(output))
|
||||
{
|
||||
Logger.WarnMarkUp($"{output} => {output = Path.ChangeExtension(output, $"copy" + Path.GetExtension(output))}");
|
||||
}
|
||||
|
||||
//修改输出后缀
|
||||
if (streamSpec.MediaType == Common.Enum.MediaType.AUDIO)
|
||||
output = Path.ChangeExtension(output, ".m4a");
|
||||
else if (streamSpec.MediaType != Common.Enum.MediaType.SUBTITLES)
|
||||
output = Path.ChangeExtension(output, ".mp4");
|
||||
|
||||
if (DownloaderConfig.MP4RealTimeDecryption && mp4InitFile != "")
|
||||
{
|
||||
File.Delete(mp4InitFile);
|
||||
|
@ -464,10 +466,18 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
else
|
||||
{
|
||||
//ffmpeg合并
|
||||
var files = FileDic.Values.Select(v => v!.ActualFilePath).OrderBy(s => s).ToArray();
|
||||
Logger.InfoMarkUp(ResString.ffmpegMerge);
|
||||
var ext = streamSpec.MediaType == MediaType.AUDIO ? "m4a" : "mp4";
|
||||
mergeSuccess = MergeUtil.MergeByFFmpeg(DownloaderConfig.FFmpegBinaryPath!, files, Path.ChangeExtension(output, null), ext, useAACFilter);
|
||||
var ffOut = Path.Combine(Path.GetDirectoryName(output)!, Path.GetFileNameWithoutExtension(output) + $".{ext}");
|
||||
//检测目标文件是否存在
|
||||
while (File.Exists(ffOut))
|
||||
{
|
||||
Logger.WarnMarkUp($"{ffOut} => {ffOut = Path.ChangeExtension(ffOut, $"copy" + Path.GetExtension(ffOut))}");
|
||||
}
|
||||
mergeSuccess = MergeUtil.MergeByFFmpeg(DownloaderConfig.FFmpegBinaryPath!, files, Path.ChangeExtension(ffOut, null), ext, useAACFilter);
|
||||
if (mergeSuccess) output = ffOut;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -504,10 +514,13 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
{
|
||||
File.Delete(enc);
|
||||
File.Move(dec, enc);
|
||||
output = dec;
|
||||
}
|
||||
}
|
||||
|
||||
//记录所有文件信息
|
||||
if (File.Exists(output))
|
||||
OutputFiles.Add(new OutputFile() { FilePath = output, LangCode = streamSpec.Language, Description = streamSpec.Name });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -546,7 +559,20 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
});
|
||||
|
||||
return Results.Values.All(v => v == true);
|
||||
var success = Results.Values.All(v => v == true);
|
||||
|
||||
//混流
|
||||
if (success && OutputFiles.Count > 0)
|
||||
{
|
||||
var outName = $"{DownloaderConfig.SaveName ?? NowDateTime.ToString("yyyy-MM-dd_HH-mm-ss")}";
|
||||
Logger.WarnMarkUp($"Muxing to [grey]{outName.EscapeMarkup()}.mkv[/]");
|
||||
var result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.FFmpegBinaryPath!, OutputFiles.ToArray(), outName);
|
||||
//完成后删除各轨道文件
|
||||
if (result) OutputFiles.ForEach(f => File.Delete(f.FilePath));
|
||||
else Logger.ErrorMarkUp($"Mux failed");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_RE.Entity
|
||||
{
|
||||
internal class OutputFile
|
||||
{
|
||||
public required string FilePath { get; set; }
|
||||
public string? LangCode { get; set; }
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
}
|
|
@ -103,6 +103,7 @@ namespace N_m3u8DL_RE
|
|||
parserConfig.UrlProcessors.Insert(0, new NowehoryzontyUrlProcessor());
|
||||
|
||||
var url = string.Empty;
|
||||
//url = "https://media.axprod.net/TestVectors/v7-Clear/Manifest_1080p.mpd"; //多音轨多字幕
|
||||
//url = "https://cmafref.akamaized.net/cmaf/live-ull/2006350/akambr/out.mpd"; //直播
|
||||
//url = "http://playertest.longtailvideo.com/adaptive/oceans_aes/oceans_aes.m3u8";
|
||||
//url = "https://vod.sdn.wavve.com/hls/S01/S01_E461382925.1/1/5000/chunklist.m3u8";
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using N_m3u8DL_RE.Common.Log;
|
||||
using N_m3u8DL_RE.Entity;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.CommandLine;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
@ -46,6 +48,30 @@ namespace N_m3u8DL_RE.Util
|
|||
}
|
||||
}
|
||||
|
||||
private static void InvokeFFmpeg(string binary, string command, string workingDirectory)
|
||||
{
|
||||
using var p = new Process();
|
||||
p.StartInfo = new ProcessStartInfo()
|
||||
{
|
||||
WorkingDirectory = workingDirectory,
|
||||
FileName = binary,
|
||||
Arguments = command,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false
|
||||
};
|
||||
p.ErrorDataReceived += (sendProcess, output) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(output.Data))
|
||||
{
|
||||
Logger.WarnMarkUp($"[grey]{output.Data.EscapeMarkup()}[/]");
|
||||
}
|
||||
};
|
||||
p.Start();
|
||||
p.BeginErrorReadLine();
|
||||
p.WaitForExit();
|
||||
}
|
||||
|
||||
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 = "",
|
||||
|
@ -53,13 +79,6 @@ namespace N_m3u8DL_RE.Util
|
|||
{
|
||||
string dateString = string.IsNullOrEmpty(recTime) ? DateTime.Now.ToString("o") : recTime;
|
||||
|
||||
//同名文件已存在的共存策略
|
||||
if (File.Exists($"{outputPath}.{muxFormat.ToLower()}"))
|
||||
{
|
||||
outputPath = Path.Combine(Path.GetDirectoryName(outputPath)!,
|
||||
Path.GetFileName(outputPath) + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
|
||||
}
|
||||
|
||||
StringBuilder command = new StringBuilder("-loglevel warning -i concat:\"");
|
||||
string ddpAudio = string.Empty;
|
||||
string addPoster = "-map 1 -c:v:1 copy -disposition:v:1 attached_pic";
|
||||
|
@ -112,31 +131,51 @@ namespace N_m3u8DL_RE.Util
|
|||
|
||||
Logger.DebugMarkUp($"{binary}: {command}");
|
||||
|
||||
using var p = new Process();
|
||||
p.StartInfo = new ProcessStartInfo()
|
||||
{
|
||||
WorkingDirectory = Path.GetDirectoryName(files[0]),
|
||||
FileName = binary,
|
||||
Arguments = command.ToString(),
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false
|
||||
};
|
||||
p.ErrorDataReceived += (sendProcess, output) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(output.Data))
|
||||
{
|
||||
Logger.WarnMarkUp($"[grey]{output.Data.EscapeMarkup()}[/]");
|
||||
}
|
||||
};
|
||||
p.Start();
|
||||
p.BeginErrorReadLine();
|
||||
p.WaitForExit();
|
||||
InvokeFFmpeg(binary, command.ToString(), Path.GetDirectoryName(files[0])!);
|
||||
|
||||
if (File.Exists($"{outputPath}.{muxFormat}") && new FileInfo($"{outputPath}.{muxFormat}").Length > 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool MuxInputsByFFmpeg(string binary, OutputFile[] files, string outputPath)
|
||||
{
|
||||
string dateString = DateTime.Now.ToString("o");
|
||||
StringBuilder command = new StringBuilder("-loglevel warning -y ");
|
||||
|
||||
//INPUT
|
||||
foreach (var item in files)
|
||||
{
|
||||
command.Append($" -i \"{item.FilePath}\" ");
|
||||
}
|
||||
|
||||
//MAP
|
||||
for (int i = 0; i < files.Length; i++)
|
||||
{
|
||||
command.Append($" -map {i} ");
|
||||
}
|
||||
|
||||
//CLEAN
|
||||
command.Append(" -map_metadata -1 ");
|
||||
|
||||
//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} ");
|
||||
if (!string.IsNullOrEmpty(files[i].Description))
|
||||
command.Append($" -metadata:s:{i} title={files[i].Description} ");
|
||||
}
|
||||
|
||||
command.Append($" -metadata date=\"{dateString}\" -ignore_unknown -copy_unknown -c copy \"{outputPath}.mkv\"");
|
||||
|
||||
InvokeFFmpeg(binary, command.ToString(), Path.GetDirectoryName(files[0].FilePath)!);
|
||||
|
||||
if (File.Exists($"{outputPath}.mkv") && new FileInfo($"{outputPath}.mkv").Length > 1024)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue