默认使用ffmpeg进行mp4解密 (#504)

BREAKING CHANGE
This commit is contained in:
nilaoda 2024-11-23 21:52:47 +08:00
parent e8e92b6337
commit 312325ca18
12 changed files with 102 additions and 50 deletions

View File

@ -77,6 +77,7 @@ public 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_useMp4decrypt => GetText("cmd_useMp4decrypt");
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");
public static string cmd_customProxy => GetText("cmd_customProxy"); public static string cmd_customProxy => GetText("cmd_customProxy");

View File

@ -270,9 +270,9 @@ internal class StaticText
), ),
["cmd_keys"] = new TextContainer ["cmd_keys"] = new TextContainer
( (
zhCN: "设置解密密钥, 程序调用mp4decrpyt/shaka-packager进行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n对于KEY相同的情况可以直接输入 --key KEY", zhCN: "设置解密密钥, 程序调用mp4decrpyt/shaka-packager/ffmpeg进行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n对于KEY相同的情况可以直接输入 --key KEY",
zhTW: "設置解密密鑰, 程序調用mp4decrpyt/shaka-packager進行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n對於KEY相同的情況可以直接輸入 --key KEY", zhTW: "設置解密密鑰, 程序調用mp4decrpyt/shaka-packager/ffmpeg進行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n對於KEY相同的情況可以直接輸入 --key KEY",
enUS: "Set decryption key(s) to mp4decrypt/shaka-packager. format:\r\n--key KID1:KEY1 --key KID2:KEY2\r\nor use --key KEY if all tracks share the same key." enUS: "Set decryption key(s) to mp4decrypt/shaka-packager/ffmpeg. format:\r\n--key KID1:KEY1 --key KID2:KEY2\r\nor use --key KEY if all tracks share the same key."
), ),
["cmd_keyText"] = new TextContainer ["cmd_keyText"] = new TextContainer
( (
@ -468,9 +468,15 @@ internal class StaticText
), ),
["cmd_useShakaPackager"] = new TextContainer ["cmd_useShakaPackager"] = new TextContainer
( (
zhCN: "解密时使用shaka-packager替代mp4decrypt", zhCN: "解密时使用shaka-packager替代ffmpeg",
zhTW: "解密時使用shaka-packager替代mp4decrypt", zhTW: "解密時使用shaka-packager替代ffmpeg",
enUS: "Use shaka-packager instead of mp4decrypt to decrypt" enUS: "Use shaka-packager instead of ffmpeg to decrypt"
),
["cmd_useMp4decrypt"] = new TextContainer
(
zhCN: "解密时使用mp4decrypt替代ffmpeg",
zhTW: "解密時使用mp4decrypt替代ffmpeg",
enUS: "Use mp4decrypt instead of ffmpeg to decrypt"
), ),
["cmd_concurrentDownload"] = new TextContainer ["cmd_concurrentDownload"] = new TextContainer
( (
@ -744,9 +750,9 @@ internal class StaticText
), ),
["realTimeDecMessage"] = new TextContainer ["realTimeDecMessage"] = new TextContainer
( (
zhCN: "启用实时解密时建议用shaka-packager而非mp4decrypt", zhCN: "启用实时解密时建议用shaka-packager而非mp4decrypt/ffmpeg",
zhTW: "啟用即時解密時建議用shaka-packager而非mp4decrypt", zhTW: "啟用即時解密時建議用shaka-packager而非mp4decrypt/ffmpeg",
enUS: "When enabling real-time decryption, it is recommended to use shaka-packager instead of mp4decrypt" enUS: "When enabling real-time decryption, it is recommended to use shaka-packager instead of mp4decrypt/ffmpeg"
), ),
["liveLimitReached"] = new TextContainer ["liveLimitReached"] = new TextContainer
( (

View File

@ -368,7 +368,7 @@ internal class HLSExtractor : IExtractor
// #EXT-X-MAP // #EXT-X-MAP
else if (line.StartsWith(HLSTags.ext_x_map)) else if (line.StartsWith(HLSTags.ext_x_map))
{ {
if (playlist.MediaInit == null) if (playlist.MediaInit == null || hasAd)
{ {
playlist.MediaInit = new MediaSegment() playlist.MediaInit = new MediaSegment()
{ {

View File

@ -54,6 +54,7 @@ internal 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); private static readonly Option<bool> UseShakaPackager = new (["--use-shaka-packager"], description: ResString.cmd_useShakaPackager, getDefaultValue: () => false);
private static readonly Option<bool> UseMp4Decrypt = new (["--use-mp4decrypt"], description: ResString.cmd_useMp4decrypt, getDefaultValue: () => false);
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);
private static readonly Option<string?> DecryptionBinaryPath = new(["--decryption-binary-path"], description: ResString.cmd_decryptionBinaryPath) { ArgumentHelpName = "PATH" }; private static readonly Option<string?> DecryptionBinaryPath = new(["--decryption-binary-path"], description: ResString.cmd_decryptionBinaryPath) { ArgumentHelpName = "PATH" };
@ -523,6 +524,7 @@ internal 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),
UseMp4Decrypt = bindingContext.ParseResult.GetValueForOption(UseMp4Decrypt),
DecryptionBinaryPath = bindingContext.ParseResult.GetValueForOption(DecryptionBinaryPath), DecryptionBinaryPath = bindingContext.ParseResult.GetValueForOption(DecryptionBinaryPath),
FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath), FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath),
KeyTextFile = bindingContext.ParseResult.GetValueForOption(KeyTextFile), KeyTextFile = bindingContext.ParseResult.GetValueForOption(KeyTextFile),
@ -615,7 +617,7 @@ internal 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, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption, LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, UseMp4Decrypt, MP4RealTimeDecryption,
MaxSpeed, MaxSpeed,
MuxAfterDone, MuxAfterDone,
CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, CustomRange, TaskStartAt, CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, CustomRange, TaskStartAt,

View File

@ -141,6 +141,10 @@ internal class MyOption
/// </summary> /// </summary>
public bool UseShakaPackager { get; set; } public bool UseShakaPackager { get; set; }
/// <summary> /// <summary>
/// See: <see cref="CommandInvoker.UseMp4Decrypt"/>.
/// </summary>
public bool UseMp4Decrypt { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.MuxAfterDone"/>. /// See: <see cref="CommandInvoker.MuxAfterDone"/>.
/// </summary> /// </summary>
public bool MuxAfterDone { get; set; } public bool MuxAfterDone { get; set; }
@ -266,4 +270,13 @@ internal class MyOption
/// See: <see cref="CommandInvoker.LiveFixVttByAudio"/>. /// See: <see cref="CommandInvoker.LiveFixVttByAudio"/>.
/// </summary> /// </summary>
public bool LiveFixVttByAudio { get; set; } public bool LiveFixVttByAudio { get; set; }
public DecryptEngine GetDecryptEngine()
{
if (UseShakaPackager)
return DecryptEngine.SHAKA_PACKAGE;
if (UseMp4Decrypt)
return DecryptEngine.MP4DECRYPT;
return DecryptEngine.FFMPEG;
}
} }

View File

@ -13,6 +13,7 @@ using N_m3u8DL_RE.Util;
using Spectre.Console; using Spectre.Console;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Text; using System.Text;
using N_m3u8DL_RE.Enum;
namespace N_m3u8DL_RE.DownloadManager; namespace N_m3u8DL_RE.DownloadManager;
@ -39,9 +40,9 @@ internal class SimpleDownloadManager
if (_key != null) if (_key != null)
{ {
if (DownloaderConfig.MyOptions.Keys == null) if (DownloaderConfig.MyOptions.Keys == null)
DownloaderConfig.MyOptions.Keys = new string[] { _key }; DownloaderConfig.MyOptions.Keys = [_key];
else else
DownloaderConfig.MyOptions.Keys = DownloaderConfig.MyOptions.Keys.Concat(new string[] { _key }).ToArray(); DownloaderConfig.MyOptions.Keys = [..DownloaderConfig.MyOptions.Keys, _key];
} }
} }
@ -109,8 +110,8 @@ internal class SimpleDownloadManager
var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName; var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
var headers = DownloaderConfig.Headers; var headers = DownloaderConfig.Headers;
// mp4decrypt var decryptionBinaryPath = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!; var decryptEngine = DownloaderConfig.MyOptions.GetDecryptEngine();
var mp4InitFile = ""; var mp4InitFile = "";
var currentKID = ""; var currentKID = "";
var readInfo = false; // 是否读取过 var readInfo = false; // 是否读取过
@ -171,7 +172,7 @@ internal class SimpleDownloadManager
currentKID = mp4Info.KID; currentKID = mp4Info.KID;
// try shaka packager, which can handle WebM // try shaka packager, which can handle WebM
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) { if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt); currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, decryptionBinaryPath);
} }
// 从文件读取KEY // 从文件读取KEY
await SearchKeyAsync(currentKID); await SearchKeyAsync(currentKID);
@ -180,7 +181,7 @@ internal class SimpleDownloadManager
{ {
var enc = result.ActualFilePath; var enc = result.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM); var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
if (dResult) if (dResult)
{ {
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec; FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
@ -229,7 +230,7 @@ internal class SimpleDownloadManager
// 需要重新解密init // 需要重新解密init
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath; var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID); var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
if (dResult) if (dResult)
{ {
FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec; FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec;
@ -243,7 +244,7 @@ internal class SimpleDownloadManager
} }
// try shaka packager, which can handle WebM // try shaka packager, which can handle WebM
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) { if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt); currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, decryptionBinaryPath);
} }
// 从文件读取KEY // 从文件读取KEY
await SearchKeyAsync(currentKID); await SearchKeyAsync(currentKID);
@ -253,7 +254,7 @@ internal class SimpleDownloadManager
var enc = result.ActualFilePath; var enc = result.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
mp4Info = MP4DecryptUtil.GetMP4Info(enc); mp4Info = MP4DecryptUtil.GetMP4Info(enc);
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM); var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
if (dResult) if (dResult)
{ {
File.Delete(enc); File.Delete(enc);
@ -291,7 +292,7 @@ internal class SimpleDownloadManager
var enc = result.ActualFilePath; var enc = result.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
mp4Info = MP4DecryptUtil.GetMP4Info(enc); mp4Info = MP4DecryptUtil.GetMP4Info(enc);
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM); var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
if (dResult) if (dResult)
{ {
File.Delete(enc); File.Delete(enc);
@ -322,8 +323,8 @@ internal class SimpleDownloadManager
if (!string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0 && mp4InitFile != "") if (!string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0 && mp4InitFile != "")
{ {
File.Delete(mp4InitFile); File.Delete(mp4InitFile);
// shaka实时解密不需要init文件用于合并 // shaka/ffmpeg实时解密不需要init文件用于合并
if (DownloaderConfig.MyOptions.UseShakaPackager) if (decryptEngine != DecryptEngine.MP4DECRYPT)
{ {
FileDic!.Remove(streamSpec.Playlist!.MediaInit, out _); FileDic!.Remove(streamSpec.Playlist!.MediaInit, out _);
} }
@ -590,7 +591,7 @@ internal class SimpleDownloadManager
currentKID = MP4DecryptUtil.GetMP4Info(output).KID; currentKID = MP4DecryptUtil.GetMP4Info(output).KID;
// try shaka packager, which can handle WebM // try shaka packager, which can handle WebM
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) { if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
currentKID = MP4DecryptUtil.ReadInitShaka(output, mp4decrypt); currentKID = MP4DecryptUtil.ReadInitShaka(output, decryptionBinaryPath);
} }
// 从文件读取KEY // 从文件读取KEY
await SearchKeyAsync(currentKID); await SearchKeyAsync(currentKID);
@ -602,8 +603,8 @@ internal class SimpleDownloadManager
var enc = output; var enc = output;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
mp4Info = MP4DecryptUtil.GetMP4Info(enc); mp4Info = MP4DecryptUtil.GetMP4Info(enc);
Logger.InfoMarkUp($"[grey]Decrypting...[/]"); Logger.InfoMarkUp($"[grey]Decrypting using {decryptEngine}...[/]");
var result = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM); var result = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
if (result) if (result)
{ {
File.Delete(enc); File.Delete(enc);
@ -653,8 +654,7 @@ internal class SimpleDownloadManager
} }
progress.Columns(progressColumns); progress.Columns(progressColumns);
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !DownloaderConfig.MyOptions.UseShakaPackager if (DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, UseShakaPackager: false, Keys.Length: > 0 })
&& DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0)
Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]"); Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
await progress.StartAsync(async ctx => await progress.StartAsync(async ctx =>

View File

@ -16,6 +16,7 @@ using System.Collections.Concurrent;
using System.IO.Pipes; using System.IO.Pipes;
using System.Text; using System.Text;
using System.Threading.Tasks.Dataflow; using System.Threading.Tasks.Dataflow;
using N_m3u8DL_RE.Enum;
namespace N_m3u8DL_RE.DownloadManager; namespace N_m3u8DL_RE.DownloadManager;
@ -143,8 +144,7 @@ internal class SimpleLiveRecordManager2
private async Task<bool> RecordStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer, BufferBlock<List<MediaSegment>> source) private async Task<bool> RecordStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer, BufferBlock<List<MediaSegment>> source)
{ {
var baseTimestamp = PublishDateTime == null ? 0L : (long)(PublishDateTime.Value.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds; var baseTimestamp = PublishDateTime == null ? 0L : (long)(PublishDateTime.Value.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
// mp4decrypt var decryptionBinaryPath = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
var mp4InitFile = ""; var mp4InitFile = "";
var currentKID = ""; var currentKID = "";
var readInfo = false; // 是否读取过 var readInfo = false; // 是否读取过
@ -164,6 +164,7 @@ internal class SimpleLiveRecordManager2
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory; var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName; var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
var headers = DownloaderConfig.Headers; var headers = DownloaderConfig.Headers;
var decryptEngine = DownloaderConfig.MyOptions.GetDecryptEngine();
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}"); Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}");
@ -214,7 +215,7 @@ internal class SimpleLiveRecordManager2
{ {
var enc = result.ActualFilePath; var enc = result.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID); var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
if (dResult) if (dResult)
{ {
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec; FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
@ -274,7 +275,7 @@ internal class SimpleLiveRecordManager2
// 需要重新解密init // 需要重新解密init
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath; var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID); var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
if (dResult) if (dResult)
{ {
FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec; FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec;
@ -293,7 +294,7 @@ internal class SimpleLiveRecordManager2
{ {
var enc = result.ActualFilePath; var enc = result.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile); var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
if (dResult) if (dResult)
{ {
File.Delete(enc); File.Delete(enc);
@ -336,7 +337,7 @@ internal class SimpleLiveRecordManager2
{ {
var enc = result.ActualFilePath; var enc = result.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile); var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
if (dResult) if (dResult)
{ {
File.Delete(enc); File.Delete(enc);
@ -554,10 +555,10 @@ internal class SimpleLiveRecordManager2
var files = FileDic.Where(f => f.Key != streamSpec.Playlist!.MediaInit).OrderBy(s => s.Key.Index).Select(f => f.Value).Select(v => v!.ActualFilePath).ToArray(); var files = FileDic.Where(f => f.Key != streamSpec.Playlist!.MediaInit).OrderBy(s => s.Key.Index).Select(f => f.Value).Select(v => v!.ActualFilePath).ToArray();
if (initResult != null && mp4InitFile != "") if (initResult != null && mp4InitFile != "")
{ {
// shaka实时解密不需要init文件用于合并mp4decrpyt需要 // shaka/ffmpeg实时解密不需要init文件用于合并mp4decrpyt需要
if (!DownloaderConfig.MyOptions.UseShakaPackager) if (decryptEngine != DecryptEngine.MP4DECRYPT)
{ {
files = new string[] { initResult.ActualFilePath }.Concat(files).ToArray(); files = [initResult.ActualFilePath, ..files];
} }
} }
foreach (var inputFilePath in files) foreach (var inputFilePath in files)
@ -839,8 +840,7 @@ internal class SimpleLiveRecordManager2
DownloaderConfig.MyOptions.ConcurrentDownload = true; DownloaderConfig.MyOptions.ConcurrentDownload = true;
DownloaderConfig.MyOptions.MP4RealTimeDecryption = true; DownloaderConfig.MyOptions.MP4RealTimeDecryption = true;
DownloaderConfig.MyOptions.LiveRecordLimit = DownloaderConfig.MyOptions.LiveRecordLimit ?? TimeSpan.MaxValue; DownloaderConfig.MyOptions.LiveRecordLimit = DownloaderConfig.MyOptions.LiveRecordLimit ?? TimeSpan.MaxValue;
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !DownloaderConfig.MyOptions.UseShakaPackager if (DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, UseShakaPackager: false, Keys.Length: > 0 })
&& DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0)
Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]"); Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
var limit = DownloaderConfig.MyOptions.LiveRecordLimit; var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
if (limit != TimeSpan.MaxValue) if (limit != TimeSpan.MaxValue)

View File

@ -0,0 +1,8 @@
namespace N_m3u8DL_RE.Enum;
internal enum DecryptEngine
{
MP4DECRYPT,
SHAKA_PACKAGE,
FFMPEG,
}

View File

@ -105,12 +105,16 @@ internal class Program
} }
// 检查互斥的选项 // 检查互斥的选项
if (option is { MuxAfterDone: false, MuxImports.Count: > 0 }) if (option is { MuxAfterDone: false, MuxImports.Count: > 0 })
{ {
throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!"); throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!");
} }
if (option is { UseShakaPackager: true, UseMp4Decrypt: true })
{
throw new ArgumentException("UseShakaPackager and UseMp4Decrypt cannot be enabled simultaneously!");
}
// LivePipeMux开启时 LiveRealTimeMerge必须开启 // LivePipeMux开启时 LiveRealTimeMerge必须开启
if (option is { LivePipeMux: true, LiveRealTimeMerge: false }) if (option is { LivePipeMux: true, LiveRealTimeMerge: false })
{ {
@ -154,13 +158,17 @@ internal class Program
option.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4; option.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4;
Logger.Extra($"shaka-packager => {option.DecryptionBinaryPath}"); Logger.Extra($"shaka-packager => {option.DecryptionBinaryPath}");
} }
else else if (option.UseMp4Decrypt)
{ {
var file = GlobalUtil.FindExecutable("mp4decrypt"); var file = GlobalUtil.FindExecutable("mp4decrypt");
if (file == null) throw new FileNotFoundException("mp4decrypt not found!"); if (file == null) throw new FileNotFoundException("mp4decrypt not found!");
option.DecryptionBinaryPath = file; option.DecryptionBinaryPath = file;
Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}"); Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}");
} }
else
{
option.DecryptionBinaryPath = option.FFmpegBinaryPath;
}
} }
else if (!File.Exists(option.DecryptionBinaryPath)) else if (!File.Exists(option.DecryptionBinaryPath))
{ {

View File

@ -24,7 +24,7 @@ internal static class DownloadUtil
else else
{ {
var buffer = new byte[expect]; var buffer = new byte[expect];
await inputStream.ReadAsync(buffer); _ = await inputStream.ReadAsync(buffer);
await outputStream.WriteAsync(buffer, 0, buffer.Length); await outputStream.WriteAsync(buffer, 0, buffer.Length);
speedContainer.Add(buffer.Length); speedContainer.Add(buffer.Length);
} }

View File

@ -14,6 +14,7 @@ internal static class LanguageCodeUtil
{ {
private static readonly List<Language> ALL_LANGS = @" private static readonly List<Language> ALL_LANGS = @"
default;und;default;default
af;afr;Afrikaans;Afrikaans af;afr;Afrikaans;Afrikaans
af-ZA;afr;Afrikaans (South Africa);Afrikaans (South Africa) af-ZA;afr;Afrikaans (South Africa);Afrikaans (South Africa)
am;amh;Amharic;Amharic am;amh;Amharic;Amharic

View File

@ -1,16 +1,16 @@
using Mp4SubtitleParser; using Mp4SubtitleParser;
using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Common.Resource; using N_m3u8DL_RE.Common.Resource;
using N_m3u8DL_RE.Config;
using System.Diagnostics; using System.Diagnostics;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using N_m3u8DL_RE.Enum;
namespace N_m3u8DL_RE.Util; namespace N_m3u8DL_RE.Util;
internal static class MP4DecryptUtil internal static class MP4DecryptUtil
{ {
private static readonly string ZeroKid = "00000000000000000000000000000000"; private static readonly string ZeroKid = "00000000000000000000000000000000";
public static async Task<bool> DecryptAsync(bool shakaPackager, string bin, string[]? keys, string source, string dest, string? kid, string init = "", bool isMultiDRM=false) public static async Task<bool> DecryptAsync(DecryptEngine decryptEngine, string bin, string[]? keys, string source, string dest, string? kid, string init = "", bool isMultiDRM=false)
{ {
if (keys == null || keys.Length == 0) return false; if (keys == null || keys.Length == 0) return false;
@ -45,27 +45,27 @@ internal static class MP4DecryptUtil
if (keyPair == null) return false; if (keyPair == null) return false;
// shakaPackager 无法单独解密init文件 // shakaPackager/ffmpeg 无法单独解密init文件
if (source.EndsWith("_init.mp4") && shakaPackager) return false; if (source.EndsWith("_init.mp4") && decryptEngine != DecryptEngine.MP4DECRYPT) return false;
string cmd; string cmd;
var tmpFile = ""; var tmpFile = "";
if (shakaPackager) if (decryptEngine == DecryptEngine.SHAKA_PACKAGE)
{ {
var enc = source; var enc = source;
// shakaPackager 手动构造文件 // shakaPackager 手动构造文件
if (init != "") if (init != "")
{ {
tmpFile = Path.ChangeExtension(source, ".itmp"); tmpFile = Path.ChangeExtension(source, ".itmp");
MergeUtil.CombineMultipleFilesIntoSingleFile(new string[] { init, source }, tmpFile); MergeUtil.CombineMultipleFilesIntoSingleFile([init, source], tmpFile);
enc = tmpFile; enc = tmpFile;
} }
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 else if (decryptEngine == DecryptEngine.MP4DECRYPT)
{ {
if (trackId == null) if (trackId == null)
{ {
@ -81,6 +81,19 @@ internal static class MP4DecryptUtil
} }
cmd += $" \"{source}\" \"{dest}\""; cmd += $" \"{source}\" \"{dest}\"";
} }
else
{
var enc = source;
// ffmpeg实时解密 手动构造文件
if (init != "")
{
tmpFile = Path.ChangeExtension(source, ".itmp");
MergeUtil.CombineMultipleFilesIntoSingleFile([init, source], tmpFile);
enc = tmpFile;
}
cmd = $"-loglevel error -nostdin -decryption_key {keyPair.Split(':')[1]} -i \"{enc}\" -c copy \"{dest}\"";
}
await RunCommandAsync(bin, cmd); await RunCommandAsync(bin, cmd);
@ -153,7 +166,7 @@ internal static class MP4DecryptUtil
{ {
using var fs = File.OpenRead(output); using var fs = File.OpenRead(output);
var header = new byte[1 * 1024 * 1024]; // 1MB var header = new byte[1 * 1024 * 1024]; // 1MB
fs.Read(header); _ = fs.Read(header);
return GetMP4Info(header); return GetMP4Info(header);
} }