You've already forked N_m3u8DL-CLI
mirror of
https://github.com/nilaoda/N_m3u8DL-CLI
synced 2025-09-07 02:45:59 +02:00
Compare commits
119 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
985f6e57c3 | ||
![]() |
d9eea2e80f | ||
![]() |
0cc4a87a4c | ||
![]() |
66d0864d72 | ||
![]() |
965ac2b513 | ||
![]() |
a95334ec57 | ||
![]() |
e05a21a034 | ||
![]() |
13cd5d0870 | ||
![]() |
8d9ad7af41 | ||
![]() |
0a11816acf | ||
![]() |
81ba4ff7d3 | ||
![]() |
f5a9ed08a1 | ||
![]() |
6b8bfde19f | ||
![]() |
ff738f2849 | ||
![]() |
3d35c24a2e | ||
![]() |
41d7151e7e | ||
![]() |
94246ef163 | ||
![]() |
9c118631cf | ||
![]() |
2ee9ab234f | ||
![]() |
606f2184df | ||
![]() |
7dd2b6cee2 | ||
![]() |
f744aa990e | ||
![]() |
97c9f444b8 | ||
![]() |
8aac37bbe2 | ||
![]() |
b7e19ec699 | ||
![]() |
7e7c5c6ba9 | ||
![]() |
50722079ba | ||
![]() |
d529ece032 | ||
![]() |
9ed8b359a1 | ||
![]() |
a7591e3978 | ||
![]() |
094a89bc79 | ||
![]() |
9e7b603d3b | ||
![]() |
3801090928 | ||
![]() |
42c1d628d4 | ||
![]() |
ecefecd827 | ||
![]() |
4661818c1f | ||
![]() |
fa61cd136d | ||
![]() |
472fb0cf02 | ||
![]() |
28e39a94ac | ||
![]() |
a42ade64f3 | ||
![]() |
539f773e37 | ||
![]() |
d6f27cf90f | ||
![]() |
8912ee5e4b | ||
![]() |
409437b2c7 | ||
![]() |
350d3b508d | ||
![]() |
1e3f06eaae | ||
![]() |
2d4bc42d55 | ||
![]() |
68e06d3fa7 | ||
![]() |
6c3340e94e | ||
![]() |
051faa9f2b | ||
![]() |
115f9c9424 | ||
![]() |
faf67cd527 | ||
![]() |
38d1a1a2dc | ||
![]() |
12eb68d592 | ||
![]() |
0804e295e5 | ||
![]() |
847c4683cb | ||
![]() |
8c72947860 | ||
![]() |
f0b240a6ee | ||
![]() |
793cf53042 | ||
![]() |
612fc29197 | ||
![]() |
307e2389de | ||
![]() |
1c932abdc3 | ||
![]() |
314f0065c7 | ||
![]() |
59060bb74d | ||
![]() |
cab882c3a3 | ||
![]() |
9955532ce5 | ||
![]() |
7e127be8c2 | ||
![]() |
b46571a57f | ||
![]() |
da5861d907 | ||
![]() |
92bc91a1fb | ||
![]() |
439f50103e | ||
![]() |
8a95e31b2f | ||
![]() |
115b8a156a | ||
![]() |
120bcaebb5 | ||
![]() |
455d56707c | ||
![]() |
048adcf118 | ||
![]() |
fe5aa27b1c | ||
![]() |
039aa489b1 | ||
![]() |
14e80f0b06 | ||
![]() |
2256fff549 | ||
![]() |
84cfd4e138 | ||
![]() |
e70c229135 | ||
![]() |
8b520d0c19 | ||
![]() |
71d69de51a | ||
![]() |
bc89ead00d | ||
![]() |
ae79d6eb3a | ||
![]() |
96bd8af883 | ||
![]() |
89b1e30e0f | ||
![]() |
7a741359ab | ||
![]() |
564b6ad291 | ||
![]() |
a0fc9404f7 | ||
![]() |
5267be1699 | ||
![]() |
20bfda39e7 | ||
![]() |
ba4c0eeda7 | ||
![]() |
5d72e24002 | ||
![]() |
a87c051d23 | ||
![]() |
44e1b68d6b | ||
![]() |
e65dfa52cd | ||
![]() |
965c173899 | ||
![]() |
880af02cc2 | ||
![]() |
2742de43c4 | ||
![]() |
9d8cb57390 | ||
![]() |
9e2a192dab | ||
![]() |
33cf9e2256 | ||
![]() |
2959cbbb5c | ||
![]() |
c2eb8a6adc | ||
![]() |
e1b591b81c | ||
![]() |
334b1939b5 | ||
![]() |
7e916b65fd | ||
![]() |
4ead563fa2 | ||
![]() |
1b387a06e5 | ||
![]() |
6e7b4ac7ea | ||
![]() |
e98c5205d1 | ||
![]() |
d7890dd124 | ||
![]() |
82f2111522 | ||
![]() |
4c3207586f | ||
![]() |
69b411e37c | ||
![]() |
1e8525041f | ||
![]() |
65ae72d4a4 |
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://nilaoda.github.io/N_m3u8DL-CLI/source/images/alipay.png','https://www.buymeacoffee.com/nilaoda']
|
2
.github/workflows/build_latest.yml
vendored
2
.github/workflows/build_latest.yml
vendored
@@ -31,4 +31,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v1.0.0
|
||||
with:
|
||||
name: N_m3u8DL-CLI_latest
|
||||
path: N_m3u8DL-CLI\bin\Release\
|
||||
path: N_m3u8DL-CLI\bin\Release\N_m3u8DL-CLI.exe
|
||||
|
661
N_m3u8DL-CLI/CSChaCha20.cs
Normal file
661
N_m3u8DL-CLI/CSChaCha20.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,80 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
/**
|
||||
* https://www.cnblogs.com/linxuanchen/p/c-sharp-command-line-argument-parser.html
|
||||
*/
|
||||
public class CommandLineArgument
|
||||
{
|
||||
List<CommandLineArgument> _arguments;
|
||||
|
||||
int _index;
|
||||
|
||||
string _argumentText;
|
||||
|
||||
public CommandLineArgument Next
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index < _arguments.Count - 1)
|
||||
{
|
||||
return _arguments[_index + 1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public CommandLineArgument Previous
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index > 0)
|
||||
{
|
||||
return _arguments[_index - 1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
internal CommandLineArgument(List<CommandLineArgument> args, int index, string argument)
|
||||
{
|
||||
_arguments = args;
|
||||
_index = index;
|
||||
_argumentText = argument;
|
||||
}
|
||||
|
||||
public CommandLineArgument Take()
|
||||
{
|
||||
return Next;
|
||||
}
|
||||
|
||||
public IEnumerable<CommandLineArgument> Take(int count)
|
||||
{
|
||||
var list = new List<CommandLineArgument>();
|
||||
var parent = this;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var next = parent.Next;
|
||||
if (next == null)
|
||||
break;
|
||||
|
||||
list.Add(next);
|
||||
|
||||
parent = next;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static implicit operator string(CommandLineArgument argument)
|
||||
{
|
||||
return argument._argumentText;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _argumentText;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
public class CommandLineArgumentParser
|
||||
{
|
||||
|
||||
List<CommandLineArgument> _arguments;
|
||||
public static CommandLineArgumentParser Parse(string[] args)
|
||||
{
|
||||
return new CommandLineArgumentParser(args);
|
||||
}
|
||||
|
||||
public CommandLineArgumentParser(string[] args)
|
||||
{
|
||||
_arguments = new List<CommandLineArgument>();
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
_arguments.Add(new CommandLineArgument(_arguments, i, args[i]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public CommandLineArgument Get(string argumentName)
|
||||
{
|
||||
return _arguments.FirstOrDefault(p => p == argumentName);
|
||||
}
|
||||
|
||||
public bool Has(string argumentName)
|
||||
{
|
||||
return _arguments.Count(p => p == argumentName) > 0;
|
||||
}
|
||||
}
|
||||
}
|
83
N_m3u8DL-CLI/DecodeCdeledu.cs
Normal file
83
N_m3u8DL-CLI/DecodeCdeledu.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using NiL.JS.BaseLibrary;
|
||||
using NiL.JS.Core;
|
||||
using NiL.JS.Extensions;
|
||||
using System;
|
||||
using Array = System.Array;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
internal class DecodeCdeledu
|
||||
{
|
||||
private static string JS = @"
|
||||
var _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
|
||||
var removePaddingChars = function(input) {
|
||||
var lkey = _keyStr.indexOf(input.charAt(input.length - 1));
|
||||
if (lkey == 64) {
|
||||
return input.substring(0, input.length - 1);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
var base64Decode = function(input, arrayBuffer) {
|
||||
input = removePaddingChars(input);
|
||||
input = removePaddingChars(input);
|
||||
var bytes = parseInt((input.length / 4) * 3, 10);
|
||||
var uarray;
|
||||
var chr1, chr2, chr3;
|
||||
var enc1, enc2, enc3, enc4;
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
if (arrayBuffer) {
|
||||
uarray = new Uint8Array(arrayBuffer);
|
||||
} else {
|
||||
uarray = new Uint8Array(bytes);
|
||||
}
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
|
||||
for (i = 0; i < bytes; i += 3) {
|
||||
enc1 = _keyStr.indexOf(input.charAt(j++));
|
||||
enc2 = _keyStr.indexOf(input.charAt(j++));
|
||||
enc3 = _keyStr.indexOf(input.charAt(j++));
|
||||
enc4 = _keyStr.indexOf(input.charAt(j++));
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
uarray[i] = chr1;
|
||||
if (enc3 != 64)
|
||||
uarray[i + 1] = chr2;
|
||||
if (enc4 != 64)
|
||||
uarray[i + 2] = chr3;
|
||||
}
|
||||
return uarray;
|
||||
}
|
||||
|
||||
var uint8ArrayToString = function(uDataArr) {
|
||||
var arrStr = '';
|
||||
for (var i = 0; i < uDataArr.length; i++) {
|
||||
arrStr += String.fromCharCode(uDataArr[i]);
|
||||
}
|
||||
return arrStr;
|
||||
}
|
||||
|
||||
var decodeKey = function(dataKeyString) {
|
||||
var decodeArr = base64Decode(dataKeyString);
|
||||
var decodeArrString = uint8ArrayToString(decodeArr);
|
||||
return decodeArrString;
|
||||
if (decodeArrString.indexOf('|&|') > 0) {
|
||||
return decodeArrString;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
";
|
||||
//https://video.cdeledu.com/js/lib/cdel.hls.min-1.0.js?v=1.3
|
||||
public static string DecodeKey(string txt)
|
||||
{
|
||||
var context = new Context();
|
||||
context.Eval(JS);
|
||||
var concatFunction = context.GetVariable("decodeKey").As<Function>();
|
||||
string key = concatFunction.Call(new Arguments { txt }).ToString();
|
||||
string realKey = key.Split(new string[] { "|&|" }, StringSplitOptions.None)[1];
|
||||
return realKey;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using CSChaCha20;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
@@ -45,6 +47,40 @@ namespace N_m3u8DL_CLI
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
public static byte[] CHACHA20Decrypt(byte[] encryptedBuff, byte[] keyBytes, byte[] nonceBytes)
|
||||
{
|
||||
if (keyBytes.Length != 32)
|
||||
throw new Exception("Key must be 32 bytes!");
|
||||
if (nonceBytes.Length != 12 && nonceBytes.Length != 8)
|
||||
throw new Exception("Key must be 12 or 8 bytes!");
|
||||
if (nonceBytes.Length == 8)
|
||||
nonceBytes = (new byte[4] { 0, 0, 0, 0 }).Concat(nonceBytes).ToArray();
|
||||
|
||||
var decStream = new MemoryStream();
|
||||
using (BinaryReader reader = new BinaryReader(new MemoryStream(encryptedBuff)))
|
||||
{
|
||||
using (BinaryWriter writer = new BinaryWriter(decStream))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var buffer = reader.ReadBytes(1024);
|
||||
byte[] dec = new byte[buffer.Length];
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
ChaCha20 forDecrypting = new ChaCha20(keyBytes, nonceBytes, 0);
|
||||
forDecrypting.DecryptBytes(dec, buffer);
|
||||
writer.Write(dec, 0, dec.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return decStream.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] HexStringToBytes(string hexStr)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hexStr))
|
||||
|
@@ -36,12 +36,14 @@ namespace N_m3u8DL_CLI
|
||||
public string MuxSetJson { get; set; } = string.Empty;
|
||||
public int TimeOut { get; set; } = 10000; //超时设置
|
||||
public static double DownloadedSize { get; set; } = 0; //已下载大小
|
||||
public static double ToDoSize { get; set; } = 0; //待下载大小
|
||||
public static bool HasSetDir { get; set; } = false;
|
||||
public bool NoMerge { get; set; } = false;
|
||||
public static int CalcTime { get; set; } = 1; //计算速度的间隔
|
||||
public static int Count { get; set; } = 0;
|
||||
public static int PartsCount { get; set; } = 0;
|
||||
public static bool DisableIntegrityCheck { get; set; } = false; //关闭完整性检查
|
||||
public static bool HasExtMap { get; set; } = false; //是否有MAP
|
||||
|
||||
static CancellationTokenSource cts = new CancellationTokenSource();
|
||||
//计算下载速度
|
||||
@@ -52,25 +54,37 @@ namespace N_m3u8DL_CLI
|
||||
timer.AutoReset = true;
|
||||
timer.Elapsed += delegate
|
||||
{
|
||||
Console.SetCursorPosition(0, 1);
|
||||
Console.Write("Speed: " + Global.FormatFileSize((Global.BYTEDOWN) / CalcTime) + " / s".PadRight(70));
|
||||
var eta = "";
|
||||
if (ToDoSize != 0)
|
||||
{
|
||||
eta = " @ " + Global.FormatTime(Convert.ToInt32(ToDoSize / (Global.BYTEDOWN / CalcTime)));
|
||||
}
|
||||
var print = Global.FormatFileSize((Global.BYTEDOWN) / CalcTime) + "/s" + eta;
|
||||
ProgressReporter.Report("", "(" + print + ")");
|
||||
|
||||
if (Global.HadReadInfo && Global.BYTEDOWN <= Global.STOP_SPEED * 1024 * CalcTime)
|
||||
{
|
||||
stopCount++;
|
||||
Console.SetCursorPosition(0, 1);
|
||||
Console.Write("Speed: " + Global.FormatFileSize((Global.BYTEDOWN) / CalcTime) + " / s [" + stopCount + "]".PadRight(70));
|
||||
eta = "";
|
||||
if (ToDoSize != 0)
|
||||
{
|
||||
eta = " @ " + Global.FormatTime(Convert.ToInt32(ToDoSize / (Global.BYTEDOWN / CalcTime)));
|
||||
}
|
||||
print = Global.FormatFileSize((Global.BYTEDOWN) / CalcTime) + "/s [" + stopCount + "]" + eta;
|
||||
ProgressReporter.Report("", "(" + print + ")");
|
||||
|
||||
if (stopCount >= 12)
|
||||
{
|
||||
Global.ShouldStop = true;
|
||||
cts.Cancel();
|
||||
timer.Enabled = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stopCount = 0;
|
||||
Global.BYTEDOWN = 0;
|
||||
Global.ShouldStop = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -134,8 +148,10 @@ namespace N_m3u8DL_CLI
|
||||
LOGGER.PrintLine(strings.startDownloading, LOGGER.Warning);
|
||||
|
||||
//下载MAP文件(若有)
|
||||
try
|
||||
downloadMap:
|
||||
if (HasExtMap)
|
||||
{
|
||||
LOGGER.PrintLine(strings.downloadingMapFile);
|
||||
Downloader sd = new Downloader();
|
||||
sd.TimeOut = TimeOut;
|
||||
sd.FileUrl = initJson["m3u8Info"]["extMAP"].Value<string>();
|
||||
@@ -153,12 +169,12 @@ namespace N_m3u8DL_CLI
|
||||
File.Delete(sd.SavePath);
|
||||
if (File.Exists(DownDir + "\\Part_0\\!MAP.ts"))
|
||||
File.Delete(DownDir + "\\Part_0\\!MAP.ts");
|
||||
LOGGER.PrintLine(strings.downloadingMapFile);
|
||||
sd.Down(); //开始下载
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//LOG.WriteLineError(e.ToString());
|
||||
if (!File.Exists(DownDir + "\\!MAP.ts")) //检测是否成功下载
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
goto downloadMap;
|
||||
}
|
||||
}
|
||||
|
||||
//首先下载第一个分片
|
||||
@@ -328,14 +344,13 @@ namespace N_m3u8DL_CLI
|
||||
Count++;
|
||||
LOGGER.WriteLine(strings.retryCount + Count + " / " + RetryCount);
|
||||
LOGGER.PrintLine(strings.retryCount + Count + " / " + RetryCount, LOGGER.Warning);
|
||||
Thread.Sleep(6000);
|
||||
Thread.Sleep(3000);
|
||||
DoDownload();
|
||||
}
|
||||
}
|
||||
else //开始合并
|
||||
{
|
||||
LOGGER.PrintLine(strings.downloadComplete + (DisableIntegrityCheck ? "(" + strings.disableIntegrityCheck + ")" : ""));
|
||||
Console.WriteLine();
|
||||
if (NoMerge == false)
|
||||
{
|
||||
string exePath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
|
||||
@@ -368,6 +383,8 @@ namespace N_m3u8DL_CLI
|
||||
//有MAP文件,一般为mp4,采取默认动作
|
||||
if(File.Exists(DownDir + "\\Part_0\\!MAP.ts"))
|
||||
MuxFormat = "mp4";
|
||||
if (isVTT)
|
||||
MuxFormat = "vtt";
|
||||
|
||||
if (Global.AUDIO_TYPE != "")
|
||||
MuxFormat = Global.AUDIO_TYPE;
|
||||
@@ -377,7 +394,6 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
if (Global.VIDEO_TYPE != "DV") //不是杜比视界
|
||||
{
|
||||
LOGGER.FFmpegCorsorIndex = LOGGER.CursorIndex;
|
||||
//检测是否为MPEG-TS封装,不是的话就转换为TS封装
|
||||
foreach (string s in Global.GetFiles(DownDir + "\\Part_0", ".ts"))
|
||||
{
|
||||
@@ -463,7 +479,6 @@ namespace N_m3u8DL_CLI
|
||||
DownDir = parser.DownDir;
|
||||
parser.Parse(); //开始解析
|
||||
Thread.Sleep(1000);
|
||||
LOGGER.CursorIndex = 5;
|
||||
Global.HadReadInfo = false;
|
||||
Global.VIDEO_TYPE = "";
|
||||
Global.AUDIO_TYPE = "";
|
||||
@@ -488,16 +503,13 @@ namespace N_m3u8DL_CLI
|
||||
DownDir = parser.DownDir;
|
||||
parser.Parse(); //开始解析
|
||||
Thread.Sleep(1000);
|
||||
LOGGER.CursorIndex = 5;
|
||||
Global.HadReadInfo = false;
|
||||
Global.VIDEO_TYPE = "";
|
||||
Global.AUDIO_TYPE = "";
|
||||
DoDownload();
|
||||
}
|
||||
LOGGER.PrintLine(strings.taskDone, LOGGER.Warning);
|
||||
Console.CursorVisible = true;
|
||||
Environment.Exit(0); //正常退出程序
|
||||
Console.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -528,13 +540,14 @@ namespace N_m3u8DL_CLI
|
||||
//有MAP文件,一般为mp4,采取默认动作
|
||||
if (File.Exists(DownDir + "\\!MAP.ts"))
|
||||
MuxFormat = "mp4";
|
||||
if (isVTT)
|
||||
MuxFormat = "vtt";
|
||||
Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir, ".ts"), FFmpeg.OutPutPath + $".{MuxFormat}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Global.VIDEO_TYPE != "DV") //不是爱奇艺杜比视界
|
||||
{
|
||||
LOGGER.FFmpegCorsorIndex = LOGGER.CursorIndex;
|
||||
//检测是否为MPEG-TS封装,不是的话就转换为TS封装
|
||||
foreach (string s in Global.GetFiles(DownDir, ".ts"))
|
||||
{
|
||||
@@ -609,7 +622,6 @@ namespace N_m3u8DL_CLI
|
||||
DownDir = parser.DownDir;
|
||||
parser.Parse(); //开始解析
|
||||
Thread.Sleep(1000);
|
||||
LOGGER.CursorIndex = 5;
|
||||
Global.HadReadInfo = false;
|
||||
Global.VIDEO_TYPE = "";
|
||||
Global.AUDIO_TYPE = "";
|
||||
@@ -634,17 +646,13 @@ namespace N_m3u8DL_CLI
|
||||
DownDir = parser.DownDir;
|
||||
parser.Parse(); //开始解析
|
||||
Thread.Sleep(1000);
|
||||
LOGGER.CursorIndex = 5;
|
||||
Global.HadReadInfo = false;
|
||||
Global.VIDEO_TYPE = "";
|
||||
Global.AUDIO_TYPE = "";
|
||||
DoDownload();
|
||||
}
|
||||
LOGGER.PrintLine(strings.taskDone, LOGGER.Warning);
|
||||
Console.CursorVisible = true;
|
||||
Environment.Exit(0); //正常退出程序
|
||||
|
||||
Console.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -47,6 +47,10 @@ namespace N_m3u8DL_CLI
|
||||
public long ExpectByte { get => expectByte; set => expectByte = value; }
|
||||
public long StartByte { get => startByte; set => startByte = value; }
|
||||
public double SegDur { get => segDur; set => segDur = value; }
|
||||
|
||||
public static bool EnableChaCha20 { get; set; } = false;
|
||||
public static string ChaCha20KeyBase64 { get; set; }
|
||||
public static string ChaCha20NonceBase64 { get; set; }
|
||||
|
||||
//重写WebClinet
|
||||
//private class WebClient : System.Net.WebClient
|
||||
@@ -179,9 +183,18 @@ namespace N_m3u8DL_CLI
|
||||
if (File.Exists(savePath) && Global.ShouldStop == false)
|
||||
{
|
||||
FileInfo fi = new FileInfo(savePath);
|
||||
if (Method == "NONE" || method.Contains("NOTSUPPORTED"))
|
||||
if (File.Exists(fi.FullName) && EnableChaCha20)
|
||||
{
|
||||
fi.MoveTo(Path.GetDirectoryName(savePath) + "\\" + Path.GetFileNameWithoutExtension(savePath) + ".ts");
|
||||
byte[] decryptBuff = Decrypter.CHACHA20Decrypt(File.ReadAllBytes(fi.FullName), Convert.FromBase64String(ChaCha20KeyBase64), Convert.FromBase64String(ChaCha20NonceBase64));
|
||||
FileStream fs = new FileStream(Path.GetDirectoryName(SavePath) + "\\" + Path.GetFileNameWithoutExtension(SavePath) + ".ts", FileMode.Create);
|
||||
fs.Write(decryptBuff, 0, decryptBuff.Length);
|
||||
fs.Close();
|
||||
DownloadManager.DownloadedSize += fi.Length;
|
||||
fi.Delete();
|
||||
}
|
||||
else if (Method == "NONE" || Method.Contains("NOTSUPPORTED"))
|
||||
{
|
||||
fi.MoveTo(Path.GetDirectoryName(SavePath) + "\\" + Path.GetFileNameWithoutExtension(SavePath) + ".ts");
|
||||
DownloadManager.DownloadedSize += fi.Length;
|
||||
//Console.WriteLine(Path.GetFileNameWithoutExtension(savePath) + " Completed.");
|
||||
}
|
||||
@@ -249,4 +262,4 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -61,7 +61,8 @@ namespace N_m3u8DL_CLI
|
||||
string m3u8Url = initJson["m3u8"].Value<string>();
|
||||
targetduration = initJson["m3u8Info"]["targetDuration"].Value<double>();
|
||||
TotalDuration = initJson["m3u8Info"]["totalDuration"].Value<double>();
|
||||
timer.Interval = (TotalDuration - targetduration) * 1000;//设置定时器运行间隔
|
||||
timer.Interval = Math.Abs(TotalDuration - targetduration) * 1000;//设置定时器运行间隔
|
||||
if (timer.Interval <= 1000) timer.Interval = 10000;
|
||||
JArray lastSegments = JArray.Parse(initJson["m3u8Info"]["segments"][0].ToString().Trim()); //上次的分段,用于比对新分段
|
||||
ArrayList tempList = new ArrayList(); //所有待下载的列表
|
||||
tempList.Clear();
|
||||
@@ -78,6 +79,7 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
|
||||
Parser parser = new Parser();
|
||||
parser.Headers = Headers;
|
||||
parser.DownDir = Path.GetDirectoryName(jsonFile);
|
||||
parser.M3u8Url = m3u8Url;
|
||||
parser.LiveStream = true;
|
||||
@@ -127,6 +129,7 @@ namespace N_m3u8DL_CLI
|
||||
sd.Iv = info["iv"].Value<string>();
|
||||
}
|
||||
sd.TimeOut = (int)timer.Interval - 1000;//超时时间不超过下次执行时间
|
||||
if (sd.TimeOut <= 0) sd.TimeOut = (int)timer.Interval;
|
||||
sd.SegIndex = index;
|
||||
sd.Headers = Headers;
|
||||
sd.SegDur = info["duration"].Value<double>();
|
||||
|
116
N_m3u8DL-CLI/IqJsonParser.cs
Normal file
116
N_m3u8DL-CLI/IqJsonParser.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class IqJsonParser
|
||||
{
|
||||
public static string Parse(string downDir, string json)
|
||||
{
|
||||
JObject jObject = JObject.Parse(json);
|
||||
var aClips = jObject["payload"]["wm_a"]["audio_track1"]["files"].Value<JArray>();
|
||||
var vClips = jObject["payload"]["wm_a"]["video_track1"]["files"].Value<JArray>();
|
||||
|
||||
var codecsList = new List<string>();
|
||||
|
||||
var audioPath = "";
|
||||
var videoPath = "";
|
||||
var audioInitPath = "";
|
||||
var videoInitPath = "";
|
||||
|
||||
if (aClips.Count > 0)
|
||||
{
|
||||
var init = jObject["payload"]["wm_a"]["audio_track1"]["codec_init"].Value<string>();
|
||||
byte[] bytes = Convert.FromBase64String(init);
|
||||
//输出init文件
|
||||
audioInitPath = Path.Combine(downDir, "iqAudioInit.mp4");
|
||||
File.WriteAllBytes(audioInitPath, bytes);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("#EXTM3U");
|
||||
sb.AppendLine("#EXT-X-VERSION:3");
|
||||
sb.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
|
||||
sb.AppendLine("#CREATED-BY:N_m3u8DL-CLI");
|
||||
sb.AppendLine($"#EXT-CODEC:{jObject["payload"]["wm_a"]["audio_track1"]["codec"].Value<string>()}");
|
||||
sb.AppendLine($"#EXT-KID:{jObject["payload"]["wm_a"]["audio_track1"]["key_id"].Value<string>()}");
|
||||
sb.AppendLine($"#EXT-X-MAP:URI=\"{new Uri(Path.Combine(downDir + "(Audio)", "iqAudioInit.mp4")).ToString()}\"");
|
||||
sb.AppendLine("#EXT-X-KEY:METHOD=PLZ-KEEP-RAW,URI=\"None\"");
|
||||
foreach (var a in aClips)
|
||||
{
|
||||
sb.AppendLine($"#EXTINF:{a["duration_second"].ToString()}");
|
||||
sb.AppendLine(a["file_name"].Value<string>());
|
||||
}
|
||||
sb.AppendLine("#EXT-X-ENDLIST");
|
||||
//输出m3u8文件
|
||||
var _path = Path.Combine(downDir, "iqAudio.m3u8");
|
||||
File.WriteAllText(_path, sb.ToString());
|
||||
audioPath = new Uri(_path).ToString();
|
||||
codecsList.Add(jObject["payload"]["wm_a"]["audio_track1"]["codec"].Value<string>());
|
||||
}
|
||||
|
||||
if (vClips.Count > 0)
|
||||
{
|
||||
var init = jObject["payload"]["wm_a"]["video_track1"]["codec_init"].Value<string>();
|
||||
byte[] bytes = Convert.FromBase64String(init);
|
||||
//输出init文件
|
||||
videoInitPath = Path.Combine(downDir, "iqVideoInit.mp4");
|
||||
File.WriteAllBytes(videoInitPath, bytes);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("#EXTM3U");
|
||||
sb.AppendLine("#EXT-X-VERSION:3");
|
||||
sb.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
|
||||
sb.AppendLine("#CREATED-BY:N_m3u8DL-CLI");
|
||||
sb.AppendLine($"#EXT-CODEC:{jObject["payload"]["wm_a"]["video_track1"]["codec"].Value<string>()}");
|
||||
sb.AppendLine($"#EXT-KID:{jObject["payload"]["wm_a"]["video_track1"]["key_id"].Value<string>()}");
|
||||
sb.AppendLine($"#EXT-X-MAP:URI=\"{new Uri(videoInitPath).ToString()}\"");
|
||||
sb.AppendLine("#EXT-X-KEY:METHOD=PLZ-KEEP-RAW,URI=\"None\"");
|
||||
foreach (var a in vClips)
|
||||
{
|
||||
var start = a["seekable"]["pos_start"].Value<long>();
|
||||
var size = a["size"].Value<long>();
|
||||
sb.AppendLine($"#EXTINF:{a["duration_second"].ToString()}");
|
||||
sb.AppendLine($"#EXT-X-BYTERANGE:{size}@{start}");
|
||||
sb.AppendLine(a["file_name"].Value<string>());
|
||||
}
|
||||
sb.AppendLine("#EXT-X-ENDLIST");
|
||||
//输出m3u8文件
|
||||
var _path = Path.Combine(downDir, "iqVideo.m3u8");
|
||||
File.WriteAllText(_path, sb.ToString());
|
||||
videoPath = new Uri(_path).ToString();
|
||||
codecsList.Add(jObject["payload"]["wm_a"]["video_track1"]["codec"].Value<string>());
|
||||
}
|
||||
|
||||
var content = "";
|
||||
if ((videoPath == "" && audioPath != "") || Global.VIDEO_TYPE == "IGNORE")
|
||||
{
|
||||
return audioPath;
|
||||
}
|
||||
else if (audioPath == "" && videoPath != "")
|
||||
{
|
||||
return videoPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Directory.Exists(downDir + "(Audio)"))
|
||||
Directory.CreateDirectory(downDir + "(Audio)");
|
||||
var _path = Path.Combine(downDir + "(Audio)", "iqAudio.m3u8");
|
||||
var _pathInit = Path.Combine(downDir + "(Audio)", "iqAudioInit.mp4");
|
||||
File.Copy(new Uri(audioPath).LocalPath, _path, true);
|
||||
File.Copy(new Uri(audioInitPath).LocalPath, _pathInit, true);
|
||||
audioPath = new Uri(_path).ToString();
|
||||
content = $"#EXTM3U\r\n" +
|
||||
$"#EXT-X-MEDIA:TYPE=AUDIO,URI=\"{audioPath}\",GROUP-ID=\"default-audio-group\",NAME=\"stream_0\",AUTOSELECT=YES,CHANNELS=\"0\"\r\n" +
|
||||
$"#EXT-X-STREAM-INF:BANDWIDTH=99999,CODECS=\"{string.Join(",", codecsList)}\",RESOLUTION=0x0,AUDIO=\"default-audio-group\"\r\n" +
|
||||
$"{videoPath}";
|
||||
}
|
||||
|
||||
var _masterPath = Path.Combine(downDir, "master.m3u8");
|
||||
File.WriteAllText(_masterPath, content);
|
||||
return new Uri(_masterPath).ToString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,8 +11,6 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
class LOGGER
|
||||
{
|
||||
public static int CursorIndex = 5;
|
||||
public static int FFmpegCorsorIndex = 5;
|
||||
public const int Default = 1;
|
||||
public const int Error = 2;
|
||||
public const int Warning = 3;
|
||||
@@ -36,8 +34,13 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
if (!Directory.Exists(Path.GetDirectoryName(LOGFILE)))//若文件夹不存在则新建文件夹
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(LOGFILE)); //新建文件夹
|
||||
if (File.Exists(LOGFILE))//若文件存在则删除
|
||||
File.Delete(LOGFILE);
|
||||
//若文件存在则加序号
|
||||
int index = 1;
|
||||
var fileName = Path.GetFileNameWithoutExtension(LOGFILE);
|
||||
while (File.Exists(LOGFILE))
|
||||
{
|
||||
LOGFILE = Path.Combine(Path.GetDirectoryName(LOGFILE), $"{fileName}-{index++}.log");
|
||||
}
|
||||
string file = LOGFILE;
|
||||
string now = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
string init = "LOG " + DateTime.Now.ToString("yyyy/MM/dd") + "\r\n"
|
||||
@@ -57,40 +60,37 @@ namespace N_m3u8DL_CLI
|
||||
//读写锁机制,当资源被占用,其他线程等待
|
||||
static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
|
||||
|
||||
public static void PrintLine(string text, int printLevel = 1, int cursorIndex = 0)
|
||||
public static void PrintLine(string text, int printLevel = 1)
|
||||
{
|
||||
int windowWith = 63;
|
||||
try
|
||||
{
|
||||
if (CursorIndex > 1000)
|
||||
{
|
||||
Console.Clear();
|
||||
CursorIndex = 0;
|
||||
}
|
||||
if (cursorIndex == 0)
|
||||
Console.SetCursorPosition(0, CursorIndex++);
|
||||
else
|
||||
Console.SetCursorPosition(0, cursorIndex);
|
||||
windowWith = Console.WindowWidth;
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception e)
|
||||
{
|
||||
;
|
||||
// empty
|
||||
}
|
||||
switch (printLevel)
|
||||
{
|
||||
case 0:
|
||||
Console.Write("\r" + new string(' ', windowWith - 1) + "\r");
|
||||
Console.WriteLine(" ".PadRight(12) + " " + text);
|
||||
break;
|
||||
case 1:
|
||||
Console.Write("\r" + new string(' ', windowWith - 1) + "\r");
|
||||
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff") + " ");
|
||||
Console.WriteLine(text);
|
||||
break;
|
||||
case 2:
|
||||
Console.Write("\r" + new string(' ', windowWith - 1) + "\r");
|
||||
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff") + " ");
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(text);
|
||||
Console.ResetColor();
|
||||
break;
|
||||
case 3:
|
||||
Console.Write("\r" + new string(' ', windowWith - 1) + "\r");
|
||||
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff") + " ");
|
||||
Console.ForegroundColor = ConsoleColor.DarkYellow;
|
||||
Console.WriteLine(text);
|
||||
@@ -118,7 +118,7 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -143,7 +143,7 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -156,7 +156,7 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(DateTime.Now.ToString("o") + " " + text);
|
||||
while (Console.ForegroundColor == ConsoleColor.Red)
|
||||
while (Console.ForegroundColor == ConsoleColor.Red)
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
@@ -14,6 +15,7 @@ namespace N_m3u8DL_CLI
|
||||
//code from https://github.com/ytdl-org/youtube-dl/blob/master/youtube_dl/extractor/common.py#L2076
|
||||
class MPDParser
|
||||
{
|
||||
private static string MPD_URL;
|
||||
static Dictionary<string, dynamic> ExtractMultisegmentInfo(XmlElement Period, XmlNamespaceManager nsMgr, Dictionary<string, dynamic> info)
|
||||
{
|
||||
var MultisegmentInfo = new Dictionary<string, dynamic>(info);
|
||||
@@ -127,6 +129,7 @@ namespace N_m3u8DL_CLI
|
||||
/// <returns></returns>
|
||||
public static string Parse(string downDir, string mpdUrl, string mpdContent, string defaultBase = "")
|
||||
{
|
||||
MPD_URL = mpdUrl;
|
||||
//XiGua
|
||||
if (mpdContent.Contains("<mas:") && !mpdContent.Contains("xmlns:mas"))
|
||||
mpdContent = mpdContent.Replace("<MPD ", "<MPD xmlns:mas=\"urn:marlin:mas:1-0:services:schemas:mpd\" ");
|
||||
@@ -134,7 +137,16 @@ namespace N_m3u8DL_CLI
|
||||
XmlDocument mpdDoc = new XmlDocument();
|
||||
mpdDoc.LoadXml(mpdContent);
|
||||
|
||||
XmlNode xn = mpdDoc.LastChild;
|
||||
XmlNode xn = null;
|
||||
//Select MPD node
|
||||
foreach (XmlNode node in mpdDoc.ChildNodes)
|
||||
{
|
||||
if (node.NodeType == XmlNodeType.Element && node.Name == "MPD")
|
||||
{
|
||||
xn = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var mediaPresentationDuration = ((XmlElement)xn).GetAttribute("mediaPresentationDuration");
|
||||
var ns = ((XmlElement)xn).GetAttribute("xmlns");
|
||||
|
||||
@@ -143,10 +155,28 @@ namespace N_m3u8DL_CLI
|
||||
|
||||
TimeSpan ts = XmlConvert.ToTimeSpan(mediaPresentationDuration); //时长
|
||||
|
||||
//读取在MPD开头定义的<BaseURL>,并替换本身的URL
|
||||
var baseNode = xn.SelectSingleNode("ns:BaseURL", nsMgr);
|
||||
if (baseNode != null)
|
||||
{
|
||||
if (MPD_URL.Contains("kkbox.com.tw/"))
|
||||
{
|
||||
var badUrl = baseNode.InnerText;
|
||||
var goodUrl = badUrl.Replace("//https:%2F%2F", "//");
|
||||
MPD_URL = mpdUrl = goodUrl;
|
||||
}
|
||||
else
|
||||
{
|
||||
MPD_URL = mpdUrl = baseNode.InnerText;
|
||||
}
|
||||
}
|
||||
|
||||
var formatList = new List<Dictionary<string, dynamic>>(); //存放所有音视频清晰度
|
||||
var periodIndex = 0; //解决同一个period且同id导致被重复添加分片
|
||||
|
||||
foreach (XmlElement period in xn.SelectNodes("ns:Period", nsMgr))
|
||||
{
|
||||
periodIndex++;
|
||||
var periodDuration = string.IsNullOrEmpty(period.GetAttribute("duration")) ? XmlConvert.ToTimeSpan(mediaPresentationDuration) : XmlConvert.ToTimeSpan(period.GetAttribute("duration"));
|
||||
var periodMsInfo = ExtractMultisegmentInfo(period, nsMgr, new Dictionary<string, dynamic>()
|
||||
{
|
||||
@@ -216,13 +246,14 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
mpdBaseUrl += "/";
|
||||
}
|
||||
baseUrl = mpdBaseUrl + baseUrl;
|
||||
baseUrl = CombineURL(mpdBaseUrl, baseUrl);
|
||||
}
|
||||
var representationId = GetAttribute("id");
|
||||
var lang = GetAttribute("lang");
|
||||
var bandwidth = IntOrNull(GetAttribute("bandwidth"));
|
||||
var f = new Dictionary<string, dynamic>
|
||||
{
|
||||
["PeriodIndex"] = periodIndex,
|
||||
["ContentType"] = contentType,
|
||||
["FormatId"] = representationId,
|
||||
["ManifestUrl"] = mpdUrl,
|
||||
@@ -447,7 +478,7 @@ namespace N_m3u8DL_CLI
|
||||
f["FragmentBaseUrl"] = baseUrl;
|
||||
if (representationMsInfo.ContainsKey("InitializationUrl"))
|
||||
{
|
||||
f["InitializationUrl"] = representationMsInfo["InitializationUrl"];
|
||||
f["InitializationUrl"] = CombineURL(baseUrl, representationMsInfo["InitializationUrl"]);
|
||||
if (f["InitializationUrl"].StartsWith("$$Range"))
|
||||
{
|
||||
f["InitializationUrl"] = CombineURL(baseUrl, f["InitializationUrl"]);
|
||||
@@ -472,7 +503,8 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
for (int i = 0; i < formatList.Count; i++)
|
||||
{
|
||||
if (formatList[i]["FormatId"] == f["FormatId"] && formatList[i]["Width"] == f["Width"] && formatList[i]["ContentType"] == f["ContentType"])
|
||||
//参数相同但不在同一个Period才可以
|
||||
if (formatList[i]["FormatId"] == f["FormatId"] && formatList[i]["Width"] == f["Width"] && formatList[i]["ContentType"] == f["ContentType"] && formatList[i]["PeriodIndex"] != f["PeriodIndex"])
|
||||
{
|
||||
formatList[i]["Fragments"].AddRange(f["Fragments"]);
|
||||
break;
|
||||
@@ -509,7 +541,7 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
string Stringify(Dictionary<string, dynamic> f)
|
||||
{
|
||||
var type = f["ContentType"] == "aduio" ? "Audio" : "Video";
|
||||
var type = f["ContentType"] == "audio" ? "Audio" : "Video";
|
||||
var res = type == "Video" ? $"[{f["Width"]}x{f["Height"]}]" : "";
|
||||
var id = $"[{f["FormatId"]}] ";
|
||||
var tbr = $"[{((int)f["Tbr"]).ToString().PadLeft(4)} Kbps] ";
|
||||
@@ -520,17 +552,30 @@ namespace N_m3u8DL_CLI
|
||||
return $"{type} => {id}{tbr}{asr}{fps}{lang}{codecs}{res}";
|
||||
}
|
||||
|
||||
var startCursorIndex = Console.CursorTop;
|
||||
var cursorIndex = startCursorIndex;
|
||||
for (int i = 0; i < formatList.Count; i++)
|
||||
{
|
||||
Console.WriteLine("".PadRight(13) + $"[{i.ToString().PadLeft(2)}]. {Stringify(formatList[i])}");
|
||||
LOGGER.CursorIndex++;
|
||||
cursorIndex++;
|
||||
}
|
||||
Console.CursorVisible = true;
|
||||
LOGGER.PrintLine("Found Multiple Language Audio Tracks.\r\n" + "".PadRight(13) + "Please Select What You Want(Up to 1 Video and 1 Audio).");
|
||||
Console.Write("".PadRight(13) + "Enter Numbers Separated By A Space: ");
|
||||
var input = Console.ReadLine();
|
||||
LOGGER.CursorIndex += 2;
|
||||
Console.CursorVisible = false;
|
||||
cursorIndex += 2;
|
||||
try
|
||||
{
|
||||
for (int i = startCursorIndex; i < cursorIndex; i++)
|
||||
{
|
||||
Console.SetCursorPosition(0, i);
|
||||
Console.Write("".PadRight(300));
|
||||
}
|
||||
Console.SetCursorPosition(0, startCursorIndex);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
bestVideo = new Dictionary<string, dynamic>() { ["Tbr"] = 0 };
|
||||
@@ -606,7 +651,7 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
|
||||
var content = "";
|
||||
if (videoPath == "" && audioPath != "")
|
||||
if ((videoPath == "" && audioPath != "") || Global.VIDEO_TYPE == "IGNORE")
|
||||
{
|
||||
return audioPath;
|
||||
}
|
||||
@@ -652,13 +697,17 @@ namespace N_m3u8DL_CLI
|
||||
if (f.ContainsKey("InitializationUrl"))
|
||||
{
|
||||
string initUrl = f["InitializationUrl"];
|
||||
if (MPD_URL.Contains("?") && MPD_URL.Contains(".kakao.com/"))
|
||||
{
|
||||
initUrl += new Regex("\\?.*").Match(MPD_URL).Value;
|
||||
}
|
||||
if (Regex.IsMatch(initUrl, "\\$\\$Range=(\\d+)-(\\d+)"))
|
||||
{
|
||||
var match = Regex.Match(initUrl, "\\$\\$Range=(\\d+)-(\\d+)");
|
||||
string rangeStr = match.Value;
|
||||
long start = Convert.ToInt64(match.Groups[1].Value);
|
||||
long end = Convert.ToInt64(match.Groups[2].Value);
|
||||
sb.AppendLine($"#EXT-X-MAP:URI=\"{initUrl.Replace(rangeStr, "")}\",BYTERANGE=\"{end}@{start}\"");
|
||||
sb.AppendLine($"#EXT-X-MAP:URI=\"{initUrl.Replace(rangeStr, "")}\",BYTERANGE=\"{end + 1 - start}@{start}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -667,11 +716,53 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
sb.AppendLine("#EXT-X-KEY:METHOD=PLZ-KEEP-RAW,URI=\"None\""); //使下载器使用二进制合并
|
||||
|
||||
List<Dictionary<string, dynamic>> fragments = f["Fragments"];
|
||||
|
||||
//检测最后一片的有效性
|
||||
if (fragments.Count > 1)
|
||||
{
|
||||
bool checkValid(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri(url));
|
||||
request.Timeout = 120000;
|
||||
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
|
||||
if (((int)response.StatusCode).ToString().StartsWith("2")) return true;
|
||||
else return false;
|
||||
}
|
||||
catch (Exception) { return false; }
|
||||
}
|
||||
|
||||
var last = fragments.Last();
|
||||
var secondToLast = fragments[fragments.Count - 2];
|
||||
var urlLast = last.ContainsKey("url") ? last["url"] : last["path"];
|
||||
var urlSecondToLast = secondToLast.ContainsKey("url") ? secondToLast["url"] : secondToLast["path"];
|
||||
if (MPD_URL.Contains("?") && MPD_URL.Contains(".kakao.com/"))
|
||||
{
|
||||
urlLast += new Regex("\\?.*").Match(MPD_URL).Value;
|
||||
urlSecondToLast += new Regex("\\?.*").Match(MPD_URL).Value;
|
||||
}
|
||||
//普通分段才判断
|
||||
if (urlLast.StartsWith("http") && !Regex.IsMatch(urlLast, "\\$\\$Range=(\\d+)-(\\d+)"))
|
||||
{
|
||||
LOGGER.PrintLine(strings.checkingLast + (f["ContentType"] != "audio" ? "(Video)" : "(Audio)"));
|
||||
LOGGER.WriteLine(strings.checkingLast + (f["ContentType"] != "audio" ? "(Video)" : "(Audio)"));
|
||||
//倒数第二段正常,倒数第一段不正常
|
||||
if (checkValid(urlSecondToLast) && !checkValid(urlLast))
|
||||
fragments.RemoveAt(fragments.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
//添加分段
|
||||
foreach (var seg in f["Fragments"])
|
||||
foreach (var seg in fragments)
|
||||
{
|
||||
var dur = seg.ContainsKey("duration") ? seg["duration"] : 0.0;
|
||||
var url = seg.ContainsKey("url") ? seg["url"] : seg["path"];
|
||||
if (MPD_URL.Contains("?") && MPD_URL.Contains(".kakao.com/"))
|
||||
{
|
||||
url += new Regex("\\?.*").Match(MPD_URL).Value;
|
||||
}
|
||||
sb.AppendLine($"#EXTINF:{dur.ToString("0.00")}");
|
||||
if (Regex.IsMatch(url, "\\$\\$Range=(\\d+)-(\\d+)"))
|
||||
{
|
||||
|
110
N_m3u8DL-CLI/MyOptions.cs
Normal file
110
N_m3u8DL-CLI/MyOptions.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using CommandLine;
|
||||
using CommandLine.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
internal class MyOptions
|
||||
{
|
||||
[Value(0, Hidden = true, MetaName = "Input Source", HelpText = "Help_input", ResourceType = typeof(strings))]
|
||||
public string Input { get; set; }
|
||||
|
||||
[Option("workDir", HelpText = "Help_workDir", ResourceType = typeof(strings))]
|
||||
public string WorkDir { get; set; }
|
||||
|
||||
[Option("saveName", HelpText = "Help_saveName", ResourceType = typeof(strings))]
|
||||
public string SaveName { get; set; } = "";
|
||||
|
||||
[Option("baseUrl", HelpText = "Help_baseUrl", ResourceType = typeof(strings))]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[Option("headers", HelpText = "Help_headers", ResourceType = typeof(strings))]
|
||||
public string Headers { get; set; } = "";
|
||||
|
||||
[Option("maxThreads", Default = 32U, HelpText = "Help_maxThreads", ResourceType = typeof(strings))]
|
||||
public uint MaxThreads { get; set; }
|
||||
|
||||
[Option("minThreads", Default = 16U, HelpText = "Help_minThreads", ResourceType = typeof(strings))]
|
||||
public uint MinThreads { get; set; }
|
||||
|
||||
[Option("retryCount", Default = 15U, HelpText = "Help_retryCount", ResourceType = typeof(strings))]
|
||||
public uint RetryCount { get; set; }
|
||||
|
||||
[Option("timeOut", Default = 10U, HelpText = "Help_timeOut", ResourceType = typeof(strings))]
|
||||
public uint TimeOut { get; set; }
|
||||
|
||||
[Option("muxSetJson", HelpText = "Help_muxSetJson", ResourceType = typeof(strings))]
|
||||
public string MuxSetJson { get; set; }
|
||||
|
||||
[Option("useKeyFile", HelpText = "Help_useKeyFile", ResourceType = typeof(strings))]
|
||||
public string UseKeyFile { get; set; }
|
||||
|
||||
[Option("useKeyBase64", HelpText = "Help_useKeyBase64", ResourceType = typeof(strings))]
|
||||
public string UseKeyBase64 { get; set; }
|
||||
|
||||
[Option("useKeyIV", HelpText = "Help_useKeyIV", ResourceType = typeof(strings))]
|
||||
public string UseKeyIV { get; set; }
|
||||
|
||||
[Option("downloadRange", HelpText = "Help_downloadRange", ResourceType = typeof(strings))]
|
||||
public string DownloadRange { get; set; }
|
||||
|
||||
[Option("liveRecDur", HelpText = "Help_liveRecDur", ResourceType = typeof(strings))]
|
||||
public string LiveRecDur { get; set; }
|
||||
|
||||
[Option("stopSpeed", HelpText = "Help_stopSpeed", ResourceType = typeof(strings))]
|
||||
public long StopSpeed { get; set; } = 0L;
|
||||
|
||||
[Option("maxSpeed", HelpText = "Help_maxSpeed", ResourceType = typeof(strings))]
|
||||
public long MaxSpeed { get; set; } = 0L;
|
||||
|
||||
[Option("proxyAddress", HelpText = "Help_proxyAddress", ResourceType = typeof(strings))]
|
||||
public string ProxyAddress { get; set; }
|
||||
|
||||
[Option("enableDelAfterDone", HelpText = "Help_enableDelAfterDone", ResourceType = typeof(strings))]
|
||||
public bool EnableDelAfterDone { get; set; }
|
||||
|
||||
[Option("enableMuxFastStart", HelpText = "Help_enableMuxFastStart", ResourceType = typeof(strings))]
|
||||
public bool EnableMuxFastStart { get; set; }
|
||||
|
||||
[Option("enableBinaryMerge", HelpText = "Help_enableBinaryMerge", ResourceType = typeof(strings))]
|
||||
public bool EnableBinaryMerge { get; set; }
|
||||
|
||||
[Option("enableParseOnly", HelpText = "Help_enableParseOnly", ResourceType = typeof(strings))]
|
||||
public bool EnableParseOnly { get; set; }
|
||||
|
||||
[Option("enableAudioOnly", HelpText = "Help_enableAudioOnly", ResourceType = typeof(strings))]
|
||||
public bool EnableAudioOnly { get; set; }
|
||||
|
||||
[Option("disableDateInfo", HelpText = "Help_disableDateInfo", ResourceType = typeof(strings))]
|
||||
public bool DisableDateInfo { get; set; }
|
||||
|
||||
[Option("disableIntegrityCheck", HelpText = "Help_disableIntegrityCheck", ResourceType = typeof(strings))]
|
||||
public bool DisableIntegrityCheck { get; set; }
|
||||
|
||||
[Option("noMerge", HelpText = "Help_noMerge", ResourceType = typeof(strings))]
|
||||
public bool NoMerge { get; set; }
|
||||
|
||||
[Option("noProxy", HelpText = "Help_noProxy", ResourceType = typeof(strings))]
|
||||
public bool NoProxy { get; set; }
|
||||
|
||||
[Option("registerUrlProtocol", HelpText = "Help_registerUrlProtocol", ResourceType = typeof(strings))]
|
||||
public bool RegisterUrlProtocol { get; set; }
|
||||
|
||||
[Option("unregisterUrlProtocol", HelpText = "Help_unregisterUrlProtocol", ResourceType = typeof(strings))]
|
||||
public bool UnregisterUrlProtocol { get; set; }
|
||||
|
||||
[Option("enableChaCha20", HelpText = "enableChaCha20")]
|
||||
public bool EnableChaCha20 { get; set; }
|
||||
|
||||
[Option("chaCha20KeyBase64", HelpText = "ChaCha20KeyBase64")]
|
||||
public string ChaCha20KeyBase64 { get; set; }
|
||||
|
||||
[Option("chaCha20NonceBase64", HelpText = "ChaCha20NonceBase64")]
|
||||
public string ChaCha20NonceBase64 { get; set; }
|
||||
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props" Condition="Exists('..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props')" />
|
||||
<Import Project="..\packages\Resource.Embedder.2.1.1\build\Resource.Embedder.props" Condition="Exists('..\packages\Resource.Embedder.2.1.1\build\Resource.Embedder.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@@ -41,13 +42,27 @@
|
||||
<ApplicationIcon>logo_3Iv_icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BrotliSharpLib, Version=0.3.2.0, Culture=neutral, PublicKeyToken=3f4e2a1cd615fcb7, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\BrotliSharpLib.0.3.3\lib\net451\BrotliSharpLib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="CommandLine, Version=2.8.0.0, Culture=neutral, PublicKeyToken=5a870481e358d379, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CommandLineParser.2.8.0\lib\net45\CommandLine.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Costura, Version=4.1.0.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Costura.Fody.4.1.0\lib\net40\Costura.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Build.Framework" />
|
||||
<Reference Include="Microsoft.Build.Utilities.v4.0" />
|
||||
<Reference Include="Microsoft.JScript" />
|
||||
<Reference Include="Microsoft.Win32.TaskScheduler, Version=2.8.7.0, Culture=neutral, PublicKeyToken=c416bc1b32d97233, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\TaskScheduler.2.8.7\lib\net452\Microsoft.Win32.TaskScheduler.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MihaZupan.HttpToSocks5Proxy, Version=1.4.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\HttpToSocks5Proxy.1.4.0\lib\net45\MihaZupan.HttpToSocks5Proxy.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
|
||||
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NiL.JS, Version=2.5.1428.0, Culture=neutral, PublicKeyToken=fa941a7c2a4de689, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NiL.JS.2.5.1428\lib\net45\NiL.JS.dll</HintPath>
|
||||
@@ -56,33 +71,45 @@
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Collections" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.IO" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="UACHelper, Version=1.3.0.4, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\UACHelper.1.3.0.5\lib\net40\UACHelper.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CommandLineArgument.cs" />
|
||||
<Compile Include="CommandLineArgumentParser.cs" />
|
||||
<Compile Include="CSChaCha20.cs" />
|
||||
<Compile Include="Decode51CtoKey.cs" />
|
||||
<Compile Include="DecodeCdeledu.cs" />
|
||||
<Compile Include="DecodeDdyun.cs" />
|
||||
<Compile Include="DecodeHuke88Key.cs" />
|
||||
<Compile Include="DecodeImooc.cs" />
|
||||
<Compile Include="DecodeNfmovies.cs" />
|
||||
<Compile Include="Decrypter.cs" />
|
||||
<Compile Include="DecodeNfmovies.cs" />
|
||||
<Compile Include="FFmpeg.cs" />
|
||||
<Compile Include="Global.cs" />
|
||||
<Compile Include="HLSLiveDownloader.cs" />
|
||||
<Compile Include="HLSTags.cs" />
|
||||
<Compile Include="IqJsonParser.cs" />
|
||||
<Compile Include="LOGGER.cs" />
|
||||
<Compile Include="DownloadManager.cs" />
|
||||
<Compile Include="MPDParser.cs" />
|
||||
<Compile Include="MyOptions.cs" />
|
||||
<Compile Include="Parser.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="ProgressReporter.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Downloader.cs" />
|
||||
<Compile Include="strings.Designer.cs">
|
||||
@@ -124,25 +151,29 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="strings.en-US.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>strings.en-US.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="strings.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>strings.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="strings.zh-TW.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>strings.zh-TW.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="..\packages\Fody.6.0.0\build\Fody.targets" Condition="Exists('..\packages\Fody.6.0.0\build\Fody.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Resource.Embedder.2.1.1\build\Resource.Embedder.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Resource.Embedder.2.1.1\build\Resource.Embedder.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Fody.6.0.0\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.6.0.0\build\Fody.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
<Import Project="..\packages\Fody.6.0.0\build\Fody.targets" Condition="Exists('..\packages\Fody.6.0.0\build\Fody.targets')" />
|
||||
</Project>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
39
N_m3u8DL-CLI/ProgressReporter.cs
Normal file
39
N_m3u8DL-CLI/ProgressReporter.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class ProgressReporter
|
||||
{
|
||||
private static string speed = "";
|
||||
private static string progress = "";
|
||||
|
||||
static object lockThis = new object();
|
||||
public static void Report(string progress, string speed)
|
||||
{
|
||||
lock (lockThis)
|
||||
{
|
||||
int windowWith = 63;
|
||||
try
|
||||
{
|
||||
windowWith = Console.WindowWidth;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// empty
|
||||
}
|
||||
if (!string.IsNullOrEmpty(progress)) ProgressReporter.progress = progress;
|
||||
if (!string.IsNullOrEmpty(speed)) ProgressReporter.speed = speed;
|
||||
string now = DateTime.Now.ToString("HH:mm:ss.000");
|
||||
var sub = windowWith - 4 - ProgressReporter.progress.Length - ProgressReporter.speed.Length - now.Length;
|
||||
if (sub <= 0) sub = 0;
|
||||
string print = now + " " + ProgressReporter.progress + " " + ProgressReporter.speed + new string(' ', sub);
|
||||
Console.Write("\r" + print + "\r");
|
||||
//Console.Write(print);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("nilaoda")]
|
||||
[assembly: AssemblyProduct("N_m3u8DL-CLI")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2020")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2022")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
||||
// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
|
||||
// 方法是按如下所示使用“*”: :
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.7.0.0")]
|
||||
[assembly: AssemblyVersion("3.0.2.0")]
|
||||
[assembly: AssemblyFileVersion("3.0.2.0")]
|
||||
|
@@ -59,10 +59,12 @@ namespace N_m3u8DL_CLI
|
||||
//Console.Title = Now + " / " + Total;
|
||||
string downloadedSize = Global.FormatFileSize(DownloadManager.DownloadedSize);
|
||||
string estimatedSize = Global.FormatFileSize(DownloadManager.DownloadedSize * total / now);
|
||||
int padding = downloadedSize.Length > estimatedSize.Length ? downloadedSize.Length : estimatedSize.Length;
|
||||
DownloadManager.ToDoSize = (DownloadManager.DownloadedSize * total / now) - DownloadManager.DownloadedSize;
|
||||
string percent = (Convert.ToDouble(now) / Convert.ToDouble(total) * 100).ToString("0.00") + "%";
|
||||
Console.SetCursorPosition(0, 2);
|
||||
Console.Write(("Progress: " + Now + " of " + Total
|
||||
+ $" ({percent}/{downloadedSize}/{estimatedSize}/{Global.FormatTime(Convert.ToInt32(TotalDuration))})").PadRight(62));
|
||||
var print = "Progress: " + Now + "/" + Total
|
||||
+ $" ({percent}) -- {downloadedSize.PadLeft(padding)}/{estimatedSize.PadRight(padding)}";
|
||||
ProgressReporter.Report(print, "");
|
||||
}
|
||||
|
||||
private void OnRenamed(object source, RenamedEventArgs e)
|
||||
@@ -77,10 +79,12 @@ namespace N_m3u8DL_CLI
|
||||
//Console.Title = Now + " / " + Total;
|
||||
string downloadedSize = Global.FormatFileSize(DownloadManager.DownloadedSize);
|
||||
string estimatedSize = Global.FormatFileSize(DownloadManager.DownloadedSize * total / now);
|
||||
int padding = downloadedSize.Length > estimatedSize.Length ? downloadedSize.Length : estimatedSize.Length;
|
||||
DownloadManager.ToDoSize = (DownloadManager.DownloadedSize * total / now) - DownloadManager.DownloadedSize;
|
||||
string percent = (Convert.ToDouble(now) / Convert.ToDouble(total) * 100).ToString("0.00") + "%";
|
||||
Console.SetCursorPosition(0, 2);
|
||||
Console.Write(("Progress: " + Now + " of " + Total
|
||||
+ $" ({percent}/{downloadedSize}/{estimatedSize}/{Global.FormatTime(Convert.ToInt32(TotalDuration))})").PadRight(62));
|
||||
var print = "Progress: " + Now + "/" + Total
|
||||
+ $" ({percent}) -- {downloadedSize.PadLeft(padding)}/{estimatedSize.PadRight(padding)}";
|
||||
ProgressReporter.Report(print, "");
|
||||
}
|
||||
|
||||
private void OnDeleted(object source, FileSystemEventArgs e)
|
||||
@@ -95,10 +99,12 @@ namespace N_m3u8DL_CLI
|
||||
//Console.Title = Now + " / " + Total;
|
||||
string downloadedSize = Global.FormatFileSize(DownloadManager.DownloadedSize);
|
||||
string estimatedSize = Global.FormatFileSize(DownloadManager.DownloadedSize * total / now);
|
||||
int padding = downloadedSize.Length > estimatedSize.Length ? downloadedSize.Length : estimatedSize.Length;
|
||||
DownloadManager.ToDoSize = (DownloadManager.DownloadedSize * total / now) - DownloadManager.DownloadedSize;
|
||||
string percent = (Convert.ToDouble(now) / Convert.ToDouble(total) * 100).ToString("0.00") + "%";
|
||||
Console.SetCursorPosition(0, 2);
|
||||
Console.Write(("Progress: " + Now + " of " + Total
|
||||
+ $" ({percent}/{downloadedSize}/{estimatedSize}/{Global.FormatTime(Convert.ToInt32(TotalDuration))})").PadRight(62));
|
||||
var print = "Progress: " + Now + "/" + Total
|
||||
+ $" ({percent}) -- {downloadedSize.PadLeft(padding)}/{estimatedSize.PadRight(padding)}";
|
||||
ProgressReporter.Report(print, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -308,4 +308,55 @@
|
||||
- 支持解密虎课网
|
||||
2021年1月18日
|
||||
- 完善MPD下载相关
|
||||
- 重新打包多语言资源
|
||||
- 重新打包多语言资源
|
||||
2021年1月24日
|
||||
- 适配Disney+资源
|
||||
- MPD选择流行为优化
|
||||
- 修复二进制合并时vtt字幕被合并为ts后缀问题
|
||||
2021年2月1日
|
||||
- 修正自定义KEY且存在IV时的隐患
|
||||
- 优化跳过PNG Header的算法
|
||||
2021年2月2日
|
||||
- 独播库自动加入referer
|
||||
- 修复气球云
|
||||
2021年2月10日
|
||||
- 修正MPD拼接BaseUrl逻辑
|
||||
2021年2月11日
|
||||
- 将CNTV视频修改为未加密链接
|
||||
2021年2月21日
|
||||
- MPD检测最后一个分片是否有效
|
||||
2021年2月22日
|
||||
- 添加用户网络代理支持,使用--proxyAddress指定代理地址。(@evanlabs)
|
||||
2021年3月3日
|
||||
- 修复M3U8选择音轨/字幕不生效问题
|
||||
- 外挂音轨时enableAudioOnly可仅下载音频
|
||||
- 移除气球云支持
|
||||
2021年3月15日
|
||||
- 修复enableAudioOnly且下载MPD文件时留下冗余(Audio)文件夹的情况
|
||||
2021年3月22日
|
||||
- 适配AppleTv资源
|
||||
2021年3月25日
|
||||
- 优化下载监控
|
||||
- 为下载分片增加了自动重试机制(3次)
|
||||
2021年3月27日
|
||||
- 优化显示输出
|
||||
- 增加ETA显示
|
||||
2021年6月27日
|
||||
- 修正判断png图片时可能出现的数组越界bug
|
||||
- 支持解压brotli(测试地址 https://www.baobuzz.com/m3u8/236963.m3u8?sign=811ae52382b7dd1d247f705e1bcaddf4)
|
||||
2021年7月4日
|
||||
- 优化master选择最高清晰度逻辑(大于改为大于等于)
|
||||
- 支持爱奇艺DRM-JSON自动转换为m3u8
|
||||
2021年8月15日
|
||||
- 优化显示输出
|
||||
- 强校验MAP下载成功
|
||||
2021年9月5日
|
||||
- 修复MPD节点选择BUG
|
||||
- 修复速度输出padding负值问题
|
||||
- 修复同一个period且同id导致被重复添加分片
|
||||
- 优化AppleTV判断
|
||||
2021年10月19日
|
||||
- 修复选择清晰度在输入选项后界面异常问题
|
||||
- 修复日志冲突问题
|
||||
2021年11月12日
|
||||
- 修复init url缺失baseurl问题
|
@@ -1,7 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Fody" version="6.0.0" targetFramework="net46" developmentDependency="true" />
|
||||
<package id="BrotliSharpLib" version="0.3.3" targetFramework="net46" />
|
||||
<package id="CommandLineParser" version="2.8.0" targetFramework="net46" />
|
||||
<package id="Costura.Fody" version="4.1.0" targetFramework="net46" />
|
||||
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net46" />
|
||||
<package id="Fody" version="6.0.0" targetFramework="net46" developmentDependency="true" />
|
||||
<package id="HttpToSocks5Proxy" version="1.4.0" targetFramework="net46" />
|
||||
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net46" />
|
||||
<package id="NiL.JS" version="2.5.1428" targetFramework="net46" />
|
||||
<package id="Resource.Embedder" version="2.1.1" targetFramework="net46" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net46" />
|
||||
<package id="TaskScheduler" version="2.8.7" targetFramework="net46" />
|
||||
<package id="UACHelper" version="1.3.0.5" targetFramework="net46" />
|
||||
</packages>
|
1378
N_m3u8DL-CLI/strings.Designer.cs
generated
1378
N_m3u8DL-CLI/strings.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
@@ -183,34 +183,6 @@
|
||||
<data name="hasExternalSubtitleTrack" xml:space="preserve">
|
||||
<value>Has External Subtitle Track</value>
|
||||
</data>
|
||||
<data name="helpInfo" xml:space="preserve">
|
||||
<value>N_m3u8DL-CLI.exe <URL|JSON|FILE> [OPTIONS]
|
||||
|
||||
--workDir Directory Set work dir (Video will be here)
|
||||
--saveName Filename Set save name(Exclude extention)
|
||||
--baseUrl BaseUrl Set Baseurl
|
||||
--headers headers Set HTTP headers,format: key:value user | split all key&value
|
||||
--maxThreads Thread Set max thread(default: 32)
|
||||
--minThreads Thread Set min thread(default: 16)
|
||||
--retryCount Count Set retry times(default: 15)
|
||||
--timeOut Sec Set timeout for http request(second,default: 10)
|
||||
--muxSetJson File Set a json file for mux
|
||||
--useKeyFile File Use 16 bytes file as KEY for AES-128 decryption
|
||||
--useKeyBase64 Base64String Use Base64 String as KEY for AES-128 decryption
|
||||
--useKeyIV HEXString Use HEX String as IV for AES-128 decryption
|
||||
--downloadRange Range Set range for a video
|
||||
--stopSpeed Number Speed below this, retry(KB/s)
|
||||
--maxSpeed Number Set max download speed(KB/s)
|
||||
--enableDelAfterDone Enable delete clips after download completed
|
||||
--enableMuxFastStart Enable fast start for mp4
|
||||
--enableBinaryMerge Enable use binary merge instead ffmpeg
|
||||
--enableParseOnly Enable parse mode
|
||||
--enableAudioOnly Enable only audio track when mux use ffmpeg
|
||||
--disableDateInfo Disable write date info when mux use ffmpeg
|
||||
--noMerge Disable auto merge
|
||||
--noProxy Disable use system proxy
|
||||
--disableIntegrityCheck Disable integrity check</value>
|
||||
</data>
|
||||
<data name="invalidM3u8" xml:space="preserve">
|
||||
<value>Invalid M3u8 (NOT Contain #EXTM3U)</value>
|
||||
</data>
|
||||
@@ -298,4 +270,109 @@
|
||||
<data name="wrtingMeta" xml:space="preserve">
|
||||
<value>Writing Json: [meta.json]</value>
|
||||
</data>
|
||||
<data name="startParsingMpd" xml:space="preserve">
|
||||
<value>Start Parsing MPD Content...</value>
|
||||
</data>
|
||||
<data name="checkingLast" xml:space="preserve">
|
||||
<value>Checking Whether The Last Fragment Is Valid...</value>
|
||||
</data>
|
||||
<data name="Help_workDir" xml:space="preserve">
|
||||
<value>Set work dir (Video will be here)</value>
|
||||
</data>
|
||||
<data name="Help_saveName" xml:space="preserve">
|
||||
<value>Set save name(Exclude extention)</value>
|
||||
</data>
|
||||
<data name="Help_input" xml:space="preserve">
|
||||
<value>URL|File|JSON</value>
|
||||
</data>
|
||||
<data name="Help_baseUrl" xml:space="preserve">
|
||||
<value>Set Baseurl</value>
|
||||
</data>
|
||||
<data name="Help_headers" xml:space="preserve">
|
||||
<value>Set HTTP headers,format: key:value use | split all key&value</value>
|
||||
</data>
|
||||
<data name="Help_maxThreads" xml:space="preserve">
|
||||
<value>Set max thread</value>
|
||||
</data>
|
||||
<data name="Help_minThreads" xml:space="preserve">
|
||||
<value>Set min thread</value>
|
||||
</data>
|
||||
<data name="Help_retryCount" xml:space="preserve">
|
||||
<value>Set retry times</value>
|
||||
</data>
|
||||
<data name="Help_timeOut" xml:space="preserve">
|
||||
<value>Set timeout for http request(second)</value>
|
||||
</data>
|
||||
<data name="Help_muxSetJson" xml:space="preserve">
|
||||
<value>Set a json file for mux</value>
|
||||
</data>
|
||||
<data name="Help_useKeyFile" xml:space="preserve">
|
||||
<value>Use 16 bytes file as KEY for AES-128 decryption</value>
|
||||
</data>
|
||||
<data name="Help_useKeyBase64" xml:space="preserve">
|
||||
<value>Use Base64 String as KEY for AES-128 decryption</value>
|
||||
</data>
|
||||
<data name="Help_useKeyIV" xml:space="preserve">
|
||||
<value>Use HEX String as IV for AES-128 decryption</value>
|
||||
</data>
|
||||
<data name="Help_downloadRange" xml:space="preserve">
|
||||
<value>Set range for a video</value>
|
||||
</data>
|
||||
<data name="Help_liveRecDur" xml:space="preserve">
|
||||
<value>When the live recording reaches this length, the software will exit automatically(HH:MM:SS)</value>
|
||||
</data>
|
||||
<data name="Help_stopSpeed" xml:space="preserve">
|
||||
<value>Speed below this, retry(KB/s)</value>
|
||||
</data>
|
||||
<data name="Help_maxSpeed" xml:space="preserve">
|
||||
<value>Set max download speed(KB/s)</value>
|
||||
</data>
|
||||
<data name="Help_proxyAddress" xml:space="preserve">
|
||||
<value>Set HTTP/SOCKS5 Proxy, like http://127.0.0.1:8080</value>
|
||||
</data>
|
||||
<data name="Help_enableDelAfterDone" xml:space="preserve">
|
||||
<value>Enable delete clips after download completed</value>
|
||||
</data>
|
||||
<data name="Help_enableMuxFastStart" xml:space="preserve">
|
||||
<value>Enable fast start for mp4</value>
|
||||
</data>
|
||||
<data name="Help_enableBinaryMerge" xml:space="preserve">
|
||||
<value>Enable use binary merge instead of ffmpeg</value>
|
||||
</data>
|
||||
<data name="Help_enableParseOnly" xml:space="preserve">
|
||||
<value>Enable parse only mode</value>
|
||||
</data>
|
||||
<data name="Help_enableAudioOnly" xml:space="preserve">
|
||||
<value>Enable only audio track when mux use ffmpeg</value>
|
||||
</data>
|
||||
<data name="Help_disableDateInfo" xml:space="preserve">
|
||||
<value>Disable write date info when mux use ffmpeg</value>
|
||||
</data>
|
||||
<data name="Help_noMerge" xml:space="preserve">
|
||||
<value>Disable auto merge</value>
|
||||
</data>
|
||||
<data name="Help_noProxy" xml:space="preserve">
|
||||
<value>Disable use system proxy</value>
|
||||
</data>
|
||||
<data name="Help_disableIntegrityCheck" xml:space="preserve">
|
||||
<value>Disable integrity check</value>
|
||||
</data>
|
||||
<data name="Help_registerUrlProtocol" xml:space="preserve">
|
||||
<value>Register m3u8dl URL protocol</value>
|
||||
</data>
|
||||
<data name="Help_unregisterUrlProtocol" xml:space="preserve">
|
||||
<value>Unregister m3u8dl URL protocol</value>
|
||||
</data>
|
||||
<data name="registerUrlProtocolFailed" xml:space="preserve">
|
||||
<value>Register m3u8dl URL protocol failed!</value>
|
||||
</data>
|
||||
<data name="registerUrlProtocolSuccessful" xml:space="preserve">
|
||||
<value>Register m3u8dl URL protocol successfully!</value>
|
||||
</data>
|
||||
<data name="unregisterUrlProtocolFailed" xml:space="preserve">
|
||||
<value>Unregister m3u8dl URL protocol failed!</value>
|
||||
</data>
|
||||
<data name="unregisterUrlProtocolSuccessful" xml:space="preserve">
|
||||
<value>Unregister m3u8dl URL protocol successfully!</value>
|
||||
</data>
|
||||
</root>
|
@@ -183,35 +183,6 @@
|
||||
<data name="hasExternalSubtitleTrack" xml:space="preserve">
|
||||
<value>识别到外挂字幕轨道</value>
|
||||
</data>
|
||||
<data name="helpInfo" xml:space="preserve">
|
||||
<value>N_m3u8DL-CLI.exe <URL|File|JSON> [OPTIONS]
|
||||
|
||||
--workDir Directory 设定程序工作目录
|
||||
--saveName Filename 设定存储文件名(不包括后缀)
|
||||
--baseUrl BaseUrl 设定Baseurl
|
||||
--headers headers 设定请求头,格式 key:value 使用|分割不同的key&value
|
||||
--maxThreads Thread 设定程序的最大线程数(默认为32)
|
||||
--minThreads Thread 设定程序的最小线程数(默认为16)
|
||||
--retryCount Count 设定程序的重试次数(默认为15)
|
||||
--timeOut Sec 设定程序网络请求的超时时间(单位为秒,默认为10秒)
|
||||
--muxSetJson File 使用外部json文件定义混流选项
|
||||
--useKeyFile File 使用外部16字节文件定义AES-128解密KEY
|
||||
--useKeyBase64 Base64String 使用Base64字符串定义AES-128解密KEY
|
||||
--useKeyIV HEXString 使用HEX字符串定义AES-128解密IV
|
||||
--downloadRange Range 仅下载视频的一部分分片或长度
|
||||
--liveRecDur HH:MM:SS 直播录制时,达到此长度自动退出软件
|
||||
--stopSpeed Number 当速度低于此值时,重试(单位为KB/s)
|
||||
--maxSpeed Number 设置下载速度上限(单位为KB/s)
|
||||
--enableDelAfterDone 开启下载后删除临时文件夹的功能
|
||||
--enableMuxFastStart 开启混流mp4的FastStart特性
|
||||
--enableBinaryMerge 开启二进制合并分片
|
||||
--enableParseOnly 开启仅解析模式(程序只进行到meta.json)
|
||||
--enableAudioOnly 合并时仅封装音频轨道
|
||||
--disableDateInfo 关闭混流中的日期写入
|
||||
--noMerge 禁用自动合并
|
||||
--noProxy 不自动使用系统代理
|
||||
--disableIntegrityCheck 不检测分片数量是否完整</value>
|
||||
</data>
|
||||
<data name="invalidM3u8" xml:space="preserve">
|
||||
<value>无效的m3u8</value>
|
||||
</data>
|
||||
@@ -299,4 +270,109 @@
|
||||
<data name="wrtingMeta" xml:space="preserve">
|
||||
<value>写出meta.json</value>
|
||||
</data>
|
||||
<data name="startParsingMpd" xml:space="preserve">
|
||||
<value>开始解析MPD内容...</value>
|
||||
</data>
|
||||
<data name="checkingLast" xml:space="preserve">
|
||||
<value>正在判断尾分片是否有效...</value>
|
||||
</data>
|
||||
<data name="Help_workDir" xml:space="preserve">
|
||||
<value>设定程序工作目录</value>
|
||||
</data>
|
||||
<data name="Help_saveName" xml:space="preserve">
|
||||
<value>设定存储文件名(不包括后缀)</value>
|
||||
</data>
|
||||
<data name="Help_input" xml:space="preserve">
|
||||
<value>链接或文件</value>
|
||||
</data>
|
||||
<data name="Help_baseUrl" xml:space="preserve">
|
||||
<value>设定Baseurl</value>
|
||||
</data>
|
||||
<data name="Help_headers" xml:space="preserve">
|
||||
<value>设定请求头,格式 key:value 使用|分割不同的key&value</value>
|
||||
</data>
|
||||
<data name="Help_maxThreads" xml:space="preserve">
|
||||
<value>设定程序的最大线程数</value>
|
||||
</data>
|
||||
<data name="Help_minThreads" xml:space="preserve">
|
||||
<value>设定程序的最小线程数</value>
|
||||
</data>
|
||||
<data name="Help_retryCount" xml:space="preserve">
|
||||
<value>设定程序的重试次数</value>
|
||||
</data>
|
||||
<data name="Help_timeOut" xml:space="preserve">
|
||||
<value>设定程序网络请求的超时时间(单位为秒)</value>
|
||||
</data>
|
||||
<data name="Help_muxSetJson" xml:space="preserve">
|
||||
<value>使用外部json文件定义混流选项</value>
|
||||
</data>
|
||||
<data name="Help_useKeyFile" xml:space="preserve">
|
||||
<value>使用外部16字节文件定义AES-128解密KEY</value>
|
||||
</data>
|
||||
<data name="Help_useKeyBase64" xml:space="preserve">
|
||||
<value>使用Base64字符串定义AES-128解密KEY</value>
|
||||
</data>
|
||||
<data name="Help_useKeyIV" xml:space="preserve">
|
||||
<value>使用HEX字符串定义AES-128解密IV</value>
|
||||
</data>
|
||||
<data name="Help_downloadRange" xml:space="preserve">
|
||||
<value>仅下载视频的一部分分片或长度</value>
|
||||
</data>
|
||||
<data name="Help_liveRecDur" xml:space="preserve">
|
||||
<value>直播录制时,达到此长度自动退出软件(HH:MM:SS)</value>
|
||||
</data>
|
||||
<data name="Help_stopSpeed" xml:space="preserve">
|
||||
<value>当速度低于此值时,重试(单位为KB/s)</value>
|
||||
</data>
|
||||
<data name="Help_maxSpeed" xml:space="preserve">
|
||||
<value>设置下载速度上限(单位为KB/s)</value>
|
||||
</data>
|
||||
<data name="Help_proxyAddress" xml:space="preserve">
|
||||
<value>设置HTTP/SOCKS5代理, 如 http://127.0.0.1:8080</value>
|
||||
</data>
|
||||
<data name="Help_enableDelAfterDone" xml:space="preserve">
|
||||
<value>开启下载后删除临时文件夹的功能</value>
|
||||
</data>
|
||||
<data name="Help_enableMuxFastStart" xml:space="preserve">
|
||||
<value>开启混流mp4的FastStart特性</value>
|
||||
</data>
|
||||
<data name="Help_enableBinaryMerge" xml:space="preserve">
|
||||
<value>开启二进制合并分片</value>
|
||||
</data>
|
||||
<data name="Help_enableParseOnly" xml:space="preserve">
|
||||
<value>开启仅解析模式(程序只进行到meta.json)</value>
|
||||
</data>
|
||||
<data name="Help_enableAudioOnly" xml:space="preserve">
|
||||
<value>合并时仅封装音频轨道</value>
|
||||
</data>
|
||||
<data name="Help_disableDateInfo" xml:space="preserve">
|
||||
<value>关闭混流中的日期写入</value>
|
||||
</data>
|
||||
<data name="Help_noMerge" xml:space="preserve">
|
||||
<value>禁用自动合并</value>
|
||||
</data>
|
||||
<data name="Help_noProxy" xml:space="preserve">
|
||||
<value>不自动使用系统代理</value>
|
||||
</data>
|
||||
<data name="Help_disableIntegrityCheck" xml:space="preserve">
|
||||
<value>不检测分片数量是否完整</value>
|
||||
</data>
|
||||
<data name="Help_registerUrlProtocol" xml:space="preserve">
|
||||
<value>注册m3u8dl链接协议</value>
|
||||
</data>
|
||||
<data name="Help_unregisterUrlProtocol" xml:space="preserve">
|
||||
<value>取消注册m3u8dl链接协议</value>
|
||||
</data>
|
||||
<data name="registerUrlProtocolFailed" xml:space="preserve">
|
||||
<value>注册m3u8dl链接协议失败!</value>
|
||||
</data>
|
||||
<data name="registerUrlProtocolSuccessful" xml:space="preserve">
|
||||
<value>注册m3u8dl链接协议成功!</value>
|
||||
</data>
|
||||
<data name="unregisterUrlProtocolFailed" xml:space="preserve">
|
||||
<value>取消注册m3u8dl链接协议失败!</value>
|
||||
</data>
|
||||
<data name="unregisterUrlProtocolSuccessful" xml:space="preserve">
|
||||
<value>取消注册m3u8dl链接协议成功!</value>
|
||||
</data>
|
||||
</root>
|
@@ -183,35 +183,6 @@
|
||||
<data name="hasExternalSubtitleTrack" xml:space="preserve">
|
||||
<value>識別到外掛字幕軌道</value>
|
||||
</data>
|
||||
<data name="helpInfo" xml:space="preserve">
|
||||
<value>N_m3u8DL-CLI.exe <URL|File|JSON> [OPTIONS]
|
||||
|
||||
--workDir Directory 設定程序工作目錄
|
||||
--saveName Filename 設定存儲文件名(不包括後綴)
|
||||
--baseUrl BaseUrl 設定Baseurl
|
||||
--headers headers 設定請求頭,格式 key:value 使用|分割不同的key&value
|
||||
--maxThreads Thread 設定程序的最大線程數(默認為32)
|
||||
--minThreads Thread 設定程序的最小線程數(默認為16)
|
||||
--retryCount Count 設定程序的重試次數(默認為15)
|
||||
--timeOut Sec 設定程序網絡請求的超時時間(單位為秒,默認為10秒)
|
||||
--muxSetJson File 使用外部json文件定義混流選項
|
||||
--useKeyFile File 使用外部16字節文件定義AES-128解密KEY
|
||||
--useKeyBase64 Base64String 使用Base64字符串定義AES-128解密KEY
|
||||
--useKeyIV HEXString 使用HEX字符串定義AES-128解密IV
|
||||
--downloadRange Range 僅下載視頻的壹部分分片或長度
|
||||
--liveRecDur HH:MM:SS 直播錄制時,達到此長度自動退出軟件
|
||||
--stopSpeed Number 當速度低於此值時,重試(單位為KB/s)
|
||||
--maxSpeed Number 設置下載速度上限(單位為KB/s)
|
||||
--enableDelAfterDone 開啟下載後刪除臨時文件夾的功能
|
||||
--enableMuxFastStart 開啟混流mp4的FastStart特性
|
||||
--enableBinaryMerge 開啟二進制合並分片
|
||||
--enableParseOnly 開啟僅解析模式(程序只進行到meta.json)
|
||||
--enableAudioOnly 合並時僅封裝音頻軌道
|
||||
--disableDateInfo 關閉混流中的日期寫入
|
||||
--noMerge 禁用自動合並
|
||||
--noProxy 不自動使用系統代理
|
||||
--disableIntegrityCheck 不檢測分片數量是否完整</value>
|
||||
</data>
|
||||
<data name="invalidM3u8" xml:space="preserve">
|
||||
<value>無效的m3u8</value>
|
||||
</data>
|
||||
@@ -299,4 +270,109 @@
|
||||
<data name="wrtingMeta" xml:space="preserve">
|
||||
<value>寫出meta.json</value>
|
||||
</data>
|
||||
<data name="startParsingMpd" xml:space="preserve">
|
||||
<value>開始解析MPD內容...</value>
|
||||
</data>
|
||||
<data name="checkingLast" xml:space="preserve">
|
||||
<value>正在判斷尾分片是否有效...</value>
|
||||
</data>
|
||||
<data name="Help_workDir" xml:space="preserve">
|
||||
<value>設定程式工作目錄</value>
|
||||
</data>
|
||||
<data name="Help_saveName" xml:space="preserve">
|
||||
<value>設定存儲文件名(不包括後綴)</value>
|
||||
</data>
|
||||
<data name="Help_input" xml:space="preserve">
|
||||
<value>連結或檔案</value>
|
||||
</data>
|
||||
<data name="Help_baseUrl" xml:space="preserve">
|
||||
<value>設定Baseurl</value>
|
||||
</data>
|
||||
<data name="Help_headers" xml:space="preserve">
|
||||
<value>設定請求頭,格式 key:value 使用|分割不同的key&value</value>
|
||||
</data>
|
||||
<data name="Help_maxThreads" xml:space="preserve">
|
||||
<value>設定程式的最大線程數</value>
|
||||
</data>
|
||||
<data name="Help_minThreads" xml:space="preserve">
|
||||
<value>設定程式的最小線程數</value>
|
||||
</data>
|
||||
<data name="Help_retryCount" xml:space="preserve">
|
||||
<value>設定程式的重試次數</value>
|
||||
</data>
|
||||
<data name="Help_timeOut" xml:space="preserve">
|
||||
<value>設定程式網絡請求的超時時間(單位為秒)</value>
|
||||
</data>
|
||||
<data name="Help_muxSetJson" xml:space="preserve">
|
||||
<value>使用外部json文件定義混流選項</value>
|
||||
</data>
|
||||
<data name="Help_useKeyFile" xml:space="preserve">
|
||||
<value>使用外部16字節文件定義AES-128解密KEY</value>
|
||||
</data>
|
||||
<data name="Help_useKeyBase64" xml:space="preserve">
|
||||
<value>使用Base64字符串定義AES-128解密KEY</value>
|
||||
</data>
|
||||
<data name="Help_useKeyIV" xml:space="preserve">
|
||||
<value>使用HEX字符串定義AES-128解密IV</value>
|
||||
</data>
|
||||
<data name="Help_downloadRange" xml:space="preserve">
|
||||
<value>僅下載視頻的壹部分分片或長度</value>
|
||||
</data>
|
||||
<data name="Help_liveRecDur" xml:space="preserve">
|
||||
<value>直播錄制時,達到此長度自動退出軟件(HH:MM:SS)</value>
|
||||
</data>
|
||||
<data name="Help_stopSpeed" xml:space="preserve">
|
||||
<value>當速度低於此值時,重試(單位為KB/s)</value>
|
||||
</data>
|
||||
<data name="Help_maxSpeed" xml:space="preserve">
|
||||
<value>設置下載速度上限(單位為KB/s)</value>
|
||||
</data>
|
||||
<data name="Help_proxyAddress" xml:space="preserve">
|
||||
<value>設置HTTP/SOCKS5代理, 如 http://127.0.0.1:8080</value>
|
||||
</data>
|
||||
<data name="Help_enableDelAfterDone" xml:space="preserve">
|
||||
<value>開啟下載後刪除臨時文件夾的功能</value>
|
||||
</data>
|
||||
<data name="Help_enableMuxFastStart" xml:space="preserve">
|
||||
<value>開啟混流mp4的FastStart特性</value>
|
||||
</data>
|
||||
<data name="Help_enableBinaryMerge" xml:space="preserve">
|
||||
<value>開啟二進制合並分片</value>
|
||||
</data>
|
||||
<data name="Help_enableParseOnly" xml:space="preserve">
|
||||
<value>開啟僅解析模式(程式只進行到meta.json)</value>
|
||||
</data>
|
||||
<data name="Help_enableAudioOnly" xml:space="preserve">
|
||||
<value>合並時僅封裝音頻軌道</value>
|
||||
</data>
|
||||
<data name="Help_disableDateInfo" xml:space="preserve">
|
||||
<value>關閉混流中的日期寫入</value>
|
||||
</data>
|
||||
<data name="Help_noMerge" xml:space="preserve">
|
||||
<value>禁用自動合並</value>
|
||||
</data>
|
||||
<data name="Help_noProxy" xml:space="preserve">
|
||||
<value>不自動使用系統代理</value>
|
||||
</data>
|
||||
<data name="Help_disableIntegrityCheck" xml:space="preserve">
|
||||
<value>不檢測分片數量是否完整</value>
|
||||
</data>
|
||||
<data name="Help_registerUrlProtocol" xml:space="preserve">
|
||||
<value>注册m3u8dl連結協定</value>
|
||||
</data>
|
||||
<data name="Help_unregisterUrlProtocol" xml:space="preserve">
|
||||
<value>取消注册m3u8dl連結協定</value>
|
||||
</data>
|
||||
<data name="registerUrlProtocolFailed" xml:space="preserve">
|
||||
<value>注册m3u8dl連結協定失敗!</value>
|
||||
</data>
|
||||
<data name="registerUrlProtocolSuccessful" xml:space="preserve">
|
||||
<value>注册m3u8dl連結協定成功!</value>
|
||||
</data>
|
||||
<data name="unregisterUrlProtocolFailed" xml:space="preserve">
|
||||
<value>取消注册m3u8dl連結協定失敗!</value>
|
||||
</data>
|
||||
<data name="unregisterUrlProtocolSuccessful" xml:space="preserve">
|
||||
<value>取消注册m3u8dl連結協定成功!</value>
|
||||
</data>
|
||||
</root>
|
108
README.md
108
README.md
@@ -13,20 +13,25 @@
|
||||
|
||||
|
||||
# [ENGLISH VERSION](https://github.com/nilaoda/N_m3u8DL-CLI/blob/master/README_ENG.md)
|
||||
|
||||
# 下载使用
|
||||
* 发行版: https://github.com/nilaoda/N_m3u8DL-CLI/releases
|
||||
* 自动构建版`(供测试)`: https://github.com/nilaoda/N_m3u8DL-CLI/actions
|
||||
|
||||
# 关于开源
|
||||
本项目已与2019年10月9日开源,采用MIT许可证,各取所需。
|
||||
本项目已于2019年10月9日开源,采用MIT许可证,各取所需。
|
||||
|
||||
# 关于跨平台
|
||||
~~本项目已通过`.NET Core`实现跨平台,理论支持Mac、Linux、Windows等平台,请移步:https://github.com/nilaoda/N_m3u8DL-CLI_Core~~
|
||||
* N_m3u8DL-CLI `(本项目)`: 基于 .NET Framework, 不具备跨平台能力. 目前已进入维护阶段.
|
||||
|
||||
暂时放弃跨平台(很多API需要重写才能实现功能,日后有空再维护)
|
||||
* [N_m3u8DL-RE](https://github.com/nilaoda/N_m3u8DL-RE) : 抛弃历史包袱从零做起, 支持Win/Linux/Mac, 更丰富的功能会在这里出现 ...
|
||||
|
||||
# N_m3u8DL-CLI
|
||||
一个**简单易用的**m3u8下载器,下载地址:https://github.com/nilaoda/N_m3u8DL-CLI/releases
|
||||
|
||||
支持下载m3u8链接或文件为`mp4`或`ts`格式,并提供丰富的命令行选项。
|
||||
* **不支持**优酷视频解密
|
||||
* **不支持**气球云视频解密
|
||||
* 支持`AES-128-CBC`加密自动解密
|
||||
* 支持多线程下载
|
||||
* 支持下载限速
|
||||
@@ -39,7 +44,9 @@
|
||||
* 支持下载路径为网络驱动器的情况
|
||||
* 支持下载外挂字幕轨道、音频轨道
|
||||
* 支持仅合并为音频
|
||||
* 自动使用系统代理(可禁止)
|
||||
* 支持设置特定http代理
|
||||
* 支持自动使用系统代理(默认行为, 可禁止)
|
||||
* 支持m3u8dl链接协议(通过web链接调用本机客户端)
|
||||
* 提供SimpleG简易的`GUI`生成常用参数
|
||||
|
||||
|
||||
@@ -48,37 +55,76 @@
|
||||
|
||||
# 命令行选项
|
||||
```
|
||||
N_m3u8DL-CLI.exe <URL|JSON|FILE> [OPTIONS]
|
||||
N_m3u8DL-CLI
|
||||
|
||||
--workDir Directory 设定程序工作目录
|
||||
--saveName Filename 设定存储文件名(不包括后缀)
|
||||
--baseUrl BaseUrl 设定Baseurl
|
||||
--headers headers 设定请求头,格式 key:value 使用|分割不同的key&value
|
||||
--maxThreads Thread 设定程序的最大线程数(默认为32)
|
||||
--minThreads Thread 设定程序的最小线程数(默认为16)
|
||||
--retryCount Count 设定程序的重试次数(默认为15)
|
||||
--timeOut Sec 设定程序网络请求的超时时间(单位为秒,默认为10秒)
|
||||
--muxSetJson File 使用外部json文件定义混流选项
|
||||
--useKeyFile File 使用外部16字节文件定义AES-128解密KEY
|
||||
--useKeyBase64 Base64String 使用Base64字符串定义AES-128解密KEY
|
||||
--useKeyIV HEXString 使用HEX字符串定义AES-128解密IV
|
||||
--downloadRange Range 仅下载视频的一部分分片或长度
|
||||
--liveRecDur HH:MM:SS 直播录制时,达到此长度自动退出软件
|
||||
--stopSpeed Number 当速度低于此值时,重试(单位为KB/s)
|
||||
--maxSpeed Number 设置下载速度上限(单位为KB/s)
|
||||
--enableDelAfterDone 开启下载后删除临时文件夹的功能
|
||||
--enableMuxFastStart 开启混流mp4的FastStart特性
|
||||
--enableBinaryMerge 开启二进制合并分片
|
||||
--enableParseOnly 开启仅解析模式(程序只进行到meta.json)
|
||||
--enableAudioOnly 合并时仅封装音频轨道
|
||||
--disableDateInfo 关闭混流中的日期写入
|
||||
--noMerge 禁用自动合并
|
||||
--noProxy 不自动使用系统代理
|
||||
--disableIntegrityCheck 不检测分片数量是否完整
|
||||
USAGE:
|
||||
|
||||
N_m3u8DL-CLI <URL|JSON|FILE> [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
|
||||
--workDir 设定程序工作目录
|
||||
--saveName 设定存储文件名(不包括后缀)
|
||||
--baseUrl 设定Baseurl
|
||||
--headers 设定请求头,格式 key:value 使用|分割不同的key&value
|
||||
--maxThreads (Default: 32) 设定程序的最大线程数
|
||||
--minThreads (Default: 16) 设定程序的最小线程数
|
||||
--retryCount (Default: 15) 设定程序的重试次数
|
||||
--timeOut (Default: 10) 设定程序网络请求的超时时间(单位为秒)
|
||||
--muxSetJson 使用外部json文件定义混流选项
|
||||
--useKeyFile 使用外部16字节文件定义AES-128解密KEY
|
||||
--useKeyBase64 使用Base64字符串定义AES-128解密KEY
|
||||
--useKeyIV 使用HEX字符串定义AES-128解密IV
|
||||
--downloadRange 仅下载视频的一部分分片或长度
|
||||
--liveRecDur 直播录制时,达到此长度自动退出软件(HH:MM:SS)
|
||||
--stopSpeed 当速度低于此值时,重试(单位为KB/s)
|
||||
--maxSpeed 设置下载速度上限(单位为KB/s)
|
||||
--proxyAddress 设置HTTP/SOCKS5代理, 如 http://127.0.0.1:8080
|
||||
--enableDelAfterDone 开启下载后删除临时文件夹的功能
|
||||
--enableMuxFastStart 开启混流mp4的FastStart特性
|
||||
--enableBinaryMerge 开启二进制合并分片
|
||||
--enableParseOnly 开启仅解析模式(程序只进行到meta.json)
|
||||
--enableAudioOnly 合并时仅封装音频轨道
|
||||
--disableDateInfo 关闭混流中的日期写入
|
||||
--disableIntegrityCheck 不检测分片数量是否完整
|
||||
--noMerge 禁用自动合并
|
||||
--noProxy 不自动使用系统代理
|
||||
--registerUrlProtocol 注册m3u8dl链接协议
|
||||
--unregisterUrlProtocol 取消注册m3u8dl链接协议
|
||||
--enableChaCha20 enableChaCha20
|
||||
--chaCha20KeyBase64 ChaCha20KeyBase64
|
||||
--chaCha20NonceBase64 ChaCha20NonceBase64
|
||||
--help Display this help screen.
|
||||
--version Display version information.
|
||||
```
|
||||
|
||||
# 关于`m3u8dl://`协议
|
||||
新增命令行参数:
|
||||
```
|
||||
--registerUrlProtocol 注册m3u8dl链接协议
|
||||
--unregisterUrlProtocol 取消注册m3u8dl链接协议
|
||||
```
|
||||
|
||||
URI格式:
|
||||
```
|
||||
m3u8dl://<base64编码的客户端命令行文本>
|
||||
```
|
||||
|
||||
URI示例:
|
||||
```
|
||||
m3u8dl://Imh0dHBzOi8vZXhhbXBsZS5jb20vYWJjLm0zdTgiIC0td29ya0RpciAiJVVTRVJQUk9GSUxFJVxEb3dubG9hZHNcbTN1OGRsIiAtLXNhdmVOYW1lICJhYmMiIC0tZW5hYmxlRGVsQWZ0ZXJEb25lIC0tZGlzYWJsZURhdGVJbmZvIC0tbm9Qcm94eQ==
|
||||
```
|
||||
|
||||
URI解码结果:
|
||||
```
|
||||
"https://example.com/abc.m3u8" --workDir "%USERPROFILE%\Downloads\m3u8dl" --saveName "abc" --enableDelAfterDone --disableDateInfo --noProxy
|
||||
```
|
||||
|
||||
# 用户文档
|
||||
https://nilaoda.github.io/N_m3u8DL-CLI/
|
||||
|
||||
# 聊聊
|
||||
https://discord.gg/SSGwKrjC44
|
||||
|
||||
# 赞赏
|
||||
https://nilaoda.github.io/N_m3u8DL-CLI/source/images/alipay.png
|
||||

|
||||
|
104
README_ENG.md
104
README_ENG.md
@@ -8,10 +8,10 @@
|
||||
╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚════╝ ╚═════╝ ╚══════╝ ╚═════╝╚══════╝╚═╝
|
||||
|
||||
```
|
||||
This is a m3u8 downloader.
|
||||
This is an m3u8 downloader.
|
||||
## Summary
|
||||
Supports:
|
||||
* Auto deceypt for `AES-128-CBC`
|
||||
* Auto decrypt for `AES-128-CBC`
|
||||
* `Master List`
|
||||
* Live stream recording(`BETA`)
|
||||
* Customize HTTP headers
|
||||
@@ -20,8 +20,8 @@ Supports:
|
||||
* Network driver on Windows OS
|
||||
* Alternative audio/video track
|
||||
* Mux without video track
|
||||
* Auto use system proxy
|
||||
* Optimization for Chinese streaming platform
|
||||
* Custom HTTP proxy or Use system proxy
|
||||
* Optimization for Chinese streaming platforms
|
||||
|
||||

|
||||
|
||||
@@ -30,33 +30,75 @@ Supports:
|
||||
|
||||
## Options
|
||||
```
|
||||
N_m3u8DL-CLI.exe <URL|JSON|FILE> [OPTIONS]
|
||||
N_m3u8DL-CLI
|
||||
|
||||
--workDir Directory Set work dir (Video will be here)
|
||||
--saveName Filename Set save name(Exclude extention)
|
||||
--baseUrl BaseUrl Set Baseurl
|
||||
--headers headers Set HTTP headers,format: key:value user | split all key&value
|
||||
--maxThreads Thread Set max thread(default: 32)
|
||||
--minThreads Thread Set min thread(default: 16)
|
||||
--retryCount Count Set retry times(default: 15)
|
||||
--timeOut Sec Set timeout for http request(second,default: 10)
|
||||
--muxSetJson File Set a json file for mux
|
||||
--useKeyFile File Use 16 bytes file as KEY for AES-128 decryption
|
||||
--useKeyBase64 Base64String Use Base64 String as KEY for AES-128 decryption
|
||||
--useKeyIV HEXString Use HEX String as IV for AES-128 decryption
|
||||
--downloadRange Range Set range for a video
|
||||
--stopSpeed Number Speed below this, retry(KB/s)
|
||||
--maxSpeed Number Set max download speed(KB/s)
|
||||
--enableDelAfterDone Enable delete clips after download completed
|
||||
--enableMuxFastStart Enable fast start for mp4
|
||||
--enableBinaryMerge Enable use binary merge instead ffmpeg
|
||||
--enableParseOnly Enable parse mode
|
||||
--enableAudioOnly Enable only audio track when mux use ffmpeg
|
||||
--disableDateInfo Disable write date info when mux use ffmpeg
|
||||
--noMerge Disable auto merge
|
||||
--noProxy Disable use system proxy
|
||||
--disableIntegrityCheck Disable integrity check
|
||||
USAGE:
|
||||
|
||||
N_m3u8DL-CLI <URL|JSON|FILE> [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
|
||||
--workDir Set work dir (Video will be here)
|
||||
--saveName Set save name(Exclude extention)
|
||||
--baseUrl Set Baseurl
|
||||
--headers Set HTTP headers,format: key:value use | split all
|
||||
key&value
|
||||
--maxThreads (Default: 32) Set max thread
|
||||
--minThreads (Default: 16) Set min thread
|
||||
--retryCount (Default: 15) Set retry times
|
||||
--timeOut (Default: 10) Set timeout for http request(second)
|
||||
--muxSetJson Set a json file for mux
|
||||
--useKeyFile Use 16 bytes file as KEY for AES-128 decryption
|
||||
--useKeyBase64 Use Base64 String as KEY for AES-128 decryption
|
||||
--useKeyIV Use HEX String as IV for AES-128 decryption
|
||||
--downloadRange Set range for a video
|
||||
--liveRecDur When the live recording reaches this length, the
|
||||
software will exit automatically(HH:MM:SS)
|
||||
--stopSpeed Speed below this, retry(KB/s)
|
||||
--maxSpeed Set max download speed(KB/s)
|
||||
--proxyAddress Set HTTP/SOCKS5 Proxy, like http://127.0.0.1:8080
|
||||
--enableDelAfterDone Enable delete clips after download completed
|
||||
--enableMuxFastStart Enable fast start for mp4
|
||||
--enableBinaryMerge Enable use binary merge instead of ffmpeg
|
||||
--enableParseOnly Enable parse only mode
|
||||
--enableAudioOnly Enable only audio track when mux use ffmpeg
|
||||
--disableDateInfo Disable write date info when mux use ffmpeg
|
||||
--disableIntegrityCheck Disable integrity check
|
||||
--noMerge Disable auto merge
|
||||
--noProxy Disable use system proxy
|
||||
--registerUrlProtocol Register m3u8dl URL protocol
|
||||
--unregisterUrlProtocol Unregister m3u8dl URL protocol
|
||||
--enableChaCha20 enableChaCha20
|
||||
--chaCha20KeyBase64 ChaCha20KeyBase64
|
||||
--chaCha20NonceBase64 ChaCha20NonceBase64
|
||||
--help Display this help screen.
|
||||
--version Display version information.
|
||||
```
|
||||
|
||||
|
||||
## About `m3u8dl://`
|
||||
New commandline options:
|
||||
```
|
||||
--registerUrlProtocol Register m3u8dl URL protocol
|
||||
--unregisterUrlProtocol Unregister m3u8dl URL protocol
|
||||
```
|
||||
|
||||
URI Format:
|
||||
```
|
||||
m3u8dl://<base64 encoded params>
|
||||
```
|
||||
|
||||
URI Example:
|
||||
```
|
||||
m3u8dl://Imh0dHBzOi8vZXhhbXBsZS5jb20vYWJjLm0zdTgiIC0td29ya0RpciAiJVVTRVJQUk9GSUxFJVxEb3dubG9hZHNcbTN1OGRsIiAtLXNhdmVOYW1lICJhYmMiIC0tZW5hYmxlRGVsQWZ0ZXJEb25lIC0tZGlzYWJsZURhdGVJbmZvIC0tbm9Qcm94eQ==
|
||||
```
|
||||
|
||||
URI Decode Result:
|
||||
```
|
||||
"https://example.com/abc.m3u8" --workDir "%USERPROFILE%\Downloads\m3u8dl" --saveName "abc" --enableDelAfterDone --disableDateInfo --noProxy
|
||||
```
|
||||
|
||||
## Document
|
||||
https://nilaoda.github.io/N_m3u8DL-CLI/
|
||||
https://nilaoda.github.io/N_m3u8DL-CLI/
|
||||
|
||||
## Chit-chat
|
||||
https://discord.gg/RscAJZv3Yq
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user