From 6b7b030a8bfe065fb69d56945d250222a29a9799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=88=9A=28noham=29=C2=B2?= <100566912+NohamR@users.noreply.github.com> Date: Sat, 1 Feb 2025 23:49:33 +0100 Subject: [PATCH] MP4Box support --- README.md | 2 +- src/N_m3u8DL-RE.Common/Resource/ResString.cs | 2 + src/N_m3u8DL-RE.Common/Resource/StaticText.cs | 12 +++++ .../Mp4/MSSMoovProcessor.cs | 15 +++--- src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs | 6 ++- src/N_m3u8DL-RE/CommandLine/MyOption.cs | 4 ++ src/N_m3u8DL-RE/Enum/DecryptEngine.cs | 1 + src/N_m3u8DL-RE/Program.cs | 12 +++++ src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs | 51 ++++++++++++++++++- 9 files changed, 94 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 31f1cd1..f326b51 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Options: --key KID1:KEY1 --key KID2:KEY2 对于KEY相同的情况可以直接输入 --key KEY --key-text-file 设置密钥文件,程序将从文件中按KID搜寻KEY以解密.(不建议使用特大文件) - --decryption-engine 设置解密时使用的第三方程序 [default: MP4DECRYPT] + --decryption-engine 设置解密时使用的第三方程序 [default: MP4DECRYPT] --decryption-binary-path MP4解密所用工具的全路径, 例如 C:\Tools\mp4decrypt.exe --mp4-real-time-decryption 实时解密MP4分片 [default: False] -R, --max-speed 设置限速,单位支持 Mbps 或 Kbps,如:15M 100K diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.cs b/src/N_m3u8DL-RE.Common/Resource/ResString.cs index 39983ae..3749d22 100644 --- a/src/N_m3u8DL-RE.Common/Resource/ResString.cs +++ b/src/N_m3u8DL-RE.Common/Resource/ResString.cs @@ -77,6 +77,7 @@ public static 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_useMP4Box => GetText("cmd_useMP4Box"); public static string cmd_decryptionEngine => GetText("cmd_decryptionEngine"); public static string cmd_concurrentDownload => GetText("cmd_concurrentDownload"); public static string cmd_useSystemProxy => GetText("cmd_useSystemProxy"); @@ -108,6 +109,7 @@ public static class ResString public static string mkvmergeNotFound => GetText("mkvmergeNotFound"); public static string mp4decryptNotFound => GetText("mp4decryptNotFound"); public static string shakaPackagerNotFound => GetText("shakaPackagerNotFound"); + public static string mp4boxNotFound => GetText("mp4boxNotFound"); public static string fixingTTML => GetText("fixingTTML"); public static string fixingTTMLmp4 => GetText("fixingTTMLmp4"); public static string fixingVTT => GetText("fixingVTT"); diff --git a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs index 7b3c259..441ca6f 100644 --- a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs +++ b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs @@ -466,6 +466,12 @@ internal static class StaticText zhTW: "解密時使用shaka-packager替代mp4decrypt", enUS: "Use shaka-packager instead of mp4decrypt to decrypt" ), + ["cmd_useMP4Box"] = new TextContainer + ( + zhCN: "解密时使用MP4Box替代mp4decrypt", + zhTW: "解密時使用MP4Box替代mp4decrypt", + enUS: "Use MP4Box instead of mp4decrypt to decrypt" + ), ["cmd_decryptionEngine"] = new TextContainer ( zhCN: "设置解密时使用的第三方程序", @@ -790,6 +796,12 @@ internal static class StaticText zhTW: "找不到shaka-packager,請自行下載:https://github.com/shaka-project/shaka-packager/releases", enUS: "shaka-packager not found, please download at: https://github.com/shaka-project/shaka-packager/releases" ), + ["mp4boxNotFound"] = new TextContainer + ( + zhCN: "找不到MP4Box,请自行下载:https://github.com/gpac/gpac/", + zhTW: "找不到MP4Box,請自行下載:https://github.com/gpac/gpac/", + enUS: "MP4Box not found, please download at: https://github.com/gpac/gpac/" + ), ["mp4decryptNotFound"] = new TextContainer ( zhCN: "找不到mp4decrypt,请自行下载:https://www.bento4.com/downloads/", diff --git a/src/N_m3u8DL-RE.Parser/Mp4/MSSMoovProcessor.cs b/src/N_m3u8DL-RE.Parser/Mp4/MSSMoovProcessor.cs index 7e16eb8..7512b3f 100644 --- a/src/N_m3u8DL-RE.Parser/Mp4/MSSMoovProcessor.cs +++ b/src/N_m3u8DL-RE.Parser/Mp4/MSSMoovProcessor.cs @@ -36,8 +36,8 @@ public partial class MSSMoovProcessor private bool IsProtection; private string ProtectionSystemId; private string ProtectionData; - private string? ProtecitonKID; - private string? ProtecitonKID_PR; + private string? ProtectionKID; + private string? ProtectionKID_PR; private byte[] UnityMatrix { get @@ -166,17 +166,18 @@ public partial class MSSMoovProcessor var text = Encoding.ASCII.GetString(bytes); var kidBytes = Convert.FromBase64String(KIDRegex().Match(text).Groups[1].Value); // save kid for playready - this.ProtecitonKID_PR = HexUtil.BytesToHex(kidBytes); + this.ProtectionKID_PR = HexUtil.BytesToHex(kidBytes); // fix byte order var reverse1 = new[] { kidBytes[3], kidBytes[2], kidBytes[1], kidBytes[0] }; var reverse2 = new[] { kidBytes[5], kidBytes[4], kidBytes[7], kidBytes[6] }; Array.Copy(reverse1, 0, kidBytes, 0, reverse1.Length); Array.Copy(reverse2, 0, kidBytes, 4, reverse1.Length); - this.ProtecitonKID = HexUtil.BytesToHex(kidBytes); + this.ProtectionKID = HexUtil.BytesToHex(kidBytes); } // widevine else if (ProtectionSystemId.ToUpper() == "EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED") { + // Console.WriteLine($"WV Protection Data: {ProtectionData}"); throw new NotSupportedException(); } } @@ -225,7 +226,7 @@ public partial class MSSMoovProcessor tencPayload.AddRange([0, 0]); tencPayload.Add(0x1); // default_IsProtected tencPayload.Add(0x8); // default_Per_Sample_IV_size - tencPayload.AddRange(HexUtil.HexToBytes(ProtecitonKID)); // default_KID + tencPayload.AddRange(HexUtil.HexToBytes(ProtectionKID)); // default_KID // tencPayload.Add(0x8);// default_constant_IV_size // tencPayload.AddRange(new byte[8]);// default_constant_IV var tencBox = FullBox("tenc", 0, 0, tencPayload.ToArray()); @@ -753,10 +754,10 @@ public partial class MSSMoovProcessor using var _stream = new MemoryStream(); using var _writer = new BinaryWriter2(_stream); var sysIdData = HexUtil.HexToBytes("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed".Replace("-", "")); - // var kid = HexUtil.HexToBytes(ProtecitonKID); + // var kid = HexUtil.HexToBytes(ProtectionKID); _writer.Write(sysIdData); // SystemID 16 bytes - var psshData = HexUtil.HexToBytes($"08011210{ProtecitonKID}1A046E647265220400000000"); + var psshData = HexUtil.HexToBytes($"08011210{ProtectionKID}1A046E647265220400000000"); _writer.WriteUInt(psshData.Length); // Size of Data 4 bytes _writer.Write(psshData); // Data var psshBox = FullBox("pssh", 0, 0, _stream.ToArray()); diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs index 010ef3e..212fc18 100644 --- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs +++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs @@ -17,7 +17,7 @@ namespace N_m3u8DL_RE.CommandLine; internal static partial class CommandInvoker { - public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20241216"; + public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) by noham"; [GeneratedRegex("((best|worst)\\d*|all)")] private static partial Regex ForStrRegex(); @@ -56,6 +56,7 @@ internal static 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) { IsHidden = true }; + private static readonly Option UseMP4Box = new (["--use-mp4box"], description: ResString.cmd_useMP4Box, getDefaultValue: () => false) { IsHidden = true }; private static readonly Option DecryptionEngine = new (["--decryption-engine"], description: ResString.cmd_decryptionEngine, getDefaultValue: () => DecryptEngine.MP4DECRYPT); 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); @@ -525,6 +526,7 @@ internal static partial class CommandInvoker UrlProcessorArgs = bindingContext.ParseResult.GetValueForOption(UrlProcessorArgs), MP4RealTimeDecryption = bindingContext.ParseResult.GetValueForOption(MP4RealTimeDecryption), UseShakaPackager = bindingContext.ParseResult.GetValueForOption(UseShakaPackager), + UseMP4Box = bindingContext.ParseResult.GetValueForOption(UseMP4Box), DecryptionEngine = bindingContext.ParseResult.GetValueForOption(DecryptionEngine), DecryptionBinaryPath = bindingContext.ParseResult.GetValueForOption(DecryptionBinaryPath), FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath), @@ -616,7 +618,7 @@ internal static 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, DecryptionEngine, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption, + LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionEngine, DecryptionBinaryPath, UseShakaPackager, UseMP4Box, 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 a3b78b8..ad303ea 100644 --- a/src/N_m3u8DL-RE/CommandLine/MyOption.cs +++ b/src/N_m3u8DL-RE/CommandLine/MyOption.cs @@ -144,6 +144,10 @@ internal class MyOption /// /// See: . /// + public bool UseMP4Box { get; set; } + /// + /// See: . + /// public DecryptEngine DecryptionEngine { get; set; } /// /// See: . diff --git a/src/N_m3u8DL-RE/Enum/DecryptEngine.cs b/src/N_m3u8DL-RE/Enum/DecryptEngine.cs index 284a9fb..655066d 100644 --- a/src/N_m3u8DL-RE/Enum/DecryptEngine.cs +++ b/src/N_m3u8DL-RE/Enum/DecryptEngine.cs @@ -4,5 +4,6 @@ internal enum DecryptEngine { MP4DECRYPT, SHAKA_PACKAGER, + MP4BOX, FFMPEG, } \ No newline at end of file diff --git a/src/N_m3u8DL-RE/Program.cs b/src/N_m3u8DL-RE/Program.cs index f773cad..f1cc0d4 100644 --- a/src/N_m3u8DL-RE/Program.cs +++ b/src/N_m3u8DL-RE/Program.cs @@ -126,6 +126,10 @@ internal class Program { option.DecryptionEngine = DecryptEngine.SHAKA_PACKAGER; } + if (option.UseMP4Box) + { + option.DecryptionEngine = DecryptEngine.MP4BOX; + } // LivePipeMux开启时 LiveRealTimeMerge必须开启 if (option is { LivePipeMux: true, LiveRealTimeMerge: false }) @@ -184,6 +188,14 @@ internal class Program Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}"); break; } + case DecryptEngine.MP4BOX: + { + var file = GlobalUtil.FindExecutable("MP4Box"); + if (file == null) throw new FileNotFoundException(ResString.mp4boxNotFound); + option.DecryptionBinaryPath = file; + Logger.Extra($"MP4Box => {option.DecryptionBinaryPath}"); + break; + } case DecryptEngine.FFMPEG: default: option.DecryptionBinaryPath = option.FFmpegBinaryPath; diff --git a/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs b/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs index 3097398..1037263 100644 --- a/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs +++ b/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs @@ -51,7 +51,7 @@ internal static partial class MP4DecryptUtil // shakaPackager/ffmpeg 无法单独解密init文件 if (source.EndsWith("_init.mp4") && decryptEngine != DecryptEngine.MP4DECRYPT) return false; - string cmd; + string cmd = ""; var tmpFile = ""; if (decryptEngine == DecryptEngine.SHAKA_PACKAGER) @@ -67,6 +67,55 @@ internal static partial class MP4DecryptUtil 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 if (decryptEngine == DecryptEngine.MP4BOX) + { + // Logger.Info($"decryptEngine: {decryptEngine}"); // decryptEngine: MP4BOX + // Logger.Info($"bin: {bin}"); // bin: /Applications/GPAC.app/Contents/MacOS/MP4Box + // Logger.Info($"keys: {string.Join(", ", keys ?? new string[0])}"); // keys: ccbf5fb4c2965be7aa130ffb3ba9fd73:9cc0c92044cb1d69433f5f5839a159df, 9bf0e9cf0d7b55aeb4b289a63bab8610:90f52fd8ca48717b21d0c2fed7a12ae1, eb676abbcb345e96bbcf616630f1a3da:100b6c20940f779a4589152b57d2dacb, 0294b9599d755de2bbf0fdca3fa5eab7:3bda2f40344c7def614227b9c0f03e26, 639da80cf23b55f3b8cab3f64cfa5df6:229f5f29b643e203004b30c4eaf348f4 + // Logger.Info($"source: {source}"); // source: /Users/noham/Documents/clone/N_m3u8DL-RE/artifact-arm64/11331_2025-02-01_20-04-27.mp4 + // Logger.Info($"dest: {dest}"); // dest: /Users/noham/Documents/clone/N_m3u8DL-RE/artifact-arm64/11331_2025-02-01_20-04-27_dec.mp4 + // Logger.Info($"kid: {kid}"); // kid: eb676abbcb345e96bbcf616630f1a3da + // Logger.Info($"init: {init}"); // init: + // Logger.Info($"isMultiDRM: {isMultiDRM}"); // isMultiDRM: False + // Logger.Info($"trackId: {trackId}"); // trackId: + // Logger.Info($"keyPair: {keyPair}"); // keyPair: eb676abbcb345e96bbcf616630f1a3da:100b6c20940f779a4589152b57d2dacb + // Logger.Info($"tmpEncFile: {tmpEncFile}"); // tmpEncFile: + // Logger.Info($"tmpDecFile: {tmpDecFile}"); // tmpDecFile: + // Logger.Info($"workDir: {workDir}"); // workDir: + // Logger.Info($"tmpFile: {tmpFile}"); // tmpFile: + + var name = Path.GetFileNameWithoutExtension(source); + // Logger.Info($"name: {name}"); + + workDir = Path.GetDirectoryName(source)!; + + var enc = source; + if (init != "") + { + var mergedinitenc = Path.ChangeExtension(source, ".itmp"); + MergeUtil.CombineMultipleFilesIntoSingleFile([init, source], mergedinitenc); + enc = mergedinitenc; + } + // Logger.Info($"enc: {enc}"); + + var xmlfile = Path.Combine(workDir, $"{name}_drm.xml.itmp"); + tmpFile = xmlfile; + var xmlContent = $""" + + + + + + + """; + File.WriteAllText(xmlfile, xmlContent); + + // Logger.Info($"xmlfile: {xmlfile}"); + // Logger.Info($"Decrypting {Path.GetFileName(enc)} using MP4Box"); + cmd = $"-decrypt \"{xmlfile}\" \"{enc}\" -out \"{dest}\" -logs=all@error"; // f"MP4Box -decrypt {xmlfile} {file} -out {file} -logs=all@error" + + } else if (decryptEngine == DecryptEngine.MP4DECRYPT) {