MP4Box support

This commit is contained in:
√(noham)² 2025-02-01 23:49:33 +01:00
parent 9a87474411
commit 6b7b030a8b
9 changed files with 94 additions and 11 deletions

View File

@ -68,7 +68,7 @@ Options:
--key KID1:KEY1 --key KID2:KEY2 --key KID1:KEY1 --key KID2:KEY2
对于KEY相同的情况可以直接输入 --key KEY 对于KEY相同的情况可以直接输入 --key KEY
--key-text-file <key-text-file> 设置密钥文件,程序将从文件中按KID搜寻KEY以解密.(不建议使用特大文件) --key-text-file <key-text-file> 设置密钥文件,程序将从文件中按KID搜寻KEY以解密.(不建议使用特大文件)
--decryption-engine <FFMPEG|MP4DECRYPT|SHAKA_PACKAGER> 设置解密时使用的第三方程序 [default: MP4DECRYPT] --decryption-engine <FFMPEG|MP4DECRYPT|SHAKA_PACKAGER|MP4Box> 设置解密时使用的第三方程序 [default: MP4DECRYPT]
--decryption-binary-path <PATH> MP4解密所用工具的全路径, 例如 C:\Tools\mp4decrypt.exe --decryption-binary-path <PATH> MP4解密所用工具的全路径, 例如 C:\Tools\mp4decrypt.exe
--mp4-real-time-decryption 实时解密MP4分片 [default: False] --mp4-real-time-decryption 实时解密MP4分片 [default: False]
-R, --max-speed <SPEED> 设置限速,单位支持 Mbps 或 Kbps15M 100K -R, --max-speed <SPEED> 设置限速,单位支持 Mbps 或 Kbps15M 100K

View File

@ -77,6 +77,7 @@ public static 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_useMP4Box => GetText("cmd_useMP4Box");
public static string cmd_decryptionEngine => GetText("cmd_decryptionEngine"); public static string cmd_decryptionEngine => GetText("cmd_decryptionEngine");
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");
@ -108,6 +109,7 @@ public static class ResString
public static string mkvmergeNotFound => GetText("mkvmergeNotFound"); public static string mkvmergeNotFound => GetText("mkvmergeNotFound");
public static string mp4decryptNotFound => GetText("mp4decryptNotFound"); public static string mp4decryptNotFound => GetText("mp4decryptNotFound");
public static string shakaPackagerNotFound => GetText("shakaPackagerNotFound"); public static string shakaPackagerNotFound => GetText("shakaPackagerNotFound");
public static string mp4boxNotFound => GetText("mp4boxNotFound");
public static string fixingTTML => GetText("fixingTTML"); public static string fixingTTML => GetText("fixingTTML");
public static string fixingTTMLmp4 => GetText("fixingTTMLmp4"); public static string fixingTTMLmp4 => GetText("fixingTTMLmp4");
public static string fixingVTT => GetText("fixingVTT"); public static string fixingVTT => GetText("fixingVTT");

View File

@ -466,6 +466,12 @@ internal static class StaticText
zhTW: "解密時使用shaka-packager替代mp4decrypt", zhTW: "解密時使用shaka-packager替代mp4decrypt",
enUS: "Use shaka-packager instead of mp4decrypt to decrypt" 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 ["cmd_decryptionEngine"] = new TextContainer
( (
zhCN: "设置解密时使用的第三方程序", zhCN: "设置解密时使用的第三方程序",
@ -790,6 +796,12 @@ internal static class StaticText
zhTW: "找不到shaka-packager請自行下載https://github.com/shaka-project/shaka-packager/releases", 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" 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 ["mp4decryptNotFound"] = new TextContainer
( (
zhCN: "找不到mp4decrypt请自行下载https://www.bento4.com/downloads/", zhCN: "找不到mp4decrypt请自行下载https://www.bento4.com/downloads/",

View File

@ -36,8 +36,8 @@ public partial class MSSMoovProcessor
private bool IsProtection; private bool IsProtection;
private string ProtectionSystemId; private string ProtectionSystemId;
private string ProtectionData; private string ProtectionData;
private string? ProtecitonKID; private string? ProtectionKID;
private string? ProtecitonKID_PR; private string? ProtectionKID_PR;
private byte[] UnityMatrix private byte[] UnityMatrix
{ {
get get
@ -166,17 +166,18 @@ public partial class MSSMoovProcessor
var text = Encoding.ASCII.GetString(bytes); var text = Encoding.ASCII.GetString(bytes);
var kidBytes = Convert.FromBase64String(KIDRegex().Match(text).Groups[1].Value); var kidBytes = Convert.FromBase64String(KIDRegex().Match(text).Groups[1].Value);
// save kid for playready // save kid for playready
this.ProtecitonKID_PR = HexUtil.BytesToHex(kidBytes); this.ProtectionKID_PR = HexUtil.BytesToHex(kidBytes);
// fix byte order // fix byte order
var reverse1 = new[] { kidBytes[3], kidBytes[2], kidBytes[1], kidBytes[0] }; var reverse1 = new[] { kidBytes[3], kidBytes[2], kidBytes[1], kidBytes[0] };
var reverse2 = new[] { kidBytes[5], kidBytes[4], kidBytes[7], kidBytes[6] }; var reverse2 = new[] { kidBytes[5], kidBytes[4], kidBytes[7], kidBytes[6] };
Array.Copy(reverse1, 0, kidBytes, 0, reverse1.Length); Array.Copy(reverse1, 0, kidBytes, 0, reverse1.Length);
Array.Copy(reverse2, 0, kidBytes, 4, reverse1.Length); Array.Copy(reverse2, 0, kidBytes, 4, reverse1.Length);
this.ProtecitonKID = HexUtil.BytesToHex(kidBytes); this.ProtectionKID = HexUtil.BytesToHex(kidBytes);
} }
// widevine // widevine
else if (ProtectionSystemId.ToUpper() == "EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED") else if (ProtectionSystemId.ToUpper() == "EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED")
{ {
// Console.WriteLine($"WV Protection Data: {ProtectionData}");
throw new NotSupportedException(); throw new NotSupportedException();
} }
} }
@ -225,7 +226,7 @@ public partial class MSSMoovProcessor
tencPayload.AddRange([0, 0]); tencPayload.AddRange([0, 0]);
tencPayload.Add(0x1); // default_IsProtected tencPayload.Add(0x1); // default_IsProtected
tencPayload.Add(0x8); // default_Per_Sample_IV_size 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.Add(0x8);// default_constant_IV_size
// tencPayload.AddRange(new byte[8]);// default_constant_IV // tencPayload.AddRange(new byte[8]);// default_constant_IV
var tencBox = FullBox("tenc", 0, 0, tencPayload.ToArray()); var tencBox = FullBox("tenc", 0, 0, tencPayload.ToArray());
@ -753,10 +754,10 @@ public partial class MSSMoovProcessor
using var _stream = new MemoryStream(); using var _stream = new MemoryStream();
using var _writer = new BinaryWriter2(_stream); using var _writer = new BinaryWriter2(_stream);
var sysIdData = HexUtil.HexToBytes("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed".Replace("-", "")); 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 _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.WriteUInt(psshData.Length); // Size of Data 4 bytes
_writer.Write(psshData); // Data _writer.Write(psshData); // Data
var psshBox = FullBox("pssh", 0, 0, _stream.ToArray()); var psshBox = FullBox("pssh", 0, 0, _stream.ToArray());

View File

@ -17,7 +17,7 @@ namespace N_m3u8DL_RE.CommandLine;
internal static partial class CommandInvoker 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)")] [GeneratedRegex("((best|worst)\\d*|all)")]
private static partial Regex ForStrRegex(); private static partial Regex ForStrRegex();
@ -56,6 +56,7 @@ internal static 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) { IsHidden = true }; private static readonly Option<bool> UseShakaPackager = new (["--use-shaka-packager"], description: ResString.cmd_useShakaPackager, getDefaultValue: () => false) { IsHidden = true };
private static readonly Option<bool> UseMP4Box = new (["--use-mp4box"], description: ResString.cmd_useMP4Box, getDefaultValue: () => false) { IsHidden = true };
private static readonly Option<DecryptEngine> DecryptionEngine = new (["--decryption-engine"], description: ResString.cmd_decryptionEngine, getDefaultValue: () => DecryptEngine.MP4DECRYPT); private static readonly Option<DecryptEngine> DecryptionEngine = new (["--decryption-engine"], description: ResString.cmd_decryptionEngine, getDefaultValue: () => DecryptEngine.MP4DECRYPT);
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);
@ -525,6 +526,7 @@ internal static 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),
UseMP4Box = bindingContext.ParseResult.GetValueForOption(UseMP4Box),
DecryptionEngine = bindingContext.ParseResult.GetValueForOption(DecryptionEngine), DecryptionEngine = bindingContext.ParseResult.GetValueForOption(DecryptionEngine),
DecryptionBinaryPath = bindingContext.ParseResult.GetValueForOption(DecryptionBinaryPath), DecryptionBinaryPath = bindingContext.ParseResult.GetValueForOption(DecryptionBinaryPath),
FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath), 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, 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, DecryptionEngine, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption, LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionEngine, DecryptionBinaryPath, UseShakaPackager, UseMP4Box, MP4RealTimeDecryption,
MaxSpeed, MaxSpeed,
MuxAfterDone, MuxAfterDone,
CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, CustomRange, TaskStartAt, CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, CustomRange, TaskStartAt,

View File

@ -144,6 +144,10 @@ internal class MyOption
/// <summary> /// <summary>
/// See: <see cref="CommandInvoker.DecryptionEngine"/>. /// See: <see cref="CommandInvoker.DecryptionEngine"/>.
/// </summary> /// </summary>
public bool UseMP4Box { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.DecryptionEngine"/>.
/// </summary>
public DecryptEngine DecryptionEngine { get; set; } public DecryptEngine DecryptionEngine { get; set; }
/// <summary> /// <summary>
/// See: <see cref="CommandInvoker.MuxAfterDone"/>. /// See: <see cref="CommandInvoker.MuxAfterDone"/>.

View File

@ -4,5 +4,6 @@ internal enum DecryptEngine
{ {
MP4DECRYPT, MP4DECRYPT,
SHAKA_PACKAGER, SHAKA_PACKAGER,
MP4BOX,
FFMPEG, FFMPEG,
} }

View File

@ -126,6 +126,10 @@ internal class Program
{ {
option.DecryptionEngine = DecryptEngine.SHAKA_PACKAGER; option.DecryptionEngine = DecryptEngine.SHAKA_PACKAGER;
} }
if (option.UseMP4Box)
{
option.DecryptionEngine = DecryptEngine.MP4BOX;
}
// LivePipeMux开启时 LiveRealTimeMerge必须开启 // LivePipeMux开启时 LiveRealTimeMerge必须开启
if (option is { LivePipeMux: true, LiveRealTimeMerge: false }) if (option is { LivePipeMux: true, LiveRealTimeMerge: false })
@ -184,6 +188,14 @@ internal class Program
Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}"); Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}");
break; 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: case DecryptEngine.FFMPEG:
default: default:
option.DecryptionBinaryPath = option.FFmpegBinaryPath; option.DecryptionBinaryPath = option.FFmpegBinaryPath;

View File

@ -51,7 +51,7 @@ internal static partial class MP4DecryptUtil
// shakaPackager/ffmpeg 无法单独解密init文件 // shakaPackager/ffmpeg 无法单独解密init文件
if (source.EndsWith("_init.mp4") && decryptEngine != DecryptEngine.MP4DECRYPT) return false; if (source.EndsWith("_init.mp4") && decryptEngine != DecryptEngine.MP4DECRYPT) return false;
string cmd; string cmd = "";
var tmpFile = ""; var tmpFile = "";
if (decryptEngine == DecryptEngine.SHAKA_PACKAGER) 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}\" " + 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 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 = $"""
<?xml version="1.0" encoding="UTF-8"?>
<GPACDRM type="CENC">
<CrypTrack trackID="1">
<key KID="{keyPair.Split(':')[0]}" value="{keyPair.Split(':')[1]}"/>
</CrypTrack>
</GPACDRM>
""";
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) else if (decryptEngine == DecryptEngine.MP4DECRYPT)
{ {