From 312325ca18d862231999e99b3ee95cef9a768b3e Mon Sep 17 00:00:00 2001 From: nilaoda Date: Sat, 23 Nov 2024 21:52:47 +0800 Subject: [PATCH] =?UTF-8?q?=E9=BB=98=E8=AE=A4=E4=BD=BF=E7=94=A8ffmpeg?= =?UTF-8?q?=E8=BF=9B=E8=A1=8Cmp4=E8=A7=A3=E5=AF=86=20(#504)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE --- src/N_m3u8DL-RE.Common/Resource/ResString.cs | 1 + src/N_m3u8DL-RE.Common/Resource/StaticText.cs | 24 ++++++++----- .../Extractor/HLSExtractor.cs | 2 +- src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs | 4 ++- src/N_m3u8DL-RE/CommandLine/MyOption.cs | 13 +++++++ .../DownloadManager/SimpleDownloadManager.cs | 34 +++++++++---------- .../SimpleLiveRecordManager2.cs | 22 ++++++------ src/N_m3u8DL-RE/Enum/DecryptEngine.cs | 8 +++++ src/N_m3u8DL-RE/Program.cs | 12 +++++-- src/N_m3u8DL-RE/Util/DownloadUtil.cs | 2 +- src/N_m3u8DL-RE/Util/LanguageCodeUtil.cs | 1 + src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs | 29 +++++++++++----- 12 files changed, 102 insertions(+), 50 deletions(-) create mode 100644 src/N_m3u8DL-RE/Enum/DecryptEngine.cs diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.cs b/src/N_m3u8DL-RE.Common/Resource/ResString.cs index 097cb9a..e72fa71 100644 --- a/src/N_m3u8DL-RE.Common/Resource/ResString.cs +++ b/src/N_m3u8DL-RE.Common/Resource/ResString.cs @@ -77,6 +77,7 @@ public class ResString public static string cmd_uiLanguage => GetText("cmd_uiLanguage"); public static string cmd_urlProcessorArgs => GetText("cmd_urlProcessorArgs"); 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_useSystemProxy => GetText("cmd_useSystemProxy"); public static string cmd_customProxy => GetText("cmd_customProxy"); diff --git a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs index d424059..bc7efcc 100644 --- a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs +++ b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs @@ -270,9 +270,9 @@ internal class StaticText ), ["cmd_keys"] = new TextContainer ( - zhCN: "设置解密密钥, 程序调用mp4decrpyt/shaka-packager进行解密. 格式:\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", - 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." + zhCN: "设置解密密钥, 程序调用mp4decrpyt/shaka-packager/ffmpeg进行解密. 格式:\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/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 ( @@ -468,9 +468,15 @@ internal class StaticText ), ["cmd_useShakaPackager"] = new TextContainer ( - zhCN: "解密时使用shaka-packager替代mp4decrypt", - zhTW: "解密時使用shaka-packager替代mp4decrypt", - enUS: "Use shaka-packager instead of mp4decrypt to decrypt" + zhCN: "解密时使用shaka-packager替代ffmpeg", + zhTW: "解密時使用shaka-packager替代ffmpeg", + 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 ( @@ -744,9 +750,9 @@ internal class StaticText ), ["realTimeDecMessage"] = new TextContainer ( - zhCN: "启用实时解密时,建议用shaka-packager而非mp4decrypt", - zhTW: "啟用即時解密時,建議用shaka-packager而非mp4decrypt", - enUS: "When enabling real-time decryption, it is recommended to use shaka-packager instead of mp4decrypt" + zhCN: "启用实时解密时,建议用shaka-packager而非mp4decrypt/ffmpeg", + zhTW: "啟用即時解密時,建議用shaka-packager而非mp4decrypt/ffmpeg", + enUS: "When enabling real-time decryption, it is recommended to use shaka-packager instead of mp4decrypt/ffmpeg" ), ["liveLimitReached"] = new TextContainer ( diff --git a/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs b/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs index 61553a5..b188561 100644 --- a/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs +++ b/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs @@ -368,7 +368,7 @@ internal class HLSExtractor : IExtractor // #EXT-X-MAP else if (line.StartsWith(HLSTags.ext_x_map)) { - if (playlist.MediaInit == null) + if (playlist.MediaInit == null || hasAd) { playlist.MediaInit = new MediaSegment() { diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs index b50c915..f65b56a 100644 --- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs +++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs @@ -54,6 +54,7 @@ internal partial class CommandInvoker private static readonly Option AppendUrlParams = new(["--append-url-params"], description: ResString.cmd_appendUrlParams, getDefaultValue: () => false); private static readonly Option MP4RealTimeDecryption = new (["--mp4-real-time-decryption"], description: ResString.cmd_MP4RealTimeDecryption, getDefaultValue: () => false); private static readonly Option UseShakaPackager = new (["--use-shaka-packager"], description: ResString.cmd_useShakaPackager, getDefaultValue: () => false); + private static readonly Option UseMp4Decrypt = new (["--use-mp4decrypt"], description: ResString.cmd_useMp4decrypt, getDefaultValue: () => false); private static readonly Option ForceAnsiConsole = new(["--force-ansi-console"], description: ResString.cmd_forceAnsiConsole); private static readonly Option NoAnsiColor = new(["--no-ansi-color"], description: ResString.cmd_noAnsiColor); private static readonly Option DecryptionBinaryPath = new(["--decryption-binary-path"], description: ResString.cmd_decryptionBinaryPath) { ArgumentHelpName = "PATH" }; @@ -523,6 +524,7 @@ internal partial class CommandInvoker UrlProcessorArgs = bindingContext.ParseResult.GetValueForOption(UrlProcessorArgs), MP4RealTimeDecryption = bindingContext.ParseResult.GetValueForOption(MP4RealTimeDecryption), UseShakaPackager = bindingContext.ParseResult.GetValueForOption(UseShakaPackager), + UseMp4Decrypt = bindingContext.ParseResult.GetValueForOption(UseMp4Decrypt), DecryptionBinaryPath = bindingContext.ParseResult.GetValueForOption(DecryptionBinaryPath), FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath), 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, BinaryMerge, UseFFmpegConcatDemuxer, DelAfterDone, NoDateInfo, NoLog, WriteMetaJson, AppendUrlParams, ConcurrentDownload, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix, FFmpegBinaryPath, - LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption, + LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, UseMp4Decrypt, MP4RealTimeDecryption, MaxSpeed, MuxAfterDone, CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, CustomRange, TaskStartAt, diff --git a/src/N_m3u8DL-RE/CommandLine/MyOption.cs b/src/N_m3u8DL-RE/CommandLine/MyOption.cs index 4a783d8..8233c56 100644 --- a/src/N_m3u8DL-RE/CommandLine/MyOption.cs +++ b/src/N_m3u8DL-RE/CommandLine/MyOption.cs @@ -141,6 +141,10 @@ internal class MyOption /// public bool UseShakaPackager { get; set; } /// + /// See: . + /// + public bool UseMp4Decrypt { get; set; } + /// /// See: . /// public bool MuxAfterDone { get; set; } @@ -266,4 +270,13 @@ internal class MyOption /// See: . /// public bool LiveFixVttByAudio { get; set; } + + public DecryptEngine GetDecryptEngine() + { + if (UseShakaPackager) + return DecryptEngine.SHAKA_PACKAGE; + if (UseMp4Decrypt) + return DecryptEngine.MP4DECRYPT; + return DecryptEngine.FFMPEG; + } } \ No newline at end of file diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs index abd4c17..b3dfe1e 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs @@ -13,6 +13,7 @@ using N_m3u8DL_RE.Util; using Spectre.Console; using System.Collections.Concurrent; using System.Text; +using N_m3u8DL_RE.Enum; namespace N_m3u8DL_RE.DownloadManager; @@ -39,9 +40,9 @@ internal class SimpleDownloadManager if (_key != null) { if (DownloaderConfig.MyOptions.Keys == null) - DownloaderConfig.MyOptions.Keys = new string[] { _key }; + DownloaderConfig.MyOptions.Keys = [_key]; 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 headers = DownloaderConfig.Headers; - // mp4decrypt - var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!; + var decryptionBinaryPath = DownloaderConfig.MyOptions.DecryptionBinaryPath!; + var decryptEngine = DownloaderConfig.MyOptions.GetDecryptEngine(); var mp4InitFile = ""; var currentKID = ""; var readInfo = false; // 是否读取过 @@ -171,7 +172,7 @@ internal class SimpleDownloadManager currentKID = mp4Info.KID; // try shaka packager, which can handle WebM if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) { - currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt); + currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, decryptionBinaryPath); } // 从文件读取KEY await SearchKeyAsync(currentKID); @@ -180,7 +181,7 @@ internal class SimpleDownloadManager { var enc = result.ActualFilePath; 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) { FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec; @@ -229,7 +230,7 @@ internal class SimpleDownloadManager // 需要重新解密init var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath; 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) { FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec; @@ -243,7 +244,7 @@ internal class SimpleDownloadManager } // try shaka packager, which can handle WebM if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) { - currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt); + currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, decryptionBinaryPath); } // 从文件读取KEY await SearchKeyAsync(currentKID); @@ -253,7 +254,7 @@ internal class SimpleDownloadManager var enc = result.ActualFilePath; var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(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) { File.Delete(enc); @@ -291,7 +292,7 @@ internal class SimpleDownloadManager var enc = result.ActualFilePath; var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(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) { 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 != "") { File.Delete(mp4InitFile); - // shaka实时解密不需要init文件用于合并 - if (DownloaderConfig.MyOptions.UseShakaPackager) + // shaka/ffmpeg实时解密不需要init文件用于合并 + if (decryptEngine != DecryptEngine.MP4DECRYPT) { FileDic!.Remove(streamSpec.Playlist!.MediaInit, out _); } @@ -590,7 +591,7 @@ internal class SimpleDownloadManager currentKID = MP4DecryptUtil.GetMP4Info(output).KID; // try shaka packager, which can handle WebM if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) { - currentKID = MP4DecryptUtil.ReadInitShaka(output, mp4decrypt); + currentKID = MP4DecryptUtil.ReadInitShaka(output, decryptionBinaryPath); } // 从文件读取KEY await SearchKeyAsync(currentKID); @@ -602,8 +603,8 @@ internal class SimpleDownloadManager var enc = output; var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); mp4Info = MP4DecryptUtil.GetMP4Info(enc); - Logger.InfoMarkUp($"[grey]Decrypting...[/]"); - var result = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM); + Logger.InfoMarkUp($"[grey]Decrypting using {decryptEngine}...[/]"); + var result = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM); if (result) { File.Delete(enc); @@ -653,8 +654,7 @@ internal class SimpleDownloadManager } progress.Columns(progressColumns); - if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !DownloaderConfig.MyOptions.UseShakaPackager - && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0) + if (DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, UseShakaPackager: false, Keys.Length: > 0 }) Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]"); await progress.StartAsync(async ctx => diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs index e5c07ef..51eafd8 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs @@ -16,6 +16,7 @@ using System.Collections.Concurrent; using System.IO.Pipes; using System.Text; using System.Threading.Tasks.Dataflow; +using N_m3u8DL_RE.Enum; namespace N_m3u8DL_RE.DownloadManager; @@ -143,8 +144,7 @@ internal class SimpleLiveRecordManager2 private async Task RecordStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer, BufferBlock> source) { var baseTimestamp = PublishDateTime == null ? 0L : (long)(PublishDateTime.Value.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds; - // mp4decrypt - var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!; + var decryptionBinaryPath = DownloaderConfig.MyOptions.DecryptionBinaryPath!; var mp4InitFile = ""; var currentKID = ""; var readInfo = false; // 是否读取过 @@ -164,6 +164,7 @@ internal class SimpleLiveRecordManager2 var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory; var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName; var headers = DownloaderConfig.Headers; + var decryptEngine = DownloaderConfig.MyOptions.GetDecryptEngine(); Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}"); @@ -214,7 +215,7 @@ internal class SimpleLiveRecordManager2 { var enc = result.ActualFilePath; 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) { FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec; @@ -274,7 +275,7 @@ internal class SimpleLiveRecordManager2 // 需要重新解密init var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath; 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) { FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec; @@ -293,7 +294,7 @@ internal class SimpleLiveRecordManager2 { var enc = result.ActualFilePath; 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) { File.Delete(enc); @@ -336,7 +337,7 @@ internal class SimpleLiveRecordManager2 { var enc = result.ActualFilePath; 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) { 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(); if (initResult != null && mp4InitFile != "") { - // shaka实时解密不需要init文件用于合并,mp4decrpyt需要 - if (!DownloaderConfig.MyOptions.UseShakaPackager) + // shaka/ffmpeg实时解密不需要init文件用于合并,mp4decrpyt需要 + if (decryptEngine != DecryptEngine.MP4DECRYPT) { - files = new string[] { initResult.ActualFilePath }.Concat(files).ToArray(); + files = [initResult.ActualFilePath, ..files]; } } foreach (var inputFilePath in files) @@ -839,8 +840,7 @@ internal class SimpleLiveRecordManager2 DownloaderConfig.MyOptions.ConcurrentDownload = true; DownloaderConfig.MyOptions.MP4RealTimeDecryption = true; DownloaderConfig.MyOptions.LiveRecordLimit = DownloaderConfig.MyOptions.LiveRecordLimit ?? TimeSpan.MaxValue; - if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !DownloaderConfig.MyOptions.UseShakaPackager - && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0) + if (DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, UseShakaPackager: false, Keys.Length: > 0 }) Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]"); var limit = DownloaderConfig.MyOptions.LiveRecordLimit; if (limit != TimeSpan.MaxValue) diff --git a/src/N_m3u8DL-RE/Enum/DecryptEngine.cs b/src/N_m3u8DL-RE/Enum/DecryptEngine.cs new file mode 100644 index 0000000..6c01e3e --- /dev/null +++ b/src/N_m3u8DL-RE/Enum/DecryptEngine.cs @@ -0,0 +1,8 @@ +namespace N_m3u8DL_RE.Enum; + +internal enum DecryptEngine +{ + MP4DECRYPT, + SHAKA_PACKAGE, + FFMPEG, +} \ No newline at end of file diff --git a/src/N_m3u8DL-RE/Program.cs b/src/N_m3u8DL-RE/Program.cs index e9493f1..b10a852 100644 --- a/src/N_m3u8DL-RE/Program.cs +++ b/src/N_m3u8DL-RE/Program.cs @@ -105,11 +105,15 @@ internal class Program } // 检查互斥的选项 - if (option is { MuxAfterDone: false, MuxImports.Count: > 0 }) { 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必须开启 if (option is { LivePipeMux: true, LiveRealTimeMerge: false }) @@ -154,13 +158,17 @@ internal class Program option.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4; Logger.Extra($"shaka-packager => {option.DecryptionBinaryPath}"); } - else + else if (option.UseMp4Decrypt) { var file = GlobalUtil.FindExecutable("mp4decrypt"); if (file == null) throw new FileNotFoundException("mp4decrypt not found!"); option.DecryptionBinaryPath = file; Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}"); } + else + { + option.DecryptionBinaryPath = option.FFmpegBinaryPath; + } } else if (!File.Exists(option.DecryptionBinaryPath)) { diff --git a/src/N_m3u8DL-RE/Util/DownloadUtil.cs b/src/N_m3u8DL-RE/Util/DownloadUtil.cs index 63e29e3..0e8bd5b 100644 --- a/src/N_m3u8DL-RE/Util/DownloadUtil.cs +++ b/src/N_m3u8DL-RE/Util/DownloadUtil.cs @@ -24,7 +24,7 @@ internal static class DownloadUtil else { var buffer = new byte[expect]; - await inputStream.ReadAsync(buffer); + _ = await inputStream.ReadAsync(buffer); await outputStream.WriteAsync(buffer, 0, buffer.Length); speedContainer.Add(buffer.Length); } diff --git a/src/N_m3u8DL-RE/Util/LanguageCodeUtil.cs b/src/N_m3u8DL-RE/Util/LanguageCodeUtil.cs index c79876c..8e5546d 100644 --- a/src/N_m3u8DL-RE/Util/LanguageCodeUtil.cs +++ b/src/N_m3u8DL-RE/Util/LanguageCodeUtil.cs @@ -14,6 +14,7 @@ internal static class LanguageCodeUtil { private static readonly List ALL_LANGS = @" +default;und;default;default af;afr;Afrikaans;Afrikaans af-ZA;afr;Afrikaans (South Africa);Afrikaans (South Africa) am;amh;Amharic;Amharic diff --git a/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs b/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs index 9b6bdd6..bada77f 100644 --- a/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs +++ b/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs @@ -1,16 +1,16 @@ using Mp4SubtitleParser; using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Common.Resource; -using N_m3u8DL_RE.Config; using System.Diagnostics; using System.Text.RegularExpressions; +using N_m3u8DL_RE.Enum; namespace N_m3u8DL_RE.Util; internal static class MP4DecryptUtil { private static readonly string ZeroKid = "00000000000000000000000000000000"; - public static async Task DecryptAsync(bool shakaPackager, string bin, string[]? keys, string source, string dest, string? kid, string init = "", bool isMultiDRM=false) + public static async Task 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; @@ -45,27 +45,27 @@ internal static class MP4DecryptUtil if (keyPair == null) return false; - // shakaPackager 无法单独解密init文件 - if (source.EndsWith("_init.mp4") && shakaPackager) return false; + // shakaPackager/ffmpeg 无法单独解密init文件 + if (source.EndsWith("_init.mp4") && decryptEngine != DecryptEngine.MP4DECRYPT) return false; string cmd; var tmpFile = ""; - if (shakaPackager) + if (decryptEngine == DecryptEngine.SHAKA_PACKAGE) { var enc = source; // shakaPackager 手动构造文件 if (init != "") { tmpFile = Path.ChangeExtension(source, ".itmp"); - MergeUtil.CombineMultipleFilesIntoSingleFile(new string[] { init, source }, tmpFile); + MergeUtil.CombineMultipleFilesIntoSingleFile([init, source], tmpFile); enc = tmpFile; } 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]}"; } - else + else if (decryptEngine == DecryptEngine.MP4DECRYPT) { if (trackId == null) { @@ -81,6 +81,19 @@ internal static class MP4DecryptUtil } 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); @@ -153,7 +166,7 @@ internal static class MP4DecryptUtil { using var fs = File.OpenRead(output); var header = new byte[1 * 1024 * 1024]; // 1MB - fs.Read(header); + _ = fs.Read(header); return GetMP4Info(header); }