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相同的情况可以直接输入 --key 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
--mp4-real-time-decryption 实时解密MP4分片 [default: False]
-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_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");

View File

@ -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/",

View File

@ -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());

View File

@ -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<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> 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<bool> ForceAnsiConsole = new(["--force-ansi-console"], description: ResString.cmd_forceAnsiConsole);
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),
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,

View File

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

View File

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

View File

@ -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;

View File

@ -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 = $"""
<?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)
{