diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.Designer.cs b/src/N_m3u8DL-RE.Common/Resource/ResString.Designer.cs
index 87aa6af..b031a27 100644
--- a/src/N_m3u8DL-RE.Common/Resource/ResString.Designer.cs
+++ b/src/N_m3u8DL-RE.Common/Resource/ResString.Designer.cs
@@ -170,6 +170,15 @@ namespace N_m3u8DL_RE.Common.Resource {
}
}
+ ///
+ /// 查找类似 Decrypt MP4 segments in real time 的本地化字符串。
+ ///
+ public static string cmd_MP4RealTimeDecryption {
+ get {
+ return ResourceManager.GetString("cmd_MP4RealTimeDecryption", resourceCulture);
+ }
+ }
+
///
/// 查找类似 Set output directory 的本地化字符串。
///
diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.resx b/src/N_m3u8DL-RE.Common/Resource/ResString.resx
index 757f282..9fd5184 100644
--- a/src/N_m3u8DL-RE.Common/Resource/ResString.resx
+++ b/src/N_m3u8DL-RE.Common/Resource/ResString.resx
@@ -237,4 +237,7 @@
Give these arguments to the URL Processors.
+
+ Decrypt MP4 segments in real time
+
\ No newline at end of file
diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hans.resx b/src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hans.resx
index 67bac30..2f4b653 100644
--- a/src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hans.resx
+++ b/src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hans.resx
@@ -257,4 +257,7 @@
此字符串将直接传递给URL Processor
+
+ 实时解密MP4分片
+
\ No newline at end of file
diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hant.resx b/src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hant.resx
index c1a51ce..1ab99c0 100644
--- a/src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hant.resx
+++ b/src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hant.resx
@@ -234,4 +234,7 @@
此字符串將直接傳遞給URL Processor
+
+ 實時解密MP4分片
+
\ No newline at end of file
diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs
index 03e33d8..07b1b75 100644
--- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs
+++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs
@@ -12,8 +12,8 @@ namespace N_m3u8DL_RE.CommandLine
{
private readonly static Argument Input = new(name: "input", description: ResString.cmd_Input);
private readonly static Option TmpDir = new(new string[] { "--tmp-dir" }, description: ResString.cmd_tmpDir);
- private readonly static Option SaveDir = new(new string[] { "--save-dir", "-o" }, description: ResString.cmd_saveDir);
- private readonly static Option SaveName = new(new string[] { "--save-name", "-O" }, description: ResString.cmd_saveName);
+ private readonly static Option SaveDir = new(new string[] { "--save-dir" }, description: ResString.cmd_saveDir);
+ private readonly static Option SaveName = new(new string[] { "--save-name" }, description: ResString.cmd_saveName);
private readonly static Option SavePattern = new(new string[] { "--save-pattern" }, description: ResString.cmd_savePattern, getDefaultValue: () => "____");
private readonly static Option UILanguage = new(new string[] { "--ui-language" }, description: ResString.cmd_uiLanguage);
private readonly static Option UrlProcessorArgs = new(new string[] { "--urlprocessor-args" }, description: ResString.cmd_urlProcessorArgs);
@@ -32,6 +32,7 @@ namespace N_m3u8DL_RE.CommandLine
private readonly static Option CheckSegmentsCount = new(new string[] { "--check-segments-count" }, description: ResString.cmd_checkSegmentsCount, getDefaultValue: () => true);
private readonly static Option WriteMetaJson = new(new string[] { "--write-meta-json" }, description: ResString.cmd_writeMetaJson, getDefaultValue: () => true);
private readonly static Option AppendUrlParams = new(new string[] { "--append-url-params" }, description: ResString.cmd_appendUrlParams, getDefaultValue: () => false);
+ private readonly static Option MP4RealTimeDecryption = new (new string[] { "--mp4-real-time-decryption" }, description: ResString.cmd_MP4RealTimeDecryption, getDefaultValue: () => false);
class MyOptionBinder : BinderBase
{
@@ -61,6 +62,7 @@ namespace N_m3u8DL_RE.CommandLine
SavePattern = bindingContext.ParseResult.GetValueForOption(SavePattern),
Keys = bindingContext.ParseResult.GetValueForOption(Keys),
UrlProcessorArgs = bindingContext.ParseResult.GetValueForOption(UrlProcessorArgs),
+ MP4RealTimeDecryption = bindingContext.ParseResult.GetValueForOption(MP4RealTimeDecryption),
};
//在这里设置语言
@@ -85,11 +87,11 @@ namespace N_m3u8DL_RE.CommandLine
public static async Task InvokeArgs(string[] args, Func action)
{
- var rootCommand = new RootCommand("N_m3u8DL-RE (Beta version) 20220721")
+ var rootCommand = new RootCommand("N_m3u8DL-RE (Beta version) 20220723")
{
Input, TmpDir, SaveDir, SaveName, ThreadCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
BinaryMerge, DelAfterDone, WriteMetaJson, AppendUrlParams, Keys, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
- LogLevel, UILanguage, UrlProcessorArgs
+ LogLevel, UILanguage, UrlProcessorArgs, MP4RealTimeDecryption
};
rootCommand.TreatUnmatchedTokensAsErrors = true;
rootCommand.SetHandler(async (myOption) => await action(myOption), new MyOptionBinder());
diff --git a/src/N_m3u8DL-RE/CommandLine/MyOption.cs b/src/N_m3u8DL-RE/CommandLine/MyOption.cs
index 194624f..18da283 100644
--- a/src/N_m3u8DL-RE/CommandLine/MyOption.cs
+++ b/src/N_m3u8DL-RE/CommandLine/MyOption.cs
@@ -70,6 +70,10 @@ namespace N_m3u8DL_RE.CommandLine
///
public bool AppendUrlParams { get; set; }
///
+ /// See: .
+ ///
+ public bool MP4RealTimeDecryption { get; set; }
+ ///
/// See: .
///
public SubtitleFormat SubtitleFormat { get; set; }
diff --git a/src/N_m3u8DL-RE/Config/DownloaderConfig.cs b/src/N_m3u8DL-RE/Config/DownloaderConfig.cs
index 6541d79..f820ad4 100644
--- a/src/N_m3u8DL-RE/Config/DownloaderConfig.cs
+++ b/src/N_m3u8DL-RE/Config/DownloaderConfig.cs
@@ -27,6 +27,7 @@ namespace N_m3u8DL_RE.Config
ThreadCount = option.ThreadCount;
SavePattern = option.SavePattern;
Keys = option.Keys;
+ MP4RealTimeDecryption = option.MP4RealTimeDecryption;
}
///
@@ -74,6 +75,10 @@ namespace N_m3u8DL_RE.Config
///
public bool AutoSubtitleFix { get; set; } = true;
///
+ /// MP4实时解密
+ ///
+ public bool MP4RealTimeDecryption { get; set; } = true;
+ ///
/// 字幕格式
///
public SubtitleFormat SubtitleFormat { get; set; } = SubtitleFormat.VTT;
diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs
index 7ce30b2..3e9cce5 100644
--- a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs
+++ b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs
@@ -51,6 +51,15 @@ namespace N_m3u8DL_RE.DownloadManager
var headers = DownloaderConfig.Headers;
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 mp4InitFile = "";
+
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}; output: {output}");
//创建文件夹
@@ -76,12 +85,20 @@ namespace N_m3u8DL_RE.DownloadManager
var path = Path.Combine(tmpDir, "_init.mp4.tmp");
var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, headers);
FileDic[streamSpec.Playlist.MediaInit] = result;
+ if (result == null)
+ {
+ throw new Exception("Download init file failed!");
+ }
+ mp4InitFile = result.ActualFilePath;
task.Increment(1);
+
//修改输出后缀
if (streamSpec.MediaType == Common.Enum.MediaType.AUDIO)
output = Path.ChangeExtension(output, ".m4a");
else
output = Path.ChangeExtension(output, ".mp4");
+
+ //读取mp4信息
if (result != null && result.Success)
{
var data = File.ReadAllBytes(result.ActualFilePath);
@@ -89,6 +106,17 @@ 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}[/]");
+ //实时解密
+ 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);
+ if (dResult)
+ {
+ result.ActualFilePath = dec;
+ }
+ }
}
}
@@ -105,8 +133,25 @@ namespace N_m3u8DL_RE.DownloadManager
var result = await Downloader.DownloadSegmentAsync(seg, path, headers);
FileDic[seg] = result;
task.Increment(1);
+ //实时解密
+ if (DownloaderConfig.MP4RealTimeDecryption && seg.EncryptInfo.Method != Common.Enum.EncryptMethod.NONE && result != null)
+ {
+ 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);
+ if (dResult)
+ {
+ File.Delete(enc);
+ result.ActualFilePath = dec;
+ }
+ }
});
+ if (DownloaderConfig.MP4RealTimeDecryption && mp4InitFile != "")
+ {
+ File.Delete(mp4InitFile);
+ }
+
//校验分片数量
if (DownloaderConfig.CheckSegmentsCount && FileDic.Values.Any(s => s == null))
{
@@ -313,30 +358,16 @@ namespace N_m3u8DL_RE.DownloadManager
}
}
- if (DownloaderConfig.Keys != null && DownloaderConfig.Keys.Length > 0)
+ //调用mp4decrypt解密
+ if (!DownloaderConfig.MP4RealTimeDecryption && DownloaderConfig.Keys != null && DownloaderConfig.Keys.Length > 0)
{
- 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;
- if (streamSpec.Playlist!.MediaParts.First().MediaSegments.First().EncryptInfo != null
- && streamSpec.Playlist!.MediaParts.First().MediaSegments.First().EncryptInfo.Method != Common.Enum.EncryptMethod.NONE)
+ 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));
- var cmd = string.Join(" ", DownloaderConfig.Keys.Select(k => $"--key {k}")) + $" \"{enc}\" \"{dec}\"";
Logger.InfoMarkUp($"[grey]Decrypting...[/]");
- await Process.Start(new ProcessStartInfo()
- {
- FileName = mp4decrypt,
- Arguments = cmd,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- UseShellExecute = false
- })!.WaitForExitAsync();
- if (File.Exists(dec) && new FileInfo(dec).Length > 0)
+ var result = await MP4DecryptUtil.DecryptAsync(mp4decrypt, DownloaderConfig.Keys, enc, dec);
+ if (result)
{
File.Delete(enc);
output = dec;
diff --git a/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs b/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs
new file mode 100644
index 0000000..79729ad
--- /dev/null
+++ b/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs
@@ -0,0 +1,41 @@
+using N_m3u8DL_RE.Config;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace N_m3u8DL_RE.Util
+{
+ internal class MP4DecryptUtil
+ {
+ public static async Task DecryptAsync(string bin, string[]? keys, string source, string dest, string init = "")
+ {
+ if (keys == null || keys.Length == 0) return false;
+
+ var cmd = string.Join(" ", keys.Select(k => $"--key {k}"));
+ if (init != "")
+ {
+ cmd += $" --fragments-info \"{init}\" ";
+ }
+ cmd += $" \"{source}\" \"{dest}\"";
+
+ await Process.Start(new ProcessStartInfo()
+ {
+ FileName = bin,
+ Arguments = cmd,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false
+ })!.WaitForExitAsync();
+
+ if (File.Exists(dest) && new FileInfo(dest).Length > 0)
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}