支持调用`shaka-packager`解密
This commit is contained in:
parent
495d8508f5
commit
d6bbde3ca4
|
@ -29,13 +29,12 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||
StartRange == segment.StartRange &&
|
||||
StopRange == segment.StopRange &&
|
||||
ExpectLength == segment.ExpectLength &&
|
||||
EqualityComparer<EncryptInfo>.Default.Equals(EncryptInfo, segment.EncryptInfo) &&
|
||||
Url == segment.Url;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Index, Duration, Title, StartRange, StopRange, ExpectLength, EncryptInfo, Url);
|
||||
return HashCode.Combine(Index, Duration, Title, StartRange, StopRange, ExpectLength, Url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,6 +123,15 @@ namespace N_m3u8DL_RE.Common.Resource {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Full path to the tool used for MP4 decryption, like C:\Tools\mp4decrypt.exe 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string cmd_decryptionBinaryPath {
|
||||
get {
|
||||
return ResourceManager.GetString("cmd_decryptionBinaryPath", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Delete temporary files when done 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -152,7 +161,7 @@ namespace N_m3u8DL_RE.Common.Resource {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Pass decryption key(s) to mp4decrypt. format:
|
||||
/// 查找类似 Pass decryption key(s) to mp4decrypt/shaka-packager. format:
|
||||
///--key KID1:KEY1 --key KID2:KEY2 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string cmd_keys {
|
||||
|
@ -287,6 +296,15 @@ namespace N_m3u8DL_RE.Common.Resource {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Use shaka-packager instead of mp4decrypt 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string cmd_useShakaPackager {
|
||||
get {
|
||||
return ResourceManager.GetString("cmd_useShakaPackager", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Write meta json after parsed 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
|
@ -231,7 +231,7 @@
|
|||
<value></value>
|
||||
</data>
|
||||
<data name="cmd_keys" xml:space="preserve">
|
||||
<value>Pass decryption key(s) to mp4decrypt. format:
|
||||
<value>Pass decryption key(s) to mp4decrypt/shaka-packager. format:
|
||||
--key KID1:KEY1 --key KID2:KEY2</value>
|
||||
</data>
|
||||
<data name="cmd_urlProcessorArgs" xml:space="preserve">
|
||||
|
@ -240,4 +240,10 @@
|
|||
<data name="cmd_MP4RealTimeDecryption" xml:space="preserve">
|
||||
<value>Decrypt MP4 segments in real time</value>
|
||||
</data>
|
||||
<data name="cmd_useShakaPackager" xml:space="preserve">
|
||||
<value>Use shaka-packager instead of mp4decrypt</value>
|
||||
</data>
|
||||
<data name="cmd_decryptionBinaryPath" xml:space="preserve">
|
||||
<value>Full path to the tool used for MP4 decryption, like C:\Tools\mp4decrypt.exe</value>
|
||||
</data>
|
||||
</root>
|
|
@ -251,7 +251,7 @@
|
|||
<value>设置保存文件命名模板, 支持使用变量</value>
|
||||
</data>
|
||||
<data name="cmd_keys" xml:space="preserve">
|
||||
<value>设置解密密钥, 程序调用mp4decrpyt进行解密. 格式:
|
||||
<value>设置解密密钥, 程序调用mp4decrpyt/shaka-packager进行解密. 格式:
|
||||
--key KID1:KEY1 --key KID2:KEY2</value>
|
||||
</data>
|
||||
<data name="cmd_urlProcessorArgs" xml:space="preserve">
|
||||
|
@ -260,4 +260,10 @@
|
|||
<data name="cmd_MP4RealTimeDecryption" xml:space="preserve">
|
||||
<value>实时解密MP4分片</value>
|
||||
</data>
|
||||
<data name="cmd_useShakaPackager" xml:space="preserve">
|
||||
<value>使用shaka-packager替代mp4decrypt</value>
|
||||
</data>
|
||||
<data name="cmd_decryptionBinaryPath" xml:space="preserve">
|
||||
<value>MP4解密所用工具的全路径, 例如 C:\Tools\mp4decrypt.exe</value>
|
||||
</data>
|
||||
</root>
|
|
@ -228,7 +228,7 @@
|
|||
<value>將輸入Url的Params添加至分片, 對某些網站很有用, 例如 kakao.com</value>
|
||||
</data>
|
||||
<data name="cmd_keys" xml:space="preserve">
|
||||
<value>設置解密密鑰, 程序調用mp4decrpyt進行解密. 格式:
|
||||
<value>設置解密密鑰, 程序調用mp4decrpyt/shaka-packager進行解密. 格式:
|
||||
--key KID1:KEY1 --key KID2:KEY2</value>
|
||||
</data>
|
||||
<data name="cmd_urlProcessorArgs" xml:space="preserve">
|
||||
|
@ -237,4 +237,10 @@
|
|||
<data name="cmd_MP4RealTimeDecryption" xml:space="preserve">
|
||||
<value>實時解密MP4分片</value>
|
||||
</data>
|
||||
<data name="cmd_useShakaPackager" xml:space="preserve">
|
||||
<value>使用shaka-packager替代mp4decrypt</value>
|
||||
</data>
|
||||
<data name="cmd_decryptionBinaryPath" xml:space="preserve">
|
||||
<value>MP4解密所用工具的全路徑, 例如 C:\Tools\mp4decrypt.exe</value>
|
||||
</data>
|
||||
</root>
|
|
@ -46,5 +46,19 @@ namespace N_m3u8DL_RE.Common.Util
|
|||
str = (ts.Hours.ToString("00") == "00" ? "" : ts.Hours.ToString("00") + "h") + ts.Minutes.ToString("00") + "m" + ts.Seconds.ToString("00") + "s";
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 寻找可执行程序
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public static string? FindExecutable(string name)
|
||||
{
|
||||
var fileExt = OperatingSystem.IsWindows() ? ".exe" : "";
|
||||
var searchPath = new[] { Environment.CurrentDirectory, Environment.ProcessPath };
|
||||
var envPath = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ??
|
||||
Array.Empty<string>();
|
||||
return searchPath.Concat(envPath).Select(p => Path.Combine(p, name + fileExt)).FirstOrDefault(File.Exists);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
private readonly static Option<bool> WriteMetaJson = new(new string[] { "--write-meta-json" }, description: ResString.cmd_writeMetaJson, getDefaultValue: () => true);
|
||||
private readonly static Option<bool> AppendUrlParams = new(new string[] { "--append-url-params" }, description: ResString.cmd_appendUrlParams, getDefaultValue: () => false);
|
||||
private readonly static Option<bool> MP4RealTimeDecryption = new (new string[] { "--mp4-real-time-decryption" }, description: ResString.cmd_MP4RealTimeDecryption, getDefaultValue: () => false);
|
||||
private readonly static Option<bool> UseShakaPackager = new (new string[] { "--use-shaka-packager" }, description: ResString.cmd_useShakaPackager, getDefaultValue: () => false);
|
||||
private readonly static Option<string?> DecryptionBinaryPath = new(new string[] { "--decryption-binary-path" }, description: ResString.cmd_decryptionBinaryPath);
|
||||
|
||||
class MyOptionBinder : BinderBase<MyOption>
|
||||
{
|
||||
|
@ -63,6 +65,8 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
Keys = bindingContext.ParseResult.GetValueForOption(Keys),
|
||||
UrlProcessorArgs = bindingContext.ParseResult.GetValueForOption(UrlProcessorArgs),
|
||||
MP4RealTimeDecryption = bindingContext.ParseResult.GetValueForOption(MP4RealTimeDecryption),
|
||||
UseShakaPackager = bindingContext.ParseResult.GetValueForOption(UseShakaPackager),
|
||||
DecryptionBinaryPath = bindingContext.ParseResult.GetValueForOption(DecryptionBinaryPath),
|
||||
};
|
||||
|
||||
//在这里设置语言
|
||||
|
@ -87,11 +91,11 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
|
||||
public static async Task<int> InvokeArgs(string[] args, Func<MyOption, Task> action)
|
||||
{
|
||||
var rootCommand = new RootCommand("N_m3u8DL-RE (Beta version) 20220723")
|
||||
var rootCommand = new RootCommand("N_m3u8DL-RE (Beta version) 20220724")
|
||||
{
|
||||
Input, TmpDir, SaveDir, SaveName, ThreadCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
|
||||
BinaryMerge, DelAfterDone, WriteMetaJson, AppendUrlParams, Keys, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
|
||||
LogLevel, UILanguage, UrlProcessorArgs, MP4RealTimeDecryption
|
||||
BinaryMerge, DelAfterDone, WriteMetaJson, AppendUrlParams, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
|
||||
LogLevel, UILanguage, UrlProcessorArgs, Keys, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption
|
||||
};
|
||||
rootCommand.TreatUnmatchedTokensAsErrors = true;
|
||||
rootCommand.SetHandler(async (myOption) => await action(myOption), new MyOptionBinder());
|
||||
|
|
|
@ -74,6 +74,10 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
/// </summary>
|
||||
public bool MP4RealTimeDecryption { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.UseShakaPackager"/>.
|
||||
/// </summary>
|
||||
public bool UseShakaPackager { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.SubtitleFormat"/>.
|
||||
/// </summary>
|
||||
public SubtitleFormat SubtitleFormat { get; set; }
|
||||
|
@ -97,5 +101,9 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
/// See: <see cref="CommandInvoker.UILanguage"/>.
|
||||
/// </summary>
|
||||
public string? UILanguage { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.DecryptionBinaryPath"/>.
|
||||
/// </summary>
|
||||
public string? DecryptionBinaryPath { get; set; }
|
||||
}
|
||||
}
|
|
@ -28,6 +28,8 @@ namespace N_m3u8DL_RE.Config
|
|||
SavePattern = option.SavePattern;
|
||||
Keys = option.Keys;
|
||||
MP4RealTimeDecryption = option.MP4RealTimeDecryption;
|
||||
UseShakaPackager = option.UseShakaPackager;
|
||||
DecryptionBinaryPath = option.DecryptionBinaryPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -79,6 +81,14 @@ namespace N_m3u8DL_RE.Config
|
|||
/// </summary>
|
||||
public bool MP4RealTimeDecryption { get; set; } = true;
|
||||
/// <summary>
|
||||
/// 使用shaka-packager替代mp4decrypt
|
||||
/// </summary>
|
||||
public bool UseShakaPackager { get; set; }
|
||||
/// <summary>
|
||||
/// MP4解密所用工具的全路径
|
||||
/// </summary>
|
||||
public string? DecryptionBinaryPath { get; set; }
|
||||
/// <summary>
|
||||
/// 字幕格式
|
||||
/// </summary>
|
||||
public SubtitleFormat SubtitleFormat { get; set; } = SubtitleFormat.VTT;
|
||||
|
|
|
@ -52,13 +52,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
var output = Path.Combine(saveDir, saveName + $".{streamSpec.Extension ?? "ts"}");
|
||||
|
||||
//mp4decrypt
|
||||
var APP_DIR = Path.GetDirectoryName(Environment.ProcessPath)!;
|
||||
var fileName = "mp4decrypt";
|
||||
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||
fileName += ".exe";
|
||||
var mp4decrypt = Path.Combine(APP_DIR, fileName);
|
||||
if (!File.Exists(mp4decrypt)) mp4decrypt = fileName;
|
||||
var mp4decrypt = DownloaderConfig.DecryptionBinaryPath!;
|
||||
var mp4InitFile = "";
|
||||
var mp4InitFileDec = "";
|
||||
var currentKID = "";
|
||||
|
||||
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}; output: {output}");
|
||||
|
||||
|
@ -106,15 +103,18 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
if (info.Scheme != null) Logger.WarnMarkUp($"[grey]Type: {info.Scheme}[/]");
|
||||
if (info.PSSH != null) Logger.WarnMarkUp($"[grey]PSSH(WV): {info.PSSH}[/]");
|
||||
if (info.KID != null) Logger.WarnMarkUp($"[grey]KID: {info.KID}[/]");
|
||||
currentKID = info.KID;
|
||||
//实时解密
|
||||
if (DownloaderConfig.MP4RealTimeDecryption && streamSpec.Playlist.MediaInit.EncryptInfo.Method != Common.Enum.EncryptMethod.NONE)
|
||||
{
|
||||
var enc = result.ActualFilePath;
|
||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||
var dResult = await MP4DecryptUtil.DecryptAsync(mp4decrypt, DownloaderConfig.Keys, enc, dec);
|
||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.UseShakaPackager, mp4decrypt, DownloaderConfig.Keys, enc, dec, currentKID);
|
||||
if (dResult)
|
||||
{
|
||||
result.ActualFilePath = dec;
|
||||
//实时解密不需要init文件用于合并
|
||||
FileDic!.Remove(streamSpec.Playlist.MediaInit, out _);
|
||||
mp4InitFileDec = dec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
{
|
||||
var enc = result.ActualFilePath;
|
||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||
var dResult = await MP4DecryptUtil.DecryptAsync(mp4decrypt, DownloaderConfig.Keys, enc, dec, mp4InitFile);
|
||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.UseShakaPackager, mp4decrypt, DownloaderConfig.Keys, enc, dec, currentKID, mp4InitFile);
|
||||
if (dResult)
|
||||
{
|
||||
File.Delete(enc);
|
||||
|
@ -150,6 +150,8 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
if (DownloaderConfig.MP4RealTimeDecryption && mp4InitFile != "")
|
||||
{
|
||||
File.Delete(mp4InitFile);
|
||||
if (mp4InitFileDec != "")
|
||||
File.Delete(mp4InitFileDec);
|
||||
}
|
||||
|
||||
//校验分片数量
|
||||
|
@ -345,7 +347,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
|
||||
//删除临时文件夹
|
||||
if (DownloaderConfig.DelAfterDone)
|
||||
if (!DownloaderConfig.SkipMerge && DownloaderConfig.DelAfterDone)
|
||||
{
|
||||
var files = FileDic.Values.Select(v => v!.ActualFilePath);
|
||||
foreach (var file in files)
|
||||
|
@ -359,14 +361,14 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
|
||||
//调用mp4decrypt解密
|
||||
if (!DownloaderConfig.MP4RealTimeDecryption && DownloaderConfig.Keys != null && DownloaderConfig.Keys.Length > 0)
|
||||
if (File.Exists(output) && !DownloaderConfig.MP4RealTimeDecryption && DownloaderConfig.Keys != null && DownloaderConfig.Keys.Length > 0)
|
||||
{
|
||||
if (totalCount > 1 && streamSpec.Playlist!.MediaParts.First().MediaSegments.First().EncryptInfo.Method != Common.Enum.EncryptMethod.NONE)
|
||||
{
|
||||
var enc = output;
|
||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||
Logger.InfoMarkUp($"[grey]Decrypting...[/]");
|
||||
var result = await MP4DecryptUtil.DecryptAsync(mp4decrypt, DownloaderConfig.Keys, enc, dec);
|
||||
var result = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.UseShakaPackager, mp4decrypt, DownloaderConfig.Keys, enc, dec, currentKID);
|
||||
if (result)
|
||||
{
|
||||
File.Delete(enc);
|
||||
|
|
|
@ -38,6 +38,33 @@ namespace N_m3u8DL_RE
|
|||
|
||||
try
|
||||
{
|
||||
//预先检查
|
||||
if (option.Keys != null && option.Keys.Length > 0)
|
||||
{
|
||||
if (string.IsNullOrEmpty(option.DecryptionBinaryPath))
|
||||
{
|
||||
if (option.UseShakaPackager)
|
||||
{
|
||||
var file = GlobalUtil.FindExecutable("shaka-packager");
|
||||
var file2 = GlobalUtil.FindExecutable("packager-linux-x64");
|
||||
var file3 = GlobalUtil.FindExecutable("packager-osx-x64");
|
||||
var file4 = GlobalUtil.FindExecutable("packager-win-x64");
|
||||
if (file == null && file2 == null && file3 == null && file4 == null) throw new FileNotFoundException("shaka-packager not found!");
|
||||
option.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4;
|
||||
}
|
||||
else
|
||||
{
|
||||
var file = GlobalUtil.FindExecutable("mp4decrypt");
|
||||
if (file == null) throw new FileNotFoundException("mp4decrypt not found!");
|
||||
option.DecryptionBinaryPath = file;
|
||||
}
|
||||
}
|
||||
else if (!File.Exists(option.DecryptionBinaryPath))
|
||||
{
|
||||
throw new FileNotFoundException(option.DecryptionBinaryPath);
|
||||
}
|
||||
}
|
||||
|
||||
var parserConfig = new ParserConfig()
|
||||
{
|
||||
AppendUrlParams = option.AppendUrlParams,
|
||||
|
|
|
@ -1,41 +1,73 @@
|
|||
using N_m3u8DL_RE.Config;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace N_m3u8DL_RE.Util
|
||||
{
|
||||
internal class MP4DecryptUtil
|
||||
{
|
||||
public static async Task<bool> DecryptAsync(string bin, string[]? keys, string source, string dest, string init = "")
|
||||
public static async Task<bool> DecryptAsync(bool shakaPackager, string bin, string[]? keys, string source, string dest, string? kid, string init = "")
|
||||
{
|
||||
if (keys == null || keys.Length == 0) return false;
|
||||
|
||||
var cmd = string.Join(" ", keys.Select(k => $"--key {k}"));
|
||||
if (init != "")
|
||||
var keyPair = keys.First();
|
||||
if (!string.IsNullOrEmpty(kid))
|
||||
{
|
||||
cmd += $" --fragments-info \"{init}\" ";
|
||||
var test = keys.Where(k => k.StartsWith(kid));
|
||||
if (test.Any()) keyPair = test.First();
|
||||
}
|
||||
cmd += $" \"{source}\" \"{dest}\"";
|
||||
|
||||
await Process.Start(new ProcessStartInfo()
|
||||
if (keyPair == null) return false;
|
||||
|
||||
//shakaPackager 无法单独解密init文件
|
||||
if (source.EndsWith("_init.mp4") && shakaPackager) return true;
|
||||
|
||||
var cmd = "";
|
||||
|
||||
var tmpFile = "";
|
||||
if (shakaPackager)
|
||||
{
|
||||
FileName = bin,
|
||||
Arguments = cmd,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false
|
||||
})!.WaitForExitAsync();
|
||||
var enc = source;
|
||||
//shakaPackager 手动构造文件
|
||||
if (init != "")
|
||||
{
|
||||
tmpFile = Path.ChangeExtension(source, ".itmp");
|
||||
DownloadUtil.CombineMultipleFilesIntoSingleFile(new string[] { init, source }, tmpFile);
|
||||
enc = tmpFile;
|
||||
}
|
||||
|
||||
cmd = $"--enable_raw_key_decryption input=\"{enc}\",stream=0,output=\"{dest}\" " +
|
||||
$"--keys key_id={keyPair.Split(':')[0]}:key={keyPair.Split(':')[1]}";
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd = string.Join(" ", keys.Select(k => $"--key {k}"));
|
||||
if (init != "")
|
||||
{
|
||||
cmd += $" --fragments-info \"{init}\" ";
|
||||
}
|
||||
cmd += $" \"{source}\" \"{dest}\"";
|
||||
}
|
||||
|
||||
await RunCommandAsync(bin, cmd);
|
||||
|
||||
if (File.Exists(dest) && new FileInfo(dest).Length > 0)
|
||||
{
|
||||
if (tmpFile != "" && File.Exists(tmpFile)) File.Delete(tmpFile);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static async Task RunCommandAsync(string name, string arg)
|
||||
{
|
||||
await Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = name,
|
||||
Arguments = arg,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false
|
||||
})!.WaitForExitAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue