You've already forked N_m3u8DL-CLI
mirror of
https://github.com/nilaoda/N_m3u8DL-CLI
synced 2025-09-10 12:40:52 +02:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
311f3b882e | ||
![]() |
4b4f537984 | ||
![]() |
8032d50b42 | ||
![]() |
f615764e55 | ||
![]() |
ccaa200ef8 | ||
![]() |
6058d878eb | ||
![]() |
c712c6dee0 | ||
![]() |
bb24bb998f | ||
![]() |
e9d951efa5 | ||
![]() |
6f88a805ef | ||
![]() |
6aa6d63a8d | ||
![]() |
147246caba | ||
![]() |
35c1ee4777 | ||
![]() |
8b32081b85 | ||
![]() |
c00de328d1 | ||
![]() |
d5193c1645 | ||
![]() |
c06fbf5820 | ||
![]() |
e700edba56 | ||
![]() |
aad948da7c | ||
![]() |
14f7b20176 | ||
![]() |
a66a9a4096 | ||
![]() |
c862f23a9c | ||
![]() |
1a722e80de | ||
![]() |
eff43e8ac3 | ||
![]() |
7648f8f8dc | ||
![]() |
1edc1a43fe | ||
![]() |
01e7735018 | ||
![]() |
a0c41d6116 | ||
![]() |
c3c25774de |
34
.github/workflows/build_latest.yml
vendored
Normal file
34
.github/workflows/build_latest.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Build_Latest
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
name: Checkout Code
|
||||
|
||||
- name: Setup MSBuild Path
|
||||
uses: warrenbuckley/Setup-MSBuild@v1
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
|
||||
- name: Setup NuGet
|
||||
uses: NuGet/setup-nuget@v1.0.2
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
|
||||
- name: Restore NuGet Packages
|
||||
run: nuget restore N_m3u8DL-CLI.sln
|
||||
|
||||
- name: Build
|
||||
run: msbuild N_m3u8DL-CLI.sln /p:Configuration=Release /p:DebugSymbols=false /p:DebugType=None
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v1.0.0
|
||||
with:
|
||||
name: N_m3u8DL-CLI_latest
|
||||
path: N_m3u8DL-CLI\bin\Release\
|
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
/**
|
||||
* https://www.cnblogs.com/linxuanchen/p/c-sharp-command-line-argument-parser.html
|
||||
*/
|
||||
public class CommandLineArgument
|
||||
{
|
||||
List<CommandLineArgument> _arguments;
|
||||
|
41
N_m3u8DL-CLI/DecodeDdyun.cs
Normal file
41
N_m3u8DL-CLI/DecodeDdyun.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class DecodeDdyun
|
||||
{
|
||||
public static string DecryptM3u8(byte[] byteArray)
|
||||
{
|
||||
string tmp = DecodeNfmovies.DecryptM3u8(byteArray);
|
||||
if (tmp.StartsWith("duoduo.key"))
|
||||
{
|
||||
tmp = Regex.Replace(tmp, @"#EXT-X-BYTERANGE:.*\s", "");
|
||||
tmp = tmp.Replace("https:", "jump/https:")
|
||||
.Replace("inews.gtimg.com", "puui.qpic.cn");
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
//https://player.ddyunp.com/jQuery.min.js?v1.5
|
||||
public static string GetVaildM3u8Url(string url)
|
||||
{
|
||||
//url: https://hls.ddyunp.com/ddyun/id/1/key/playlist.m3u8
|
||||
string id = Regex.Match(url, @"\w{20,}").Value;
|
||||
string tm = Global.GetTimeStamp(false);
|
||||
string t = ((long.Parse(tm) / 0x186a0) * 0x64).ToString();
|
||||
string tmp = id + "duoduo" + "1" + t;
|
||||
MD5 md5 = MD5.Create();
|
||||
byte[] bs = Encoding.UTF8.GetBytes(tmp);
|
||||
byte[] hs = md5.ComputeHash(bs);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (byte b in hs)
|
||||
{
|
||||
sb.Append(b.ToString("x2"));
|
||||
}
|
||||
string key = sb.ToString();
|
||||
return Regex.Replace(url, @"1/\w{20,}", "1/" + key);
|
||||
}
|
||||
}
|
||||
}
|
36
N_m3u8DL-CLI/DecodeNfmovies.cs
Normal file
36
N_m3u8DL-CLI/DecodeNfmovies.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class DecodeNfmovies
|
||||
{
|
||||
//https://jx.nfmovies.com/hls.min.js
|
||||
public static string DecryptM3u8(byte[] byteArray)
|
||||
{
|
||||
var t = byteArray;
|
||||
var decrypt = "";
|
||||
if (137 == t[0] && 80 == t[1] && 130 == t[354] && 96 == t[353]) t = t.Skip(355).ToArray();
|
||||
else
|
||||
{
|
||||
if (137 != t[0] || 80 != t[1] || 130 != t[394] || 96 != t[393])
|
||||
{
|
||||
for (var i = 0; i < t.Length; i++) decrypt += Convert.ToChar(t[i]);
|
||||
return decrypt;
|
||||
}
|
||||
t = t.Skip(395).ToArray();
|
||||
}
|
||||
using (var zipStream =
|
||||
new System.IO.Compression.GZipStream(new MemoryStream(t), System.IO.Compression.CompressionMode.Decompress))
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(zipStream, Encoding.UTF8))
|
||||
{
|
||||
decrypt = sr.ReadToEnd();
|
||||
}
|
||||
}
|
||||
return decrypt;
|
||||
}
|
||||
}
|
||||
}
|
@@ -192,7 +192,7 @@ namespace N_m3u8DL_CLI
|
||||
if (sd.SegDur < 0) sd.SegDur = 0; //防止负数
|
||||
sd.FileUrl = firstSeg["segUri"].Value<string>();
|
||||
//VTT字幕
|
||||
if (isVTT == false && sd.FileUrl.Trim('\"').EndsWith(".vtt"))
|
||||
if (isVTT == false && (sd.FileUrl.Trim('\"').EndsWith(".vtt") || sd.FileUrl.Trim('\"').EndsWith(".webvtt")))
|
||||
isVTT = true;
|
||||
sd.Method = firstSeg["method"].Value<string>();
|
||||
if (sd.Method != "NONE")
|
||||
@@ -281,8 +281,8 @@ namespace N_m3u8DL_CLI
|
||||
sd.SegDur = info["duration"].Value<double>();
|
||||
if (sd.SegDur < 0) sd.SegDur = 0; //防止负数
|
||||
sd.FileUrl = info["segUri"].Value<string>();
|
||||
//VTT字幕
|
||||
if (isVTT == false && sd.FileUrl.Trim('\"').EndsWith(".vtt"))
|
||||
//VTT字幕
|
||||
if (isVTT == false && (sd.FileUrl.Trim('\"').EndsWith(".vtt") || sd.FileUrl.Trim('\"').EndsWith(".webvtt")))
|
||||
isVTT = true;
|
||||
sd.Method = info["method"].Value<string>();
|
||||
if (sd.Method != "NONE")
|
||||
@@ -370,7 +370,7 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
else //开始合并
|
||||
{
|
||||
LOGGER.PrintLine(strings.downloadComplete + (DisableIntegrityCheck ? "(" + strings.disableIntegrityCheck + ")" : ""));
|
||||
LOGGER.PrintLine(strings.downloadComplete + (DisableIntegrityCheck ? "("+strings.disableIntegrityCheck+")" : ""));
|
||||
Console.WriteLine();
|
||||
if (NoMerge == false)
|
||||
{
|
||||
@@ -381,7 +381,10 @@ namespace N_m3u8DL_CLI
|
||||
LOGGER.PrintLine(strings.startMerging, LOGGER.Warning);
|
||||
//VTT字幕
|
||||
if (isVTT == true)
|
||||
{
|
||||
MuxFormat = "vtt";
|
||||
Global.ReAdjustVtt(Global.GetFiles(DownDir + "\\Part_0", ".ts"));
|
||||
}
|
||||
//只有一个Part直接用ffmpeg合并
|
||||
if (PartsCount == 1)
|
||||
{
|
||||
@@ -497,6 +500,9 @@ namespace N_m3u8DL_CLI
|
||||
parser.Parse(); //开始解析
|
||||
Thread.Sleep(1000);
|
||||
LOGGER.CursorIndex = 5;
|
||||
Global.HadReadInfo = false;
|
||||
Global.VIDEO_TYPE = "";
|
||||
Global.AUDIO_TYPE = "";
|
||||
DoDownload();
|
||||
}
|
||||
if (externalSub) //下载独立字幕
|
||||
@@ -509,16 +515,19 @@ namespace N_m3u8DL_CLI
|
||||
parser.Headers = Headers; //继承Header
|
||||
parser.BaseUrl = "";
|
||||
parser.M3u8Url = externalSubUrl;
|
||||
parser.DownName = DownName + "(Subtitle)";
|
||||
parser.DownName = DownName.Replace("(Audio)", "") + "(Subtitle)";
|
||||
parser.DownDir = Path.Combine(Path.GetDirectoryName(DownDir), parser.DownName);
|
||||
LOGGER.WriteLine(strings.startParsing + externalSubUrl);
|
||||
LOGGER.WriteLine(strings.downloadingExternalSubtitleTrack);
|
||||
DownName = DownName + "(Subtitle)";
|
||||
DownName = parser.DownName;
|
||||
fflogName = "_ffreport(Subtitle).log";
|
||||
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);
|
||||
@@ -530,7 +539,7 @@ namespace N_m3u8DL_CLI
|
||||
|
||||
FFmpeg.OutPutPath = Path.Combine(Directory.GetParent(DownDir).FullName, DownName);
|
||||
FFmpeg.ReportFile = driverName + "\\:" + exePath.Remove(0, exePath.IndexOf(':') + 1).Replace("\\", "/") + "/Logs/" + Path.GetFileNameWithoutExtension(LOGGER.LOGFILE) + fflogName;
|
||||
|
||||
|
||||
//合并分段
|
||||
LOGGER.PrintLine(strings.startMerging);
|
||||
for (int i = 0; i < PartsCount; i++)
|
||||
@@ -637,6 +646,9 @@ namespace N_m3u8DL_CLI
|
||||
parser.Parse(); //开始解析
|
||||
Thread.Sleep(1000);
|
||||
LOGGER.CursorIndex = 5;
|
||||
Global.HadReadInfo = false;
|
||||
Global.VIDEO_TYPE = "";
|
||||
Global.AUDIO_TYPE = "";
|
||||
DoDownload();
|
||||
}
|
||||
if (externalSub) //下载独立字幕
|
||||
@@ -649,16 +661,19 @@ namespace N_m3u8DL_CLI
|
||||
parser.Headers = Headers; //继承Header
|
||||
parser.BaseUrl = "";
|
||||
parser.M3u8Url = externalSubUrl;
|
||||
parser.DownName = DownName + "(Subtitle)";
|
||||
parser.DownName = DownName.Replace("(Audio)", "") + "(Subtitle)";
|
||||
parser.DownDir = Path.Combine(Path.GetDirectoryName(DownDir), parser.DownName);
|
||||
LOGGER.WriteLine(strings.startParsing + externalSubUrl);
|
||||
LOGGER.WriteLine(strings.downloadingExternalSubtitleTrack);
|
||||
DownName = DownName + "(Subtitle)";
|
||||
DownName = parser.DownName;
|
||||
fflogName = "_ffreport(Subtitle).log";
|
||||
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);
|
||||
|
@@ -10,19 +10,20 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
class FFmpeg
|
||||
{
|
||||
private static string outPutPath = string.Empty;
|
||||
private static string reportFile = string.Empty;
|
||||
private static bool useAACFilter = false; //是否启用滤镜
|
||||
private static bool writeDate = true; //是否写入录制日期
|
||||
public static string OutPutPath { get => outPutPath; set => outPutPath = value; }
|
||||
public static string ReportFile { get => reportFile; set => reportFile = value; }
|
||||
public static bool UseAACFilter { get => useAACFilter; set => useAACFilter = value; }
|
||||
public static bool WriteDate { get => writeDate; set => writeDate = value; }
|
||||
public static string FFMPEG_PATH = "ffmpeg";
|
||||
public static string REC_TIME = ""; //录制日期
|
||||
|
||||
public static string OutPutPath { get; set; } = string.Empty;
|
||||
public static string ReportFile { get; set; } = string.Empty;
|
||||
public static bool UseAACFilter { get; set; } = false; //是否启用滤镜
|
||||
public static bool WriteDate { get; set; } = true; //是否写入录制日期
|
||||
|
||||
public static void Merge(string[] files, string muxFormat, bool fastStart,
|
||||
string poster = "", string audioName = "", string title = "",
|
||||
string copyright = "", string comment = "", string encodingTool = "")
|
||||
{
|
||||
string dateString = string.IsNullOrEmpty(REC_TIME) ? DateTime.Now.ToString("o") : REC_TIME;
|
||||
|
||||
//同名文件已存在的共存策略
|
||||
if (File.Exists($"{OutPutPath}.{muxFormat.ToLower()}"))
|
||||
{
|
||||
@@ -50,7 +51,7 @@ namespace N_m3u8DL_CLI
|
||||
command += " " + (string.IsNullOrEmpty(ddpAudio) ? "" : "-i \"" + ddpAudio + "\"");
|
||||
command +=
|
||||
$" -map 0:v? {(string.IsNullOrEmpty(ddpAudio) ? "-map 0:a?" : $"-map {(string.IsNullOrEmpty(poster) ? "1" : "2")}:a -map 0:a?")} -map 0:s? " + (string.IsNullOrEmpty(poster) ? "" : addPoster)
|
||||
+ (writeDate ? " -metadata date=\"" + DateTime.Now.ToString("o") + "\"" : "") +
|
||||
+ (WriteDate ? " -metadata date=\"" + dateString + "\"" : "") +
|
||||
" -metadata encoding_tool=\"" + encodingTool + "\" -metadata title=\"" + title +
|
||||
"\" -metadata copyright=\"" + copyright + "\" -metadata comment=\"" + comment +
|
||||
$"\" -metadata:s:a:{(string.IsNullOrEmpty(ddpAudio) ? "0" : "1")} handler_name=\"" + audioName + $"\" -metadata:s:a:{(string.IsNullOrEmpty(ddpAudio) ? "0" : "1")} handler=\"" + audioName + "\" ";
|
||||
@@ -83,7 +84,7 @@ namespace N_m3u8DL_CLI
|
||||
|
||||
}
|
||||
|
||||
Run("ffmpeg", command, Path.GetDirectoryName(files[0]));
|
||||
Run(FFMPEG_PATH, command, Path.GetDirectoryName(files[0]));
|
||||
LOGGER.WriteLine(strings.ffmpegDone);
|
||||
//Console.WriteLine(command);
|
||||
}
|
||||
@@ -92,7 +93,7 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
if (Global.VIDEO_TYPE == "H264")
|
||||
{
|
||||
Run("ffmpeg",
|
||||
Run(FFMPEG_PATH,
|
||||
"-loglevel quiet -i \"" + file + "\" -map 0 -c copy -copy_unknown -f mpegts -bsf:v h264_mp4toannexb \""
|
||||
+ Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts\"",
|
||||
Path.GetDirectoryName(file));
|
||||
@@ -104,7 +105,7 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
else if (Global.VIDEO_TYPE == "H265")
|
||||
{
|
||||
Run("ffmpeg",
|
||||
Run(FFMPEG_PATH,
|
||||
"-loglevel quiet -i \"" + file + "\" -map 0 -c copy -copy_unknown -f mpegts -bsf:v hevc_mp4toannexb \""
|
||||
+ Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts\"",
|
||||
Path.GetDirectoryName(file));
|
||||
|
@@ -30,8 +30,8 @@ namespace N_m3u8DL_CLI
|
||||
|
||||
|
||||
/*===============================================================================*/
|
||||
static string nowVer = "2.7.1";
|
||||
static string nowDate = "20200719";
|
||||
static string nowVer = "2.8.1";
|
||||
static string nowDate = "20201121";
|
||||
public static void WriteInit()
|
||||
{
|
||||
Console.Clear();
|
||||
@@ -108,7 +108,7 @@ namespace N_m3u8DL_CLI
|
||||
HttpWebRequest webRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
|
||||
webRequest.Method = "GET";
|
||||
if (NoProxy) webRequest.Proxy = null;
|
||||
webRequest.UserAgent = "Mozilla/4.0";
|
||||
webRequest.UserAgent = "Mozilla/5.0 (Linux; U; Android 7.0; zh-cn; 15 Plus Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/9.4 Mobile Safari/537.36";
|
||||
webRequest.Headers.Add("Accept-Encoding", "gzip, deflate");
|
||||
webRequest.Timeout = TimeOut; //设置超时
|
||||
webRequest.KeepAlive = false;
|
||||
@@ -145,6 +145,10 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
}
|
||||
HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
|
||||
|
||||
//文件过大则认为不是m3u8
|
||||
if (webResponse.ContentLength != -1 && webRequest.ContentLength > 50 * 1024 * 1024) return "";
|
||||
|
||||
if (webResponse.ContentEncoding != null
|
||||
&& webResponse.ContentEncoding.ToLower() == "gzip") //如果使用了GZip则先解压
|
||||
{
|
||||
@@ -514,7 +518,7 @@ namespace N_m3u8DL_CLI
|
||||
request.Headers.Add("Cookie", "MQGUID");
|
||||
}
|
||||
else
|
||||
request.UserAgent = "VLC/2.2.1 LibVLC/2.2.1";
|
||||
request.UserAgent = "Mozilla/5.0 (Linux; U; Android 7.0; zh-cn; 15 Plus Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/9.4 Mobile Safari/537.36";
|
||||
//下载部分字节
|
||||
if (expectByte != -1)
|
||||
request.AddRange("bytes", startByte, startByte + expectByte - 1);
|
||||
@@ -545,6 +549,7 @@ namespace N_m3u8DL_CLI
|
||||
|
||||
long totalLen = 0;
|
||||
long downLen = 0;
|
||||
bool pngHeader = false; //PNG HEADER检测
|
||||
using (var response = (HttpWebResponse)request.GetResponse())
|
||||
{
|
||||
using (var responseStream = response.GetResponseStream())
|
||||
@@ -555,6 +560,17 @@ namespace N_m3u8DL_CLI
|
||||
totalLen = response.ContentLength;
|
||||
byte[] bArr = new byte[1024];
|
||||
int size = responseStream.Read(bArr, 0, (int)bArr.Length);
|
||||
if (!pngHeader && size > 3 && 137 == bArr[0] && 80 == bArr[1] && 78 == bArr[2] && 71 == bArr[3])
|
||||
{
|
||||
pngHeader = true;
|
||||
}
|
||||
//GIF HEADER检测
|
||||
if (!pngHeader && size > 3 && 0x47 == bArr[0] && 0x49 == bArr[1] && 0x46 == bArr[2] && 0x38 == bArr[3])
|
||||
{
|
||||
bArr = bArr.Skip(42).ToArray();
|
||||
size -= 42;
|
||||
downLen += 42;
|
||||
}
|
||||
while (size > 0)
|
||||
{
|
||||
stream.Write(bArr, 0, size);
|
||||
@@ -579,6 +595,8 @@ namespace N_m3u8DL_CLI
|
||||
try { File.Delete(path); } catch (Exception) { }
|
||||
if (totalLen != -1 && downLen != totalLen)
|
||||
try { File.Delete(path); } catch (Exception) { }
|
||||
if (pngHeader)
|
||||
TrySkipPngHeader(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -587,6 +605,51 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于处理利用图床上传TS导致前面被插入PNG Header的情况
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
public static void TrySkipPngHeader(string filePath)
|
||||
{
|
||||
var u = File.ReadAllBytes(filePath);
|
||||
if (0x47 == u[0])
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[118] && 130 == u[119])
|
||||
{
|
||||
u = u.Skip(120).ToArray();
|
||||
}
|
||||
else if (137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[6100] && 130 == u[6101])
|
||||
{
|
||||
u = u.Skip(6102).ToArray();
|
||||
}
|
||||
else if (137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[67] && 130 == u[68])
|
||||
{
|
||||
u = u.Skip(69).ToArray();
|
||||
}
|
||||
else if (137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[769] && 130 == u[770])
|
||||
{
|
||||
u = u.Skip(771).ToArray();
|
||||
}
|
||||
else if (137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3])
|
||||
{
|
||||
//确定是PNG但是需要手动查询结尾标记(0x60 0x82 0x47)
|
||||
int skip = 0;
|
||||
for (int i = 4; i < u.Length - 3; i++)
|
||||
{
|
||||
if (u[i] == 0x60 && u[i + 1] == 0x82 && u[i + 2] == 0x47)
|
||||
{
|
||||
skip = i + 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
u = u.Skip(skip).ToArray();
|
||||
}
|
||||
|
||||
File.WriteAllBytes(filePath, u);
|
||||
}
|
||||
|
||||
//格式化json字符串
|
||||
public static string ConvertJsonString(string str)
|
||||
{
|
||||
@@ -748,11 +811,17 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
}
|
||||
|
||||
if(res.Contains("Audio aac"))
|
||||
if (res.Contains("Audio aac"))
|
||||
{
|
||||
FFmpeg.UseAACFilter = true;
|
||||
}
|
||||
|
||||
//有非AAC音轨则关闭UseAACFilter
|
||||
if (res.Contains("Audio") && !res.Contains("Audio aac"))
|
||||
{
|
||||
FFmpeg.UseAACFilter = false;
|
||||
}
|
||||
|
||||
if ((VIDEO_TYPE == "" || VIDEO_TYPE == "IGNORE") && res.Contains("Audio eac3"))
|
||||
{
|
||||
AUDIO_TYPE = "eac3";
|
||||
@@ -1015,5 +1084,58 @@ namespace N_m3u8DL_CLI
|
||||
return wr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过X-TIMESTAMP-MAP 调整VTT字幕的时间轴
|
||||
*/
|
||||
public static void ReAdjustVtt(string[] vtts)
|
||||
{
|
||||
string MsToTime(int ms)
|
||||
{
|
||||
TimeSpan ts = new TimeSpan(0, 0, 0, 0, ms);
|
||||
string str = "";
|
||||
str = (ts.Hours.ToString("00") + ":") + ts.Minutes.ToString("00") + ":" + ts.Seconds.ToString("00") + "." + ts.Milliseconds.ToString("000");
|
||||
return str;
|
||||
}
|
||||
|
||||
int TimeToMs(string line)
|
||||
{
|
||||
int hh = Convert.ToInt32(line.Split(':')[0]);
|
||||
int mm = Convert.ToInt32(line.Split(':')[1]);
|
||||
int ss = Convert.ToInt32(line.Split(':')[2].Split('.')[0]);
|
||||
int ms = Convert.ToInt32(line.Split(':')[2].Split('.')[1]);
|
||||
return hh * 60 * 60 * 1000 + mm * 60 * 1000 + ss * 1000 + ms;
|
||||
}
|
||||
|
||||
int addTime = 0;
|
||||
int baseTime = 0;
|
||||
for (int i = 0; i < vtts.Length; i++)
|
||||
{
|
||||
string tmp = File.ReadAllText(vtts[i], Encoding.UTF8);
|
||||
if (!Regex.IsMatch(tmp, "X-TIMESTAMP-MAP.*MPEGTS:(\\d+)"))
|
||||
break;
|
||||
if (i > 0)
|
||||
{
|
||||
int newTime = Convert.ToInt32(Regex.Match(tmp, "X-TIMESTAMP-MAP.*MPEGTS:(\\d+)").Groups[1].Value);
|
||||
if (newTime == 900000)
|
||||
continue;
|
||||
//计算偏移量
|
||||
//LOGGER.PrintLine((newTime - baseTime).ToString());
|
||||
addTime = addTime + ((newTime - baseTime) / 100);
|
||||
if ((newTime - baseTime) == 6300000)
|
||||
addTime -= 3000;
|
||||
//将新的作为基准时间
|
||||
baseTime = newTime;
|
||||
foreach (Match m in Regex.Matches(tmp, @"(\d{2}:\d{2}:\d{2}\.\d{3}) --> (\d{2}:\d{2}:\d{2}\.\d{3})"))
|
||||
{
|
||||
string start = m.Groups[1].Value;
|
||||
string end = m.Groups[2].Value;
|
||||
tmp = tmp.Replace(m.Value, MsToTime(TimeToMs(start) + addTime) + " --> " + MsToTime(TimeToMs(end) + addTime));
|
||||
}
|
||||
}
|
||||
File.WriteAllText(vtts[i], Regex.Replace(tmp, "X-TIMESTAMP-MAP=.*", ""), Encoding.UTF8);
|
||||
}
|
||||
//Console.ReadLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
617
N_m3u8DL-CLI/MPDParser.cs
Normal file
617
N_m3u8DL-CLI/MPDParser.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -40,11 +40,11 @@
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.JScript" />
|
||||
<Reference Include="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>..\packages\Newtonsoft.Json.dll</HintPath>
|
||||
<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>
|
||||
<Reference Include="NiL.JS, Version=2.5.1428.0, Culture=neutral, PublicKeyToken=fa941a7c2a4de689, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NiL.JS.dll</HintPath>
|
||||
<HintPath>..\packages\NiL.JS.2.5.1428\lib\net45\NiL.JS.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="System" />
|
||||
@@ -63,6 +63,9 @@
|
||||
<Compile Include="CommandLineArgument.cs" />
|
||||
<Compile Include="CommandLineArgumentParser.cs" />
|
||||
<Compile Include="Decode51CtoKey.cs" />
|
||||
<Compile Include="DecodeDdyun.cs" />
|
||||
<Compile Include="DecodeImooc.cs" />
|
||||
<Compile Include="DecodeNfmovies.cs" />
|
||||
<Compile Include="Decrypter.cs" />
|
||||
<Compile Include="FFmpeg.cs" />
|
||||
<Compile Include="Global.cs" />
|
||||
@@ -70,10 +73,26 @@
|
||||
<Compile Include="HLSTags.cs" />
|
||||
<Compile Include="LOGGER.cs" />
|
||||
<Compile Include="DownloadManager.cs" />
|
||||
<Compile Include="MPDParser.cs" />
|
||||
<Compile Include="Parser.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Downloader.cs" />
|
||||
<Compile Include="strings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>strings.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="strings.en-US.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>strings.en-US.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="strings.zh-TW.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>strings.zh-TW.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Watcher.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -82,9 +101,6 @@
|
||||
</None>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="bin\Debug\Newtonsoft.Json.dll" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<COMReference Include="Scripting">
|
||||
<Guid>{420B2830-E718-11CF-893D-00A0C9054228}</Guid>
|
||||
@@ -100,16 +116,18 @@
|
||||
<Content Include="logo_3Iv_icon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<EmbeddedResource Include="strings.en-US.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<LastGenOutput>strings.en-US.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="strings.en-US.resx" />
|
||||
<EmbeddedResource Include="strings.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>strings.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="strings.zh-TW.resx" />
|
||||
<EmbeddedResource Include="strings.zh-TW.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>strings.zh-TW.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -273,6 +273,27 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
/// 2020年7月18日
|
||||
/// - 从当前路径和exe路径同时寻找ffmpeg
|
||||
/// - 支持多语言本地化(简繁英)
|
||||
/// 2020年8月4日
|
||||
/// - 修复外挂字幕命名问题
|
||||
/// - 修复外挂字幕识别问题
|
||||
/// - 修复外挂轨道的一些逻辑问题
|
||||
/// - 优化多语言识别逻辑
|
||||
/// 2020年8月5日
|
||||
/// - 支持相对时间的vtt合并(还存在问题)
|
||||
/// 2020年8月9日
|
||||
/// - 修复IV错误导致的AES-128解密异常问题
|
||||
/// - 支持自定义IV(--useKeyIV)
|
||||
/// 2020年9月12日
|
||||
/// - 支持nfmovies m3u8解密
|
||||
/// - 支持自动去除PNG Header(https://puui.qpic.cn/newsapp_ls/0/12418116195/0)
|
||||
/// - 修复相对时间的vtt合并的一些错误逻辑(还存在问题)
|
||||
/// 2020年9月19日
|
||||
/// - 在自定义KEY且未自定义IV情况下,自动读取m3u8中存在的IV
|
||||
/// - 支持阿房影视等ddyun m3u8解密
|
||||
/// 2020年10月14日
|
||||
/// - 咪咕分片链接后拼接m3u8_url参数
|
||||
/// - 修复文件名过长导致的BUG
|
||||
/// - 优化ffmpeg调用逻辑
|
||||
/// </summary>
|
||||
///
|
||||
|
||||
@@ -318,7 +339,11 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
{
|
||||
loc = "zh-TW";
|
||||
}
|
||||
else if (loc != "zh-CN" && loc != "zh-SG")
|
||||
else if (loc == "zh-CN" || loc == "zh-SG")
|
||||
{
|
||||
loc = "zh-CN";
|
||||
}
|
||||
else
|
||||
{
|
||||
loc = "en-US";
|
||||
}
|
||||
@@ -334,7 +359,15 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
string fileName = "";
|
||||
|
||||
//寻找ffmpeg.exe
|
||||
if (!File.Exists("ffmpeg.exe") && !File.Exists(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "ffmpeg.exe")))
|
||||
if (File.Exists("ffmpeg.exe"))
|
||||
{
|
||||
FFmpeg.FFMPEG_PATH = Path.Combine(Environment.CurrentDirectory, "ffmpeg.exe");
|
||||
}
|
||||
else if (File.Exists(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "ffmpeg.exe")))
|
||||
{
|
||||
FFmpeg.FFMPEG_PATH = Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "ffmpeg.exe");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -342,7 +375,10 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
foreach (var de in EnvironmentPath)
|
||||
{
|
||||
if (File.Exists(Path.Combine(de.Trim('\"').Trim(), "ffmpeg.exe")))
|
||||
{
|
||||
FFmpeg.FFMPEG_PATH = Path.Combine(de.Trim('\"').Trim(), "ffmpeg.exe");
|
||||
goto HasFFmpeg;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -356,8 +392,7 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
Console.ResetColor(); //将控制台的前景色和背景色设为默认值
|
||||
Console.WriteLine(strings.ffmpegTip);
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("x86 https://ffmpeg.zeranoe.com/builds/win32/static/");
|
||||
Console.WriteLine("x64 https://ffmpeg.zeranoe.com/builds/win64/static/");
|
||||
Console.WriteLine("http://ffmpeg.org/download.html#build-windows");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(strings.pressAnyKeyExit);
|
||||
Console.ReadKey();
|
||||
@@ -384,6 +419,7 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
string reqHeaders = "";
|
||||
string keyFile = "";
|
||||
string keyBase64 = "";
|
||||
string keyIV = "";
|
||||
string muxSetJson = "MUXSETS.json";
|
||||
string workDir = CURRENT_PATH + "\\Downloads";
|
||||
bool muxFastStart = false;
|
||||
@@ -472,6 +508,10 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
{
|
||||
keyBase64 = arguments.Get("--useKeyBase64").Next;
|
||||
}
|
||||
if (arguments.Has("--useKeyIV"))
|
||||
{
|
||||
keyIV = arguments.Get("--useKeyIV").Next;
|
||||
}
|
||||
if (arguments.Has("--stopSpeed"))
|
||||
{
|
||||
Global.STOP_SPEED = Convert.ToInt64(arguments.Get("--stopSpeed").Next);
|
||||
@@ -619,6 +659,20 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
string m3u8Content = string.Empty;
|
||||
bool isVOD = true;
|
||||
|
||||
//避免文件路径过长
|
||||
if (workDir.Length >= 200)
|
||||
{
|
||||
//目录不能随便改 直接抛出异常
|
||||
throw new Exception("保存目录过长!");
|
||||
}
|
||||
else if (workDir.Length + fileName.Length >= 200)
|
||||
{
|
||||
//尝试缩短文件名
|
||||
while (workDir.Length + fileName.Length >= 200)
|
||||
{
|
||||
fileName = fileName.Substring(0, fileName.Length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
//开始解析
|
||||
|
||||
@@ -631,6 +685,7 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
parser.DownDir = Path.Combine(workDir, parser.DownName);
|
||||
parser.M3u8Url = testurl;
|
||||
parser.KeyBase64 = keyBase64;
|
||||
parser.KeyIV = keyIV;
|
||||
parser.KeyFile = keyFile;
|
||||
if (baseUrl != "")
|
||||
parser.BaseUrl = baseUrl;
|
||||
|
287
N_m3u8DL-CLI/changelog.txt
Normal file
287
N_m3u8DL-CLI/changelog.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net46" />
|
||||
<package id="NiL.JS" version="2.5.1428" targetFramework="net46" />
|
||||
</packages>
|
@@ -197,6 +197,7 @@
|
||||
--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)
|
||||
|
@@ -197,6 +197,7 @@
|
||||
--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)
|
||||
|
@@ -197,6 +197,7 @@
|
||||
--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)
|
||||
|
@@ -61,6 +61,7 @@ N_m3u8DL-CLI.exe <URL|JSON|FILE> [OPTIONS]
|
||||
--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)
|
||||
|
@@ -43,6 +43,7 @@ N_m3u8DL-CLI.exe <URL|JSON|FILE> [OPTIONS]
|
||||
--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)
|
||||
|
File diff suppressed because one or more lines are too long
555
docs/GetM3u8.html
Normal file
555
docs/GetM3u8.html
Normal file
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
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
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
Reference in New Issue
Block a user