parent
e8e92b6337
commit
312325ca18
|
@ -77,6 +77,7 @@ public class ResString
|
||||||
public static string cmd_uiLanguage => GetText("cmd_uiLanguage");
|
public static string cmd_uiLanguage => GetText("cmd_uiLanguage");
|
||||||
public static string cmd_urlProcessorArgs => GetText("cmd_urlProcessorArgs");
|
public static string cmd_urlProcessorArgs => GetText("cmd_urlProcessorArgs");
|
||||||
public static string cmd_useShakaPackager => GetText("cmd_useShakaPackager");
|
public static string cmd_useShakaPackager => GetText("cmd_useShakaPackager");
|
||||||
|
public static string cmd_useMp4decrypt => GetText("cmd_useMp4decrypt");
|
||||||
public static string cmd_concurrentDownload => GetText("cmd_concurrentDownload");
|
public static string cmd_concurrentDownload => GetText("cmd_concurrentDownload");
|
||||||
public static string cmd_useSystemProxy => GetText("cmd_useSystemProxy");
|
public static string cmd_useSystemProxy => GetText("cmd_useSystemProxy");
|
||||||
public static string cmd_customProxy => GetText("cmd_customProxy");
|
public static string cmd_customProxy => GetText("cmd_customProxy");
|
||||||
|
|
|
@ -270,9 +270,9 @@ internal class StaticText
|
||||||
),
|
),
|
||||||
["cmd_keys"] = new TextContainer
|
["cmd_keys"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "设置解密密钥, 程序调用mp4decrpyt/shaka-packager进行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n对于KEY相同的情况可以直接输入 --key KEY",
|
zhCN: "设置解密密钥, 程序调用mp4decrpyt/shaka-packager/ffmpeg进行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n对于KEY相同的情况可以直接输入 --key KEY",
|
||||||
zhTW: "設置解密密鑰, 程序調用mp4decrpyt/shaka-packager進行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n對於KEY相同的情況可以直接輸入 --key KEY",
|
zhTW: "設置解密密鑰, 程序調用mp4decrpyt/shaka-packager/ffmpeg進行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n對於KEY相同的情況可以直接輸入 --key KEY",
|
||||||
enUS: "Set decryption key(s) to mp4decrypt/shaka-packager. format:\r\n--key KID1:KEY1 --key KID2:KEY2\r\nor use --key KEY if all tracks share the same key."
|
enUS: "Set decryption key(s) to mp4decrypt/shaka-packager/ffmpeg. format:\r\n--key KID1:KEY1 --key KID2:KEY2\r\nor use --key KEY if all tracks share the same key."
|
||||||
),
|
),
|
||||||
["cmd_keyText"] = new TextContainer
|
["cmd_keyText"] = new TextContainer
|
||||||
(
|
(
|
||||||
|
@ -468,9 +468,15 @@ internal class StaticText
|
||||||
),
|
),
|
||||||
["cmd_useShakaPackager"] = new TextContainer
|
["cmd_useShakaPackager"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "解密时使用shaka-packager替代mp4decrypt",
|
zhCN: "解密时使用shaka-packager替代ffmpeg",
|
||||||
zhTW: "解密時使用shaka-packager替代mp4decrypt",
|
zhTW: "解密時使用shaka-packager替代ffmpeg",
|
||||||
enUS: "Use shaka-packager instead of mp4decrypt to decrypt"
|
enUS: "Use shaka-packager instead of ffmpeg to decrypt"
|
||||||
|
),
|
||||||
|
["cmd_useMp4decrypt"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "解密时使用mp4decrypt替代ffmpeg",
|
||||||
|
zhTW: "解密時使用mp4decrypt替代ffmpeg",
|
||||||
|
enUS: "Use mp4decrypt instead of ffmpeg to decrypt"
|
||||||
),
|
),
|
||||||
["cmd_concurrentDownload"] = new TextContainer
|
["cmd_concurrentDownload"] = new TextContainer
|
||||||
(
|
(
|
||||||
|
@ -744,9 +750,9 @@ internal class StaticText
|
||||||
),
|
),
|
||||||
["realTimeDecMessage"] = new TextContainer
|
["realTimeDecMessage"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "启用实时解密时,建议用shaka-packager而非mp4decrypt",
|
zhCN: "启用实时解密时,建议用shaka-packager而非mp4decrypt/ffmpeg",
|
||||||
zhTW: "啟用即時解密時,建議用shaka-packager而非mp4decrypt",
|
zhTW: "啟用即時解密時,建議用shaka-packager而非mp4decrypt/ffmpeg",
|
||||||
enUS: "When enabling real-time decryption, it is recommended to use shaka-packager instead of mp4decrypt"
|
enUS: "When enabling real-time decryption, it is recommended to use shaka-packager instead of mp4decrypt/ffmpeg"
|
||||||
),
|
),
|
||||||
["liveLimitReached"] = new TextContainer
|
["liveLimitReached"] = new TextContainer
|
||||||
(
|
(
|
||||||
|
|
|
@ -368,7 +368,7 @@ internal class HLSExtractor : IExtractor
|
||||||
// #EXT-X-MAP
|
// #EXT-X-MAP
|
||||||
else if (line.StartsWith(HLSTags.ext_x_map))
|
else if (line.StartsWith(HLSTags.ext_x_map))
|
||||||
{
|
{
|
||||||
if (playlist.MediaInit == null)
|
if (playlist.MediaInit == null || hasAd)
|
||||||
{
|
{
|
||||||
playlist.MediaInit = new MediaSegment()
|
playlist.MediaInit = new MediaSegment()
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,6 +54,7 @@ internal partial class CommandInvoker
|
||||||
private static readonly Option<bool> AppendUrlParams = new(["--append-url-params"], description: ResString.cmd_appendUrlParams, getDefaultValue: () => false);
|
private static readonly Option<bool> AppendUrlParams = new(["--append-url-params"], description: ResString.cmd_appendUrlParams, getDefaultValue: () => false);
|
||||||
private static readonly Option<bool> MP4RealTimeDecryption = new (["--mp4-real-time-decryption"], description: ResString.cmd_MP4RealTimeDecryption, getDefaultValue: () => false);
|
private static readonly Option<bool> MP4RealTimeDecryption = new (["--mp4-real-time-decryption"], description: ResString.cmd_MP4RealTimeDecryption, getDefaultValue: () => false);
|
||||||
private static readonly Option<bool> UseShakaPackager = new (["--use-shaka-packager"], description: ResString.cmd_useShakaPackager, getDefaultValue: () => false);
|
private static readonly Option<bool> UseShakaPackager = new (["--use-shaka-packager"], description: ResString.cmd_useShakaPackager, getDefaultValue: () => false);
|
||||||
|
private static readonly Option<bool> UseMp4Decrypt = new (["--use-mp4decrypt"], description: ResString.cmd_useMp4decrypt, getDefaultValue: () => false);
|
||||||
private static readonly Option<bool> ForceAnsiConsole = new(["--force-ansi-console"], description: ResString.cmd_forceAnsiConsole);
|
private static readonly Option<bool> ForceAnsiConsole = new(["--force-ansi-console"], description: ResString.cmd_forceAnsiConsole);
|
||||||
private static readonly Option<bool> NoAnsiColor = new(["--no-ansi-color"], description: ResString.cmd_noAnsiColor);
|
private static readonly Option<bool> NoAnsiColor = new(["--no-ansi-color"], description: ResString.cmd_noAnsiColor);
|
||||||
private static readonly Option<string?> DecryptionBinaryPath = new(["--decryption-binary-path"], description: ResString.cmd_decryptionBinaryPath) { ArgumentHelpName = "PATH" };
|
private static readonly Option<string?> DecryptionBinaryPath = new(["--decryption-binary-path"], description: ResString.cmd_decryptionBinaryPath) { ArgumentHelpName = "PATH" };
|
||||||
|
@ -523,6 +524,7 @@ internal partial class CommandInvoker
|
||||||
UrlProcessorArgs = bindingContext.ParseResult.GetValueForOption(UrlProcessorArgs),
|
UrlProcessorArgs = bindingContext.ParseResult.GetValueForOption(UrlProcessorArgs),
|
||||||
MP4RealTimeDecryption = bindingContext.ParseResult.GetValueForOption(MP4RealTimeDecryption),
|
MP4RealTimeDecryption = bindingContext.ParseResult.GetValueForOption(MP4RealTimeDecryption),
|
||||||
UseShakaPackager = bindingContext.ParseResult.GetValueForOption(UseShakaPackager),
|
UseShakaPackager = bindingContext.ParseResult.GetValueForOption(UseShakaPackager),
|
||||||
|
UseMp4Decrypt = bindingContext.ParseResult.GetValueForOption(UseMp4Decrypt),
|
||||||
DecryptionBinaryPath = bindingContext.ParseResult.GetValueForOption(DecryptionBinaryPath),
|
DecryptionBinaryPath = bindingContext.ParseResult.GetValueForOption(DecryptionBinaryPath),
|
||||||
FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath),
|
FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath),
|
||||||
KeyTextFile = bindingContext.ParseResult.GetValueForOption(KeyTextFile),
|
KeyTextFile = bindingContext.ParseResult.GetValueForOption(KeyTextFile),
|
||||||
|
@ -615,7 +617,7 @@ internal partial class CommandInvoker
|
||||||
Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, HttpRequestTimeout, ForceAnsiConsole, NoAnsiColor,AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
|
Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, HttpRequestTimeout, ForceAnsiConsole, NoAnsiColor,AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
|
||||||
BinaryMerge, UseFFmpegConcatDemuxer, 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,
|
FFmpegBinaryPath,
|
||||||
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
|
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, UseMp4Decrypt, MP4RealTimeDecryption,
|
||||||
MaxSpeed,
|
MaxSpeed,
|
||||||
MuxAfterDone,
|
MuxAfterDone,
|
||||||
CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, CustomRange, TaskStartAt,
|
CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, CustomRange, TaskStartAt,
|
||||||
|
|
|
@ -141,6 +141,10 @@ internal class MyOption
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseShakaPackager { get; set; }
|
public bool UseShakaPackager { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// See: <see cref="CommandInvoker.UseMp4Decrypt"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool UseMp4Decrypt { get; set; }
|
||||||
|
/// <summary>
|
||||||
/// See: <see cref="CommandInvoker.MuxAfterDone"/>.
|
/// See: <see cref="CommandInvoker.MuxAfterDone"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool MuxAfterDone { get; set; }
|
public bool MuxAfterDone { get; set; }
|
||||||
|
@ -266,4 +270,13 @@ internal class MyOption
|
||||||
/// See: <see cref="CommandInvoker.LiveFixVttByAudio"/>.
|
/// See: <see cref="CommandInvoker.LiveFixVttByAudio"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool LiveFixVttByAudio { get; set; }
|
public bool LiveFixVttByAudio { get; set; }
|
||||||
|
|
||||||
|
public DecryptEngine GetDecryptEngine()
|
||||||
|
{
|
||||||
|
if (UseShakaPackager)
|
||||||
|
return DecryptEngine.SHAKA_PACKAGE;
|
||||||
|
if (UseMp4Decrypt)
|
||||||
|
return DecryptEngine.MP4DECRYPT;
|
||||||
|
return DecryptEngine.FFMPEG;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -13,6 +13,7 @@ using N_m3u8DL_RE.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using N_m3u8DL_RE.Enum;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.DownloadManager;
|
namespace N_m3u8DL_RE.DownloadManager;
|
||||||
|
|
||||||
|
@ -39,9 +40,9 @@ internal class SimpleDownloadManager
|
||||||
if (_key != null)
|
if (_key != null)
|
||||||
{
|
{
|
||||||
if (DownloaderConfig.MyOptions.Keys == null)
|
if (DownloaderConfig.MyOptions.Keys == null)
|
||||||
DownloaderConfig.MyOptions.Keys = new string[] { _key };
|
DownloaderConfig.MyOptions.Keys = [_key];
|
||||||
else
|
else
|
||||||
DownloaderConfig.MyOptions.Keys = DownloaderConfig.MyOptions.Keys.Concat(new string[] { _key }).ToArray();
|
DownloaderConfig.MyOptions.Keys = [..DownloaderConfig.MyOptions.Keys, _key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,8 +110,8 @@ internal class SimpleDownloadManager
|
||||||
var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
|
var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
|
||||||
var headers = DownloaderConfig.Headers;
|
var headers = DownloaderConfig.Headers;
|
||||||
|
|
||||||
// mp4decrypt
|
var decryptionBinaryPath = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
|
||||||
var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
|
var decryptEngine = DownloaderConfig.MyOptions.GetDecryptEngine();
|
||||||
var mp4InitFile = "";
|
var mp4InitFile = "";
|
||||||
var currentKID = "";
|
var currentKID = "";
|
||||||
var readInfo = false; // 是否读取过
|
var readInfo = false; // 是否读取过
|
||||||
|
@ -171,7 +172,7 @@ internal class SimpleDownloadManager
|
||||||
currentKID = mp4Info.KID;
|
currentKID = mp4Info.KID;
|
||||||
// try shaka packager, which can handle WebM
|
// try shaka packager, which can handle WebM
|
||||||
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
||||||
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt);
|
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, decryptionBinaryPath);
|
||||||
}
|
}
|
||||||
// 从文件读取KEY
|
// 从文件读取KEY
|
||||||
await SearchKeyAsync(currentKID);
|
await SearchKeyAsync(currentKID);
|
||||||
|
@ -180,7 +181,7 @@ internal class SimpleDownloadManager
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
||||||
|
@ -229,7 +230,7 @@ internal class SimpleDownloadManager
|
||||||
// 需要重新解密init
|
// 需要重新解密init
|
||||||
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
|
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec;
|
FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec;
|
||||||
|
@ -243,7 +244,7 @@ internal class SimpleDownloadManager
|
||||||
}
|
}
|
||||||
// try shaka packager, which can handle WebM
|
// try shaka packager, which can handle WebM
|
||||||
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
||||||
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt);
|
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, decryptionBinaryPath);
|
||||||
}
|
}
|
||||||
// 从文件读取KEY
|
// 从文件读取KEY
|
||||||
await SearchKeyAsync(currentKID);
|
await SearchKeyAsync(currentKID);
|
||||||
|
@ -253,7 +254,7 @@ internal class SimpleDownloadManager
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
mp4Info = MP4DecryptUtil.GetMP4Info(enc);
|
mp4Info = MP4DecryptUtil.GetMP4Info(enc);
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
File.Delete(enc);
|
File.Delete(enc);
|
||||||
|
@ -291,7 +292,7 @@ internal class SimpleDownloadManager
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
mp4Info = MP4DecryptUtil.GetMP4Info(enc);
|
mp4Info = MP4DecryptUtil.GetMP4Info(enc);
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
File.Delete(enc);
|
File.Delete(enc);
|
||||||
|
@ -322,8 +323,8 @@ internal class SimpleDownloadManager
|
||||||
if (!string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0 && mp4InitFile != "")
|
if (!string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0 && mp4InitFile != "")
|
||||||
{
|
{
|
||||||
File.Delete(mp4InitFile);
|
File.Delete(mp4InitFile);
|
||||||
// shaka实时解密不需要init文件用于合并
|
// shaka/ffmpeg实时解密不需要init文件用于合并
|
||||||
if (DownloaderConfig.MyOptions.UseShakaPackager)
|
if (decryptEngine != DecryptEngine.MP4DECRYPT)
|
||||||
{
|
{
|
||||||
FileDic!.Remove(streamSpec.Playlist!.MediaInit, out _);
|
FileDic!.Remove(streamSpec.Playlist!.MediaInit, out _);
|
||||||
}
|
}
|
||||||
|
@ -590,7 +591,7 @@ internal class SimpleDownloadManager
|
||||||
currentKID = MP4DecryptUtil.GetMP4Info(output).KID;
|
currentKID = MP4DecryptUtil.GetMP4Info(output).KID;
|
||||||
// try shaka packager, which can handle WebM
|
// try shaka packager, which can handle WebM
|
||||||
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
||||||
currentKID = MP4DecryptUtil.ReadInitShaka(output, mp4decrypt);
|
currentKID = MP4DecryptUtil.ReadInitShaka(output, decryptionBinaryPath);
|
||||||
}
|
}
|
||||||
// 从文件读取KEY
|
// 从文件读取KEY
|
||||||
await SearchKeyAsync(currentKID);
|
await SearchKeyAsync(currentKID);
|
||||||
|
@ -602,8 +603,8 @@ internal class SimpleDownloadManager
|
||||||
var enc = output;
|
var enc = output;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
mp4Info = MP4DecryptUtil.GetMP4Info(enc);
|
mp4Info = MP4DecryptUtil.GetMP4Info(enc);
|
||||||
Logger.InfoMarkUp($"[grey]Decrypting...[/]");
|
Logger.InfoMarkUp($"[grey]Decrypting using {decryptEngine}...[/]");
|
||||||
var result = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
|
var result = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
File.Delete(enc);
|
File.Delete(enc);
|
||||||
|
@ -653,8 +654,7 @@ internal class SimpleDownloadManager
|
||||||
}
|
}
|
||||||
progress.Columns(progressColumns);
|
progress.Columns(progressColumns);
|
||||||
|
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !DownloaderConfig.MyOptions.UseShakaPackager
|
if (DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, UseShakaPackager: false, Keys.Length: > 0 })
|
||||||
&& DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0)
|
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
|
||||||
|
|
||||||
await progress.StartAsync(async ctx =>
|
await progress.StartAsync(async ctx =>
|
||||||
|
|
|
@ -16,6 +16,7 @@ using System.Collections.Concurrent;
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks.Dataflow;
|
using System.Threading.Tasks.Dataflow;
|
||||||
|
using N_m3u8DL_RE.Enum;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.DownloadManager;
|
namespace N_m3u8DL_RE.DownloadManager;
|
||||||
|
|
||||||
|
@ -143,8 +144,7 @@ internal class SimpleLiveRecordManager2
|
||||||
private async Task<bool> RecordStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer, BufferBlock<List<MediaSegment>> source)
|
private async Task<bool> RecordStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer, BufferBlock<List<MediaSegment>> source)
|
||||||
{
|
{
|
||||||
var baseTimestamp = PublishDateTime == null ? 0L : (long)(PublishDateTime.Value.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
|
var baseTimestamp = PublishDateTime == null ? 0L : (long)(PublishDateTime.Value.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
|
||||||
// mp4decrypt
|
var decryptionBinaryPath = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
|
||||||
var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
|
|
||||||
var mp4InitFile = "";
|
var mp4InitFile = "";
|
||||||
var currentKID = "";
|
var currentKID = "";
|
||||||
var readInfo = false; // 是否读取过
|
var readInfo = false; // 是否读取过
|
||||||
|
@ -164,6 +164,7 @@ internal class SimpleLiveRecordManager2
|
||||||
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
||||||
var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
|
var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
|
||||||
var headers = DownloaderConfig.Headers;
|
var headers = DownloaderConfig.Headers;
|
||||||
|
var decryptEngine = DownloaderConfig.MyOptions.GetDecryptEngine();
|
||||||
|
|
||||||
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}");
|
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}");
|
||||||
|
|
||||||
|
@ -214,7 +215,7 @@ internal class SimpleLiveRecordManager2
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
||||||
|
@ -274,7 +275,7 @@ internal class SimpleLiveRecordManager2
|
||||||
// 需要重新解密init
|
// 需要重新解密init
|
||||||
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
|
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec;
|
FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec;
|
||||||
|
@ -293,7 +294,7 @@ internal class SimpleLiveRecordManager2
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
File.Delete(enc);
|
File.Delete(enc);
|
||||||
|
@ -336,7 +337,7 @@ internal class SimpleLiveRecordManager2
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
File.Delete(enc);
|
File.Delete(enc);
|
||||||
|
@ -554,10 +555,10 @@ internal class SimpleLiveRecordManager2
|
||||||
var files = FileDic.Where(f => f.Key != streamSpec.Playlist!.MediaInit).OrderBy(s => s.Key.Index).Select(f => f.Value).Select(v => v!.ActualFilePath).ToArray();
|
var files = FileDic.Where(f => f.Key != streamSpec.Playlist!.MediaInit).OrderBy(s => s.Key.Index).Select(f => f.Value).Select(v => v!.ActualFilePath).ToArray();
|
||||||
if (initResult != null && mp4InitFile != "")
|
if (initResult != null && mp4InitFile != "")
|
||||||
{
|
{
|
||||||
// shaka实时解密不需要init文件用于合并,mp4decrpyt需要
|
// shaka/ffmpeg实时解密不需要init文件用于合并,mp4decrpyt需要
|
||||||
if (!DownloaderConfig.MyOptions.UseShakaPackager)
|
if (decryptEngine != DecryptEngine.MP4DECRYPT)
|
||||||
{
|
{
|
||||||
files = new string[] { initResult.ActualFilePath }.Concat(files).ToArray();
|
files = [initResult.ActualFilePath, ..files];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var inputFilePath in files)
|
foreach (var inputFilePath in files)
|
||||||
|
@ -839,8 +840,7 @@ internal class SimpleLiveRecordManager2
|
||||||
DownloaderConfig.MyOptions.ConcurrentDownload = true;
|
DownloaderConfig.MyOptions.ConcurrentDownload = true;
|
||||||
DownloaderConfig.MyOptions.MP4RealTimeDecryption = true;
|
DownloaderConfig.MyOptions.MP4RealTimeDecryption = true;
|
||||||
DownloaderConfig.MyOptions.LiveRecordLimit = DownloaderConfig.MyOptions.LiveRecordLimit ?? TimeSpan.MaxValue;
|
DownloaderConfig.MyOptions.LiveRecordLimit = DownloaderConfig.MyOptions.LiveRecordLimit ?? TimeSpan.MaxValue;
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !DownloaderConfig.MyOptions.UseShakaPackager
|
if (DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, UseShakaPackager: false, Keys.Length: > 0 })
|
||||||
&& DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0)
|
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
|
||||||
var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
|
var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
|
||||||
if (limit != TimeSpan.MaxValue)
|
if (limit != TimeSpan.MaxValue)
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace N_m3u8DL_RE.Enum;
|
||||||
|
|
||||||
|
internal enum DecryptEngine
|
||||||
|
{
|
||||||
|
MP4DECRYPT,
|
||||||
|
SHAKA_PACKAGE,
|
||||||
|
FFMPEG,
|
||||||
|
}
|
|
@ -105,11 +105,15 @@ internal class Program
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查互斥的选项
|
// 检查互斥的选项
|
||||||
|
|
||||||
if (option is { MuxAfterDone: false, MuxImports.Count: > 0 })
|
if (option is { MuxAfterDone: false, MuxImports.Count: > 0 })
|
||||||
{
|
{
|
||||||
throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!");
|
throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (option is { UseShakaPackager: true, UseMp4Decrypt: true })
|
||||||
|
{
|
||||||
|
throw new ArgumentException("UseShakaPackager and UseMp4Decrypt cannot be enabled simultaneously!");
|
||||||
|
}
|
||||||
|
|
||||||
// LivePipeMux开启时 LiveRealTimeMerge必须开启
|
// LivePipeMux开启时 LiveRealTimeMerge必须开启
|
||||||
if (option is { LivePipeMux: true, LiveRealTimeMerge: false })
|
if (option is { LivePipeMux: true, LiveRealTimeMerge: false })
|
||||||
|
@ -154,13 +158,17 @@ internal class Program
|
||||||
option.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4;
|
option.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4;
|
||||||
Logger.Extra($"shaka-packager => {option.DecryptionBinaryPath}");
|
Logger.Extra($"shaka-packager => {option.DecryptionBinaryPath}");
|
||||||
}
|
}
|
||||||
else
|
else if (option.UseMp4Decrypt)
|
||||||
{
|
{
|
||||||
var file = GlobalUtil.FindExecutable("mp4decrypt");
|
var file = GlobalUtil.FindExecutable("mp4decrypt");
|
||||||
if (file == null) throw new FileNotFoundException("mp4decrypt not found!");
|
if (file == null) throw new FileNotFoundException("mp4decrypt not found!");
|
||||||
option.DecryptionBinaryPath = file;
|
option.DecryptionBinaryPath = file;
|
||||||
Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}");
|
Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
option.DecryptionBinaryPath = option.FFmpegBinaryPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!File.Exists(option.DecryptionBinaryPath))
|
else if (!File.Exists(option.DecryptionBinaryPath))
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,7 +24,7 @@ internal static class DownloadUtil
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var buffer = new byte[expect];
|
var buffer = new byte[expect];
|
||||||
await inputStream.ReadAsync(buffer);
|
_ = await inputStream.ReadAsync(buffer);
|
||||||
await outputStream.WriteAsync(buffer, 0, buffer.Length);
|
await outputStream.WriteAsync(buffer, 0, buffer.Length);
|
||||||
speedContainer.Add(buffer.Length);
|
speedContainer.Add(buffer.Length);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ internal static class LanguageCodeUtil
|
||||||
{
|
{
|
||||||
|
|
||||||
private static readonly List<Language> ALL_LANGS = @"
|
private static readonly List<Language> ALL_LANGS = @"
|
||||||
|
default;und;default;default
|
||||||
af;afr;Afrikaans;Afrikaans
|
af;afr;Afrikaans;Afrikaans
|
||||||
af-ZA;afr;Afrikaans (South Africa);Afrikaans (South Africa)
|
af-ZA;afr;Afrikaans (South Africa);Afrikaans (South Africa)
|
||||||
am;amh;Amharic;Amharic
|
am;amh;Amharic;Amharic
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
using Mp4SubtitleParser;
|
using Mp4SubtitleParser;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Config;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using N_m3u8DL_RE.Enum;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util;
|
namespace N_m3u8DL_RE.Util;
|
||||||
|
|
||||||
internal static class MP4DecryptUtil
|
internal static class MP4DecryptUtil
|
||||||
{
|
{
|
||||||
private static readonly string ZeroKid = "00000000000000000000000000000000";
|
private static readonly string ZeroKid = "00000000000000000000000000000000";
|
||||||
public static async Task<bool> DecryptAsync(bool shakaPackager, string bin, string[]? keys, string source, string dest, string? kid, string init = "", bool isMultiDRM=false)
|
public static async Task<bool> DecryptAsync(DecryptEngine decryptEngine, string bin, string[]? keys, string source, string dest, string? kid, string init = "", bool isMultiDRM=false)
|
||||||
{
|
{
|
||||||
if (keys == null || keys.Length == 0) return false;
|
if (keys == null || keys.Length == 0) return false;
|
||||||
|
|
||||||
|
@ -45,27 +45,27 @@ internal static class MP4DecryptUtil
|
||||||
|
|
||||||
if (keyPair == null) return false;
|
if (keyPair == null) return false;
|
||||||
|
|
||||||
// shakaPackager 无法单独解密init文件
|
// shakaPackager/ffmpeg 无法单独解密init文件
|
||||||
if (source.EndsWith("_init.mp4") && shakaPackager) return false;
|
if (source.EndsWith("_init.mp4") && decryptEngine != DecryptEngine.MP4DECRYPT) return false;
|
||||||
|
|
||||||
string cmd;
|
string cmd;
|
||||||
|
|
||||||
var tmpFile = "";
|
var tmpFile = "";
|
||||||
if (shakaPackager)
|
if (decryptEngine == DecryptEngine.SHAKA_PACKAGE)
|
||||||
{
|
{
|
||||||
var enc = source;
|
var enc = source;
|
||||||
// shakaPackager 手动构造文件
|
// shakaPackager 手动构造文件
|
||||||
if (init != "")
|
if (init != "")
|
||||||
{
|
{
|
||||||
tmpFile = Path.ChangeExtension(source, ".itmp");
|
tmpFile = Path.ChangeExtension(source, ".itmp");
|
||||||
MergeUtil.CombineMultipleFilesIntoSingleFile(new string[] { init, source }, tmpFile);
|
MergeUtil.CombineMultipleFilesIntoSingleFile([init, source], tmpFile);
|
||||||
enc = tmpFile;
|
enc = tmpFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = $"--quiet --enable_raw_key_decryption input=\"{enc}\",stream=0,output=\"{dest}\" " +
|
cmd = $"--quiet --enable_raw_key_decryption input=\"{enc}\",stream=0,output=\"{dest}\" " +
|
||||||
$"--keys {(trackId != null ? $"label={trackId}:" : "")}key_id={(trackId != null ? ZeroKid : kid)}:key={keyPair.Split(':')[1]}";
|
$"--keys {(trackId != null ? $"label={trackId}:" : "")}key_id={(trackId != null ? ZeroKid : kid)}:key={keyPair.Split(':')[1]}";
|
||||||
}
|
}
|
||||||
else
|
else if (decryptEngine == DecryptEngine.MP4DECRYPT)
|
||||||
{
|
{
|
||||||
if (trackId == null)
|
if (trackId == null)
|
||||||
{
|
{
|
||||||
|
@ -81,6 +81,19 @@ internal static class MP4DecryptUtil
|
||||||
}
|
}
|
||||||
cmd += $" \"{source}\" \"{dest}\"";
|
cmd += $" \"{source}\" \"{dest}\"";
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var enc = source;
|
||||||
|
// ffmpeg实时解密 手动构造文件
|
||||||
|
if (init != "")
|
||||||
|
{
|
||||||
|
tmpFile = Path.ChangeExtension(source, ".itmp");
|
||||||
|
MergeUtil.CombineMultipleFilesIntoSingleFile([init, source], tmpFile);
|
||||||
|
enc = tmpFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = $"-loglevel error -nostdin -decryption_key {keyPair.Split(':')[1]} -i \"{enc}\" -c copy \"{dest}\"";
|
||||||
|
}
|
||||||
|
|
||||||
await RunCommandAsync(bin, cmd);
|
await RunCommandAsync(bin, cmd);
|
||||||
|
|
||||||
|
@ -153,7 +166,7 @@ internal static class MP4DecryptUtil
|
||||||
{
|
{
|
||||||
using var fs = File.OpenRead(output);
|
using var fs = File.OpenRead(output);
|
||||||
var header = new byte[1 * 1024 * 1024]; // 1MB
|
var header = new byte[1 * 1024 * 1024]; // 1MB
|
||||||
fs.Read(header);
|
_ = fs.Read(header);
|
||||||
return GetMP4Info(header);
|
return GetMP4Info(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue