增加代理、自定义HLS KEY选项

This commit is contained in:
nilaoda 2022-09-20 22:51:28 +08:00
parent 0b02ecd2c8
commit e84558d908
11 changed files with 153 additions and 48 deletions

View File

@ -36,6 +36,9 @@ namespace N_m3u8DL_RE.Common.Resource
public static string cmd_selectAudio_more { get => GetText("cmd_selectAudio_more"); }
public static string cmd_selectSubtitle { get => GetText("cmd_selectSubtitle"); }
public static string cmd_selectSubtitle_more { get => GetText("cmd_selectSubtitle_more"); }
public static string cmd_customHLSMethod { get => GetText("cmd_customHLSMethod"); }
public static string cmd_customHLSKey { get => GetText("cmd_customHLSKey"); }
public static string cmd_customHLSIv { get => GetText("cmd_customHLSIv"); }
public static string cmd_Input { get => GetText("cmd_Input"); }
public static string cmd_keys { get => GetText("cmd_keys"); }
public static string cmd_keyText { get => GetText("cmd_keyText"); }
@ -57,6 +60,7 @@ namespace N_m3u8DL_RE.Common.Resource
public static string cmd_urlProcessorArgs { get => GetText("cmd_urlProcessorArgs"); }
public static string cmd_useShakaPackager { get => GetText("cmd_useShakaPackager"); }
public static string cmd_concurrentDownload { get => GetText("cmd_concurrentDownload"); }
public static string cmd_useSystemProxy { get => GetText("cmd_useSystemProxy"); }
public static string cmd_liveKeepSegments { get => GetText("cmd_liveKeepSegments"); }
public static string cmd_liveRecordLimit { get => GetText("cmd_liveRecordLimit"); }
public static string cmd_liveRealTimeMerge { get => GetText("cmd_liveRealTimeMerge"); }

View File

@ -244,17 +244,41 @@ namespace N_m3u8DL_RE.Common.Resource
zhTW: "錄製直播時即時合併",
enUS: "Real-time merge into file when recording live"
),
["cmd_useSystemProxy"] = new TextContainer
(
zhCN: "使用系统默认代理",
zhTW: "使用系統默認代理",
enUS: "Use system default proxy"
),
["cmd_livePerformAsVod"] = new TextContainer
(
zhCN: "以点播方式下载直播流",
zhTW: "以點播方式下載直播流",
enUS: "Download live streams as vod"
),
["cmd_customHLSMethod"] = new TextContainer
(
zhCN: "指定HLS加密方式 (AES_128|AES_128_ECB|CENC|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)",
zhTW: "指定HLS加密方式 (AES_128|AES_128_ECB|CENC|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)",
enUS: "Set HLS encryption method (AES_128|AES_128_ECB|CENC|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)"
),
["cmd_customHLSKey"] = new TextContainer
(
zhCN: "指定HLS解密KEY. 可以是文件, HEX或Base64",
zhTW: "指定HLS解密KEY. 可以是文件, HEX或Base64",
enUS: "Set the HLS decryption key. Can be file, HEX or Base64"
),
["cmd_customHLSIv"] = new TextContainer
(
zhCN: "指定HLS解密IV. 可以是文件, HEX或Base64",
zhTW: "指定HLS解密IV. 可以是文件, HEX或Base64",
enUS: "Set the HLS decryption iv. Can be file, HEX or Base64"
),
["cmd_liveKeepSegments"] = new TextContainer
(
zhCN: "录制直播并开启实时合并时依然保留分片",
zhTW: "錄製直播並開啟即時合併時依然保留分片",
enUS: "Keep segments when recording a live broadcast and enable liveRealTimeMerge"
enUS: "Keep segments when recording a live (liveRealTimeMerge enabled)"
),
["cmd_liveRecordLimit"] = new TextContainer
(
@ -276,9 +300,9 @@ namespace N_m3u8DL_RE.Common.Resource
),
["cmd_selectVideo"] = new TextContainer
(
zhCN: "通过正则表达式选择符合要求的视频流. 输入 \"--morehelp select-video\" 以查看详细信息.",
zhTW: "通過正則表達式選擇符合要求的影片軌. 輸入 \"--morehelp select-video\" 以查看詳細訊息.",
enUS: "Select video streams by regular expressions. Use \"--morehelp select-video\" for more details."
zhCN: "通过正则表达式选择符合要求的视频流. 输入 \"--morehelp select-video\" 以查看详细信息",
zhTW: "通過正則表達式選擇符合要求的影片軌. 輸入 \"--morehelp select-video\" 以查看詳細訊息",
enUS: "Select video streams by regular expressions. Use \"--morehelp select-video\" for more details"
),
["cmd_selectVideo_more"] = new TextContainer
(
@ -312,9 +336,9 @@ namespace N_m3u8DL_RE.Common.Resource
),
["cmd_selectAudio"] = new TextContainer
(
zhCN: "通过正则表达式选择符合要求的音频流. 输入 \"--morehelp select-audio\" 以查看详细信息.",
zhTW: "通過正則表達式選擇符合要求的音軌. 輸入 \"--morehelp select-audio\" 以查看詳細訊息.",
enUS: "Select audio streams by regular expressions. Use \"--morehelp select-audio\" for more details."
zhCN: "通过正则表达式选择符合要求的音频流. 输入 \"--morehelp select-audio\" 以查看详细信息",
zhTW: "通過正則表達式選擇符合要求的音軌. 輸入 \"--morehelp select-audio\" 以查看詳細訊息",
enUS: "Select audio streams by regular expressions. Use \"--morehelp select-audio\" for more details"
),
["cmd_selectAudio_more"] = new TextContainer
(
@ -345,9 +369,9 @@ namespace N_m3u8DL_RE.Common.Resource
),
["cmd_selectSubtitle"] = new TextContainer
(
zhCN: "通过正则表达式选择符合要求的字幕流. 输入 \"--morehelp select-subtitle\" 以查看详细信息.",
zhTW: "通過正則表達式選擇符合要求的字幕流. 輸入 \"--morehelp select-subtitle\" 以查看詳細訊息.",
enUS: "Select subtitle streams by regular expressions. Use \"--morehelp select-subtitle\" for more details."
zhCN: "通过正则表达式选择符合要求的字幕流. 输入 \"--morehelp select-subtitle\" 以查看详细信息",
zhTW: "通過正則表達式選擇符合要求的字幕流. 輸入 \"--morehelp select-subtitle\" 以查看詳細訊息",
enUS: "Select subtitle streams by regular expressions. Use \"--morehelp select-subtitle\" for more details"
),
["cmd_selectSubtitle_more"] = new TextContainer
(
@ -411,15 +435,15 @@ namespace N_m3u8DL_RE.Common.Resource
),
["cmd_muxAfterDone"] = new TextContainer
(
zhCN: "所有工作完成时尝试混流分离的音视频. 输入 \"--morehelp mux-after-done\" 以查看详细信息.",
zhTW: "所有工作完成時嘗試混流分離的影音. 輸入 \"--morehelp mux-after-done\" 以查看詳細訊息.",
enUS: "When all works is done, try to mux the downloaded streams. Use \"--morehelp mux-after-done\" for more details."
zhCN: "所有工作完成时尝试混流分离的音视频. 输入 \"--morehelp mux-after-done\" 以查看详细信息",
zhTW: "所有工作完成時嘗試混流分離的影音. 輸入 \"--morehelp mux-after-done\" 以查看詳細訊息",
enUS: "When all works is done, try to mux the downloaded streams. Use \"--morehelp mux-after-done\" for more details"
),
["cmd_muxImport"] = new TextContainer
(
zhCN: "混流时引入外部媒体文件. 输入 \"--morehelp mux-import\" 以查看详细信息.",
zhTW: "混流時引入外部媒體檔案. 輸入 \"--morehelp mux-import\" 以查看詳細訊息.",
enUS: "When MuxAfterDone enabled, allow to import local media files. Use \"--morehelp mux-import\" for more details."
zhCN: "混流时引入外部媒体文件. 输入 \"--morehelp mux-import\" 以查看详细信息",
zhTW: "混流時引入外部媒體檔案. 輸入 \"--morehelp mux-import\" 以查看詳細訊息",
enUS: "When MuxAfterDone enabled, allow to import local media files. Use \"--morehelp mux-import\" for more details"
),
["cmd_muxImport_more"] = new TextContainer
(

View File

@ -23,13 +23,14 @@ namespace N_m3u8DL_RE.Common.Util
{
public class HTTPUtil
{
public static readonly HttpClient AppHttpClient = new(new HttpClientHandler
public static readonly HttpClientHandler HttpClientHandler = new()
{
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.All,
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
})
};
public static readonly HttpClient AppHttpClient = new(HttpClientHandler)
{
Timeout = TimeSpan.FromMinutes(2)
};

View File

@ -13,6 +13,25 @@ namespace N_m3u8DL_RE.Common.Util
return BitConverter.ToString(data).Replace("-", split);
}
/// <summary>
/// 判断是不是HEX字符串
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static bool TryParseHexString(string input, out byte[]? bytes)
{
bytes = null;
input = input.ToUpper();
if (input.StartsWith("0X"))
input = input[2..];
if (input.Length % 2 != 0)
return false;
if (input.Any(c => !"0123456789ABCDEF".Contains(c)))
return false;
bytes = HexToBytes(input);
return true;
}
public static byte[] HexToBytes(string hex)
{
hex = hex.Trim();

View File

@ -318,18 +318,6 @@ namespace N_m3u8DL_RE.Parser.Extractor
//解析KEY
else if (line.StartsWith(HLSTags.ext_x_key))
{
//自定义KEY情况 不读取当前行的KEY信息.
//对于IV没自定义且当前行有IV的话 就用
if (ParserConfig.CustomeKey != null)
{
currentEncryptInfo.Key = ParserConfig.CustomeKey;
if (ParserConfig.CustomeIV == null && line.Contains("IV=0x"))
currentEncryptInfo.IV = HexUtil.HexToBytes(ParserUtil.GetAttribute(line, "IV"));
continue;
}
var iv = ParserUtil.GetAttribute(line, "IV");
var method = ParserUtil.GetAttribute(line, "METHOD");
var uri = ParserUtil.GetAttribute(line, "URI");
var uri_last = ParserUtil.GetAttribute(lastKeyLine, "URI");

View File

@ -28,12 +28,21 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
Logger.Debug("METHOD:{},URI:{},IV:{}", method, uri, iv);
var encryptInfo = new EncryptInfo(method);
//处理自定义加密方式
if (parserConfig.CustomMethod != null)
{
encryptInfo.Method = parserConfig.CustomMethod.Value;
Logger.Warn("METHOD changed to {}", method, encryptInfo.Method);
}
//IV
if (!string.IsNullOrEmpty(iv))
{
encryptInfo.IV = HexUtil.HexToBytes(iv);
}
if (parserConfig.CustomeIV != null)
//自定义IV
if (parserConfig.CustomeIV != null && parserConfig.CustomeIV.Length > 0)
{
encryptInfo.IV = parserConfig.CustomeIV;
}
@ -41,7 +50,7 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
//KEY
try
{
if (parserConfig.CustomeKey != null)
if (parserConfig.CustomeKey != null && parserConfig.CustomeKey.Length > 0)
{
encryptInfo.Key = parserConfig.CustomeKey;
}
@ -75,7 +84,7 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
{
Logger.WarnMarkUp($"[grey]{_ex.Message.EscapeMarkup()} retryCount: {retryCount}[/]");
Thread.Sleep(1000);
if (retryCount > 0) goto getHttpKey;
if (retryCount-- > 0) goto getHttpKey;
else throw;
}
}

View File

@ -1,5 +1,7 @@
using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Common.Enum;
using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Common.Resource;
using N_m3u8DL_RE.Common.Util;
using N_m3u8DL_RE.Entity;
using N_m3u8DL_RE.Enum;
using N_m3u8DL_RE.Util;
@ -48,9 +50,18 @@ namespace N_m3u8DL_RE.CommandLine
private readonly static Option<string?> BaseUrl = new(new string[] { "--base-url" }, description: ResString.cmd_baseUrl);
private readonly static Option<bool> ConcurrentDownload = new(new string[] { "-mt", "--concurrent-download" }, description: ResString.cmd_concurrentDownload, getDefaultValue: () => false);
//代理选项
private readonly static Option<bool> UseSystemProxy = new(new string[] { "--use-system-proxy" }, description: ResString.cmd_useSystemProxy, getDefaultValue: () => true);
//morehelp
private readonly static Option<string?> MoreHelp = new(new string[] { "--morehelp" }, description: ResString.cmd_moreHelp) { ArgumentHelpName = "OPTION" };
//自定义KEY等
private readonly static Option<EncryptMethod?> CustomHLSMethod = new(name: "--custom-hls-method", description: ResString.cmd_customHLSMethod) { ArgumentHelpName = "METHOD" };
private readonly static Option<byte[]?> CustomHLSKey = new(name: "--custom-hls-key", description: ResString.cmd_customHLSKey, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" };
private readonly static Option<byte[]?> CustomHLSIv = new(name: "--custom-hls-iv", description: ResString.cmd_customHLSIv, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" };
//直播相关
private readonly static Option<bool> LivePerformAsVod = new(new string[] { "--live-perform-as-vod" }, description: ResString.cmd_livePerformAsVod, getDefaultValue: () => false);
private readonly static Option<bool> LiveRealTimeMerge = new(new string[] { "--live-real-time-merge" }, description: ResString.cmd_liveRealTimeMerge, getDefaultValue: () => false);
@ -65,6 +76,32 @@ namespace N_m3u8DL_RE.CommandLine
private readonly static Option<StreamFilter?> AudioFilter = new(new string[] { "-sa", "--select-audio" }, description: ResString.cmd_selectAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
private readonly static Option<StreamFilter?> SubtitleFilter = new(new string[] { "-ss", "--select-subtitle" }, description: ResString.cmd_selectSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
/// <summary>
/// 解析自定义KEY
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
private static byte[]? ParseHLSCustomKey(ArgumentResult result)
{
var input = result.Tokens.First().Value;
try
{
if (string.IsNullOrEmpty(input))
return null;
if (File.Exists(input))
return File.ReadAllBytes(input);
else if (HexUtil.TryParseHexString(input, out byte[]? bytes))
return bytes;
else
return Convert.FromBase64String(input);
}
catch (Exception)
{
result.ErrorMessage = "error in parse hls custom key: " + input;
return null;
}
}
/// <summary>
/// 解析录制直播时长限制
/// </summary>
@ -302,6 +339,10 @@ namespace N_m3u8DL_RE.CommandLine
LiveKeepSegments = bindingContext.ParseResult.GetValueForOption(LiveKeepSegments),
LiveRecordLimit = bindingContext.ParseResult.GetValueForOption(LiveRecordLimit),
LivePerformAsVod = bindingContext.ParseResult.GetValueForOption(LivePerformAsVod),
UseSystemProxy = bindingContext.ParseResult.GetValueForOption(UseSystemProxy),
CustomHLSMethod = bindingContext.ParseResult.GetValueForOption(CustomHLSMethod),
CustomHLSKey = bindingContext.ParseResult.GetValueForOption(CustomHLSKey),
CustomHLSIv = bindingContext.ParseResult.GetValueForOption(CustomHLSIv),
};
var parsedHeaders = bindingContext.ParseResult.GetValueForOption(Headers);
@ -362,6 +403,7 @@ namespace N_m3u8DL_RE.CommandLine
FFmpegBinaryPath,
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
MuxAfterDone,
CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy,
LivePerformAsVod, LiveRealTimeMerge, LiveKeepSegments, LiveRecordLimit,
MuxImports, VideoFilter, AudioFilter, SubtitleFilter, MoreHelp
};

View File

@ -1,4 +1,5 @@
using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Common.Enum;
using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Entity;
using N_m3u8DL_RE.Enum;
@ -123,6 +124,10 @@ namespace N_m3u8DL_RE.CommandLine
/// </summary>
public bool LivePerformAsVod { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.UseSystemProxy"/>.
/// </summary>
public bool UseSystemProxy { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.SubtitleFormat"/>.
/// </summary>
public SubtitleFormat SubtitleFormat { get; set; }
@ -174,6 +179,18 @@ namespace N_m3u8DL_RE.CommandLine
/// See: <see cref="CommandInvoker.SubtitleFilter"/>.
/// </summary>
public StreamFilter? SubtitleFilter { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.CustomHLSMethod"/>.
/// </summary>
public EncryptMethod? CustomHLSMethod { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.CustomHLSKey"/>.
/// </summary>
public byte[]? CustomHLSKey { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.CustomHLSIv"/>.
/// </summary>
public byte[]? CustomHLSIv { get; set; }
public bool MuxKeepFiles { get; set; }
}
}

View File

@ -58,6 +58,11 @@ namespace N_m3u8DL_RE
{
Logger.LogLevel = option.LogLevel;
if (option.UseSystemProxy == false)
{
HTTPUtil.HttpClientHandler.UseProxy = false;
}
try
{
//检查互斥的选项
@ -130,7 +135,10 @@ namespace N_m3u8DL_RE
AppendUrlParams = option.AppendUrlParams,
UrlProcessorArgs = option.UrlProcessorArgs,
BaseUrl = option.BaseUrl!,
Headers = headers
Headers = headers,
CustomMethod = option.CustomHLSMethod,
CustomeKey = option.CustomHLSKey,
CustomeIV = option.CustomHLSIv,
};
//demo1

View File

@ -1,5 +1,6 @@
using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Common.Resource;
using N_m3u8DL_RE.Common.Util;
using N_m3u8DL_RE.Entity;
using System;
using System.Collections.Generic;
@ -14,15 +15,7 @@ namespace N_m3u8DL_RE.Util
{
internal class DownloadUtil
{
private static readonly HttpClient AppHttpClient = new(new HttpClientHandler
{
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.All,
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
})
{
Timeout = TimeSpan.FromMinutes(2)
};
private static readonly HttpClient AppHttpClient = HTTPUtil.AppHttpClient;
public static async Task<DownloadResult> DownloadToFileAsync(string url, string path, SpeedContainer speedContainer, Dictionary<string, string>? headers = null, long? fromPosition = null, long? toPosition = null)
{

View File

@ -8,9 +8,9 @@ namespace N_m3u8DL_RE.Util
{
internal class OtherUtil
{
public static Dictionary<string,string> SplitHeaderArrayToDic(string[]? headers)
public static Dictionary<string, string> SplitHeaderArrayToDic(string[]? headers)
{
Dictionary<string,string> dic = new();
Dictionary<string, string> dic = new();
if (headers != null)
{