使用ffmpeg读取媒体信息以辅助判断媒体类型
This commit is contained in:
parent
9be32b2872
commit
df354e4eb8
|
@ -69,6 +69,24 @@ namespace N_m3u8DL_RE.Common.Resource {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Dolby Vision content is detected, binary merging is automatically enabled 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string autoBinaryMerge2 {
|
||||
get {
|
||||
return ResourceManager.GetString("autoBinaryMerge2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 An unrecognized encryption method is detected, binary merging is automatically enabled 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string autoBinaryMerge3 {
|
||||
get {
|
||||
return ResourceManager.GetString("autoBinaryMerge3", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Bad m3u8 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -368,6 +386,15 @@ namespace N_m3u8DL_RE.Common.Resource {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 ffmpeg not found, please download at: https://ffmpeg.org/download.html 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string ffmpegNotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("ffmpegNotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Extracting TTML(raw) subtitle... 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -503,6 +530,15 @@ namespace N_m3u8DL_RE.Common.Resource {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Reading media info... 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string readingInfo {
|
||||
get {
|
||||
return ResourceManager.GetString("readingInfo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Trying to search for KEY from text file... 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
|
@ -264,4 +264,16 @@
|
|||
<data name="searchKey" xml:space="preserve">
|
||||
<value>Trying to search for KEY from text file...</value>
|
||||
</data>
|
||||
<data name="ffmpegNotFound" xml:space="preserve">
|
||||
<value>ffmpeg not found, please download at: https://ffmpeg.org/download.html</value>
|
||||
</data>
|
||||
<data name="readingInfo" xml:space="preserve">
|
||||
<value>Reading media info...</value>
|
||||
</data>
|
||||
<data name="autoBinaryMerge2" xml:space="preserve">
|
||||
<value>Dolby Vision content is detected, binary merging is automatically enabled</value>
|
||||
</data>
|
||||
<data name="autoBinaryMerge3" xml:space="preserve">
|
||||
<value>An unrecognized encryption method is detected, binary merging is automatically enabled</value>
|
||||
</data>
|
||||
</root>
|
|
@ -284,4 +284,16 @@
|
|||
<data name="searchKey" xml:space="preserve">
|
||||
<value>正在尝试从文本文件搜索KEY...</value>
|
||||
</data>
|
||||
<data name="ffmpegNotFound" xml:space="preserve">
|
||||
<value>找不到ffmpeg,请自行下载:https://ffmpeg.org/download.html</value>
|
||||
</data>
|
||||
<data name="readingInfo" xml:space="preserve">
|
||||
<value>读取媒体信息...</value>
|
||||
</data>
|
||||
<data name="autoBinaryMerge2" xml:space="preserve">
|
||||
<value>检测到杜比视界内容,自动开启二进制合并</value>
|
||||
</data>
|
||||
<data name="autoBinaryMerge3" xml:space="preserve">
|
||||
<value>检测到无法识别的加密方式,自动开启二进制合并</value>
|
||||
</data>
|
||||
</root>
|
|
@ -261,4 +261,16 @@
|
|||
<data name="searchKey" xml:space="preserve">
|
||||
<value>正在嘗試從文本文件搜尋KEY...</value>
|
||||
</data>
|
||||
<data name="ffmpegNotFound" xml:space="preserve">
|
||||
<value>找不到ffmpeg,請自行下載:https://ffmpeg.org/download.html</value>
|
||||
</data>
|
||||
<data name="readingInfo" xml:space="preserve">
|
||||
<value>讀取媒體訊息...</value>
|
||||
</data>
|
||||
<data name="autoBinaryMerge2" xml:space="preserve">
|
||||
<value>檢測到杜比視界內容,自動開啟二進位制合併</value>
|
||||
</data>
|
||||
<data name="autoBinaryMerge3" xml:space="preserve">
|
||||
<value>檢測到無法識別的加密方式,自動開啟二進位制合併</value>
|
||||
</data>
|
||||
</root>
|
|
@ -57,7 +57,7 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ResString.cmd_loadKeyFailed + ": " + ex.ToString());
|
||||
Logger.Error(ResString.cmd_loadKeyFailed + ": " + ex.Message);
|
||||
encryptInfo.Method = EncryptMethod.UNKNOWN;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,9 +26,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
NowDateTime = DateTime.Now;
|
||||
}
|
||||
|
||||
private async Task<bool> DownloadStreamAsync(StreamSpec streamSpec, ProgressTask task)
|
||||
{
|
||||
string? ReadInit(byte[] data)
|
||||
private string? ReadInit(byte[] data)
|
||||
{
|
||||
var info = MP4InitUtil.ReadInit(data);
|
||||
if (info.Scheme != null) Logger.WarnMarkUp($"[grey]Type: {info.Scheme}[/]");
|
||||
|
@ -37,6 +35,28 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
return info.KID;
|
||||
}
|
||||
|
||||
private void ChangeSpecInfo(StreamSpec streamSpec, List<Mediainfo> mediainfos)
|
||||
{
|
||||
if (!DownloaderConfig.BinaryMerge && mediainfos.Any(m => m.DolbyVison == true))
|
||||
{
|
||||
DownloaderConfig.BinaryMerge = true;
|
||||
Logger.WarnMarkUp(ResString.autoBinaryMerge2);
|
||||
}
|
||||
|
||||
if (mediainfos.All(m => m.Type == "Audio"))
|
||||
{
|
||||
streamSpec.MediaType = MediaType.AUDIO;
|
||||
}
|
||||
else if (mediainfos.All(m => m.Type == "Subtitle"))
|
||||
{
|
||||
streamSpec.MediaType = MediaType.SUBTITLES;
|
||||
if (streamSpec.Extension == null || streamSpec.Extension == "ts")
|
||||
streamSpec.Extension = "vtt";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> DownloadStreamAsync(StreamSpec streamSpec, ProgressTask task)
|
||||
{
|
||||
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
|
||||
|
||||
var segments = streamSpec.Playlist?.MediaParts.SelectMany(m => m.MediaSegments);
|
||||
|
@ -50,19 +70,14 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
var saveDir = DownloaderConfig.SaveDir ?? Environment.CurrentDirectory;
|
||||
var saveName = DownloaderConfig.SaveName != null ? $"{DownloaderConfig.SaveName}.{type}.{streamSpec.Language}".TrimEnd('.') : dirName;
|
||||
var headers = DownloaderConfig.Headers;
|
||||
var output = Path.Combine(saveDir, saveName + $".{streamSpec.Extension ?? "ts"}");
|
||||
//检测目标文件是否存在
|
||||
while (File.Exists(output))
|
||||
{
|
||||
Logger.WarnMarkUp($"{output} => {output = Path.ChangeExtension(output, $"copy" + Path.GetExtension(output))}");
|
||||
}
|
||||
|
||||
//mp4decrypt
|
||||
var mp4decrypt = DownloaderConfig.DecryptionBinaryPath!;
|
||||
var mp4InitFile = "";
|
||||
var currentKID = "";
|
||||
var readInfo = false; //是否读取过
|
||||
|
||||
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}; output: {output}");
|
||||
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}");
|
||||
|
||||
//创建文件夹
|
||||
if (!Directory.Exists(tmpDir)) Directory.CreateDirectory(tmpDir);
|
||||
|
@ -83,7 +98,13 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
//下载init
|
||||
if (streamSpec.Playlist?.MediaInit != null)
|
||||
{
|
||||
totalCount++;
|
||||
//对于fMP4,自动开启二进制合并
|
||||
if (!DownloaderConfig.BinaryMerge && streamSpec.MediaType != MediaType.SUBTITLES)
|
||||
{
|
||||
DownloaderConfig.BinaryMerge = true;
|
||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge}[/]");
|
||||
}
|
||||
|
||||
var path = Path.Combine(tmpDir, "_init.mp4.tmp");
|
||||
var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, headers);
|
||||
FileDic[streamSpec.Playlist.MediaInit] = result;
|
||||
|
@ -94,12 +115,6 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
mp4InitFile = result.ActualFilePath;
|
||||
task.Increment(1);
|
||||
|
||||
//修改输出后缀
|
||||
if (streamSpec.MediaType == Common.Enum.MediaType.AUDIO)
|
||||
output = Path.ChangeExtension(output, ".m4a");
|
||||
else
|
||||
output = Path.ChangeExtension(output, ".mp4");
|
||||
|
||||
//读取mp4信息
|
||||
if (result != null && result.Success)
|
||||
{
|
||||
|
@ -125,11 +140,53 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
||||
}
|
||||
}
|
||||
//ffmpeg读取信息
|
||||
if (!readInfo)
|
||||
{
|
||||
Logger.WarnMarkUp(ResString.readingInfo);
|
||||
var mediainfos = await MediainfoUtil.ReadInfoAsync(DownloaderConfig.FFmpegBinaryPath!, result.ActualFilePath);
|
||||
mediainfos.ForEach(info => Logger.InfoMarkUp(info.ToStringMarkUp()));
|
||||
ChangeSpecInfo(streamSpec, mediainfos);
|
||||
readInfo = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//开始下载
|
||||
//计算填零个数
|
||||
var pad = "0".PadLeft(segments.Count().ToString().Length, '0');
|
||||
|
||||
//下载第一个分片
|
||||
if (!readInfo)
|
||||
{
|
||||
var seg = segments.First();
|
||||
segments = segments.Skip(1);
|
||||
|
||||
var index = seg.Index;
|
||||
var path = Path.Combine(tmpDir, index.ToString(pad) + $".{streamSpec.Extension ?? "clip"}.tmp");
|
||||
var result = await Downloader.DownloadSegmentAsync(seg, path, headers);
|
||||
FileDic[seg] = result;
|
||||
task.Increment(1);
|
||||
//实时解密
|
||||
if (DownloaderConfig.MP4RealTimeDecryption && seg.EncryptInfo.Method != Common.Enum.EncryptMethod.NONE && result != null)
|
||||
{
|
||||
var enc = result.ActualFilePath;
|
||||
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);
|
||||
if (dResult)
|
||||
{
|
||||
File.Delete(enc);
|
||||
result.ActualFilePath = dec;
|
||||
}
|
||||
}
|
||||
//ffmpeg读取信息
|
||||
Logger.WarnMarkUp(ResString.readingInfo);
|
||||
var mediainfos = await MediainfoUtil.ReadInfoAsync(DownloaderConfig.FFmpegBinaryPath!, result!.ActualFilePath);
|
||||
mediainfos.ForEach(info => Logger.InfoMarkUp(info.ToStringMarkUp()));
|
||||
ChangeSpecInfo(streamSpec, mediainfos);
|
||||
readInfo = true;
|
||||
}
|
||||
|
||||
//开始下载
|
||||
var options = new ParallelOptions()
|
||||
{
|
||||
MaxDegreeOfParallelism = DownloaderConfig.ThreadCount
|
||||
|
@ -155,6 +212,19 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
});
|
||||
|
||||
var output = Path.Combine(saveDir, saveName + $".{streamSpec.Extension ?? "ts"}");
|
||||
//检测目标文件是否存在
|
||||
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);
|
||||
|
@ -346,13 +416,6 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
//合并
|
||||
if (!DownloaderConfig.SkipMerge)
|
||||
{
|
||||
//对于fMP4,自动开启二进制合并
|
||||
if (!DownloaderConfig.BinaryMerge && streamSpec.MediaType != MediaType.SUBTITLES && mp4InitFile != "")
|
||||
{
|
||||
DownloaderConfig.BinaryMerge = true;
|
||||
Logger.WarnMarkUp($"[white on darkorange3_1]{ResString.autoBinaryMerge}[/]");
|
||||
}
|
||||
|
||||
//字幕也使用二进制合并
|
||||
if (DownloaderConfig.BinaryMerge || streamSpec.MediaType == MediaType.SUBTITLES)
|
||||
{
|
||||
|
|
|
@ -47,7 +47,7 @@ namespace N_m3u8DL_RE.Downloader
|
|||
}
|
||||
else if (segment.EncryptInfo.Method == EncryptMethod.SAMPLE_AES_CTR)
|
||||
{
|
||||
throw new NotSupportedException("SAMPLE-AES-CTR");
|
||||
//throw new NotSupportedException("SAMPLE-AES-CTR");
|
||||
}
|
||||
}
|
||||
return dResult;
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace N_m3u8DL_RE.Entity
|
||||
{
|
||||
internal class Mediainfo
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Text { get; set; }
|
||||
public string BaseInfo { get; set; }
|
||||
public string Bitrate { get; set; }
|
||||
public string Resolution { get; set; }
|
||||
public string Fps { get; set; }
|
||||
public string Type { get; set; }
|
||||
public bool DolbyVison { get; set; }
|
||||
|
||||
public override string? ToString()
|
||||
{
|
||||
return $"{(string.IsNullOrEmpty(Id) ? "NaN" : Id)}: " + string.Join(", ", new List<string> { Type, BaseInfo, Resolution, Fps, Bitrate }.Where(i => !string.IsNullOrEmpty(i)));
|
||||
}
|
||||
|
||||
public string ToStringMarkUp()
|
||||
{
|
||||
return "[steelblue]" + ToString().EscapeMarkup() + (DolbyVison ? " [darkorange3_1][[DOVI]][/]" : "") + "[/]";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,13 +39,12 @@ namespace N_m3u8DL_RE
|
|||
try
|
||||
{
|
||||
//预先检查ffmpeg
|
||||
if (!option.BinaryMerge)
|
||||
{
|
||||
if (option.FFmpegBinaryPath == null)
|
||||
option.FFmpegBinaryPath = GlobalUtil.FindExecutable("ffmpeg");
|
||||
|
||||
if (string.IsNullOrEmpty(option.FFmpegBinaryPath))
|
||||
{
|
||||
throw new FileNotFoundException("ffmpeg not found!");
|
||||
}
|
||||
throw new FileNotFoundException(ResString.ffmpegNotFound);
|
||||
}
|
||||
|
||||
//预先检查
|
||||
|
@ -192,6 +191,12 @@ namespace N_m3u8DL_RE
|
|||
if (lists.Count() > 1)
|
||||
await extractor.FetchPlayListAsync(selectedStreams);
|
||||
|
||||
//无法识别的加密方式,自动开启二进制合并
|
||||
if (selectedStreams.Any(s => s.Playlist.MediaParts.Any(p => p.MediaSegments.Any(m => m.EncryptInfo.Method == EncryptMethod.UNKNOWN))))
|
||||
{
|
||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge3}[/]");
|
||||
}
|
||||
|
||||
if (option.WriteMetaJson)
|
||||
{
|
||||
Logger.Warn(ResString.writeJson);
|
||||
|
@ -228,7 +233,11 @@ namespace N_m3u8DL_RE
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex.ToString());
|
||||
string msg = ex.Message;
|
||||
#if DEBUG
|
||||
msg = ex.ToString();
|
||||
#endif
|
||||
Logger.Error(msg);
|
||||
await Task.Delay(3000);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
using N_m3u8DL_RE.Entity;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace N_m3u8DL_RE.Util
|
||||
{
|
||||
internal partial class MediainfoUtil
|
||||
{
|
||||
[RegexGenerator(" Stream #.*")]
|
||||
private static partial Regex TextRegex();
|
||||
[RegexGenerator("#0:\\d(\\[0x\\w+?\\])")]
|
||||
private static partial Regex IdRegex();
|
||||
[RegexGenerator(": (\\w+): (.*)")]
|
||||
private static partial Regex TypeRegex();
|
||||
[RegexGenerator("(.*?)(,|$)")]
|
||||
private static partial Regex BaseInfoRegex();
|
||||
[RegexGenerator(" \\/ 0x\\w+")]
|
||||
private static partial Regex ReplaceRegex();
|
||||
[RegexGenerator("\\d{2,}x\\d+")]
|
||||
private static partial Regex ResRegex();
|
||||
[RegexGenerator("\\d+ kb\\/s")]
|
||||
private static partial Regex BitrateRegex();
|
||||
[RegexGenerator("\\d+ fps")]
|
||||
private static partial Regex FpsRegex();
|
||||
|
||||
public static async Task<List<Mediainfo>> ReadInfoAsync(string binary, string file)
|
||||
{
|
||||
var result = new List<Mediainfo>();
|
||||
|
||||
if (string.IsNullOrEmpty(file) || !File.Exists(file)) return result;
|
||||
|
||||
string cmd = "-hide_banner -i \"" + file + "\"";
|
||||
var p = Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = binary,
|
||||
Arguments = cmd,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false
|
||||
})!;
|
||||
var output = p.StandardError.ReadToEnd();
|
||||
await p.WaitForExitAsync();
|
||||
|
||||
foreach (Match stream in TextRegex().Matches(output))
|
||||
{
|
||||
var info = new Mediainfo()
|
||||
{
|
||||
Text = TypeRegex().Match(stream.Value).Groups[2].Value,
|
||||
Id = IdRegex().Match(stream.Value).Groups[1].Value,
|
||||
Type = TypeRegex().Match(stream.Value).Groups[1].Value,
|
||||
};
|
||||
|
||||
info.Resolution = ResRegex().Match(info.Text).Value;
|
||||
info.Bitrate = BitrateRegex().Match(info.Text).Value;
|
||||
info.Fps = FpsRegex().Match(info.Text).Value;
|
||||
info.BaseInfo = BaseInfoRegex().Match(info.Text).Groups[1].Value;
|
||||
info.BaseInfo = ReplaceRegex().Replace(info.BaseInfo, "");
|
||||
|
||||
if (info.BaseInfo.Contains("dvhe")
|
||||
|| info.BaseInfo.Contains("dvh1")
|
||||
|| info.BaseInfo.Contains("DOVI")
|
||||
|| info.Type.Contains("dvvideo")
|
||||
)
|
||||
info.DolbyVison = true;
|
||||
|
||||
result.Add(info);
|
||||
}
|
||||
|
||||
if (result.Count == 0)
|
||||
{
|
||||
result.Add(new Mediainfo()
|
||||
{
|
||||
Type = "Unknown"
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,6 +51,13 @@ 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"));
|
||||
}
|
||||
|
||||
bool useAACFilter = true;
|
||||
StringBuilder command = new StringBuilder("-loglevel warning -i concat:\"");
|
||||
string ddpAudio = string.Empty;
|
||||
|
|
Loading…
Reference in New Issue