1
mirror of https://github.com/nilaoda/N_m3u8DL-CLI synced 2025-09-15 02:00:49 +02:00

Compare commits

..

7 Commits
2.9.1 ... 2.9.4

Author SHA1 Message Date
nilaoda
82f2111522 update 2021-01-24 16:26:32 +08:00
nilaoda
4c3207586f Update MPDParser.cs 2021-01-24 16:24:03 +08:00
nilaoda
69b411e37c fix sub merge bug 2021-01-24 16:22:13 +08:00
nilaoda
1e8525041f Download from DSNP 2021-01-24 16:21:32 +08:00
nilaoda
65ae72d4a4 Update MPDParser.cs 2021-01-18 20:36:43 +08:00
nilaoda
4a4bfae5ab 优化MPD下载行为 2021-01-18 02:00:19 +08:00
nilaoda
d586dddfcd Update changelog.txt 2021-01-18 01:59:31 +08:00
5 changed files with 221 additions and 32 deletions

View File

@@ -368,6 +368,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;
@@ -528,6 +530,8 @@ 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

View File

@@ -32,7 +32,7 @@ namespace N_m3u8DL_CLI
/*===============================================================================*/
static Version ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
static string nowVer = $"{ver.Major}.{ver.Minor}.{ver.Build}";
static string nowDate = "20201229";
static string nowDate = "20210124";
public static void WriteInit()
{
Console.Clear();

View File

@@ -61,14 +61,18 @@ namespace N_m3u8DL_CLI
void ExtractInitialization(XmlNode source)
{
var initialization = source.SelectSingleNode("//ns:Initialization", nsMgr);
var initialization = source.SelectSingleNode("ns:Initialization", nsMgr);
if (initialization != null)
{
MultisegmentInfo["InitializationUrl"] = ((XmlElement)initialization).GetAttribute("sourceURL");
if (((XmlElement)initialization).HasAttribute("range"))
{
MultisegmentInfo["InitializationUrl"] += "$$Range=" + ((XmlElement)initialization).GetAttribute("range");
}
}
}
var segmentList = Period.SelectSingleNode("//ns:SegmentList", nsMgr);
var segmentList = Period.SelectSingleNode("ns:SegmentList", nsMgr);
if (segmentList != null)
{
ExtractCommon(segmentList);
@@ -77,7 +81,14 @@ namespace N_m3u8DL_CLI
MultisegmentInfo["SegmentUrls"] = new List<string>();
foreach (XmlElement segment in segmentUrlsE)
{
MultisegmentInfo["SegmentUrls"].Add(segment.GetAttribute("media"));
if (segment.HasAttribute("mediaRange"))
{
MultisegmentInfo["SegmentUrls"].Add("$$Range=" + segment.GetAttribute("mediaRange"));
}
else
{
MultisegmentInfo["SegmentUrls"].Add(segment.GetAttribute("media"));
}
}
}
else
@@ -212,6 +223,7 @@ namespace N_m3u8DL_CLI
var bandwidth = IntOrNull(GetAttribute("bandwidth"));
var f = new Dictionary<string, dynamic>
{
["ContentType"] = contentType,
["FormatId"] = representationId,
["ManifestUrl"] = mpdUrl,
["Width"] = IntOrNull(GetAttribute("width")),
@@ -436,6 +448,10 @@ namespace N_m3u8DL_CLI
if (representationMsInfo.ContainsKey("InitializationUrl"))
{
f["InitializationUrl"] = representationMsInfo["InitializationUrl"];
if (f["InitializationUrl"].StartsWith("$$Range"))
{
f["InitializationUrl"] = CombineURL(baseUrl, f["InitializationUrl"]);
}
f["Fragments"] = representationMsInfo["Fragments"];
}
}
@@ -452,11 +468,11 @@ namespace N_m3u8DL_CLI
}
//处理同一ID分散在不同Period的情况
if (formatList.Any(_f => _f["FormatId"] == f["FormatId"] && _f["Width"] == f["Width"]))
if (formatList.Any(_f => _f["FormatId"] == f["FormatId"] && _f["Width"] == f["Width"] && _f["ContentType"] == f["ContentType"]))
{
for (int i = 0; i < formatList.Count; i++)
{
if (formatList[i]["FormatId"] == f["FormatId"] && formatList[i]["Width"] == f["Width"])
if (formatList[i]["FormatId"] == f["FormatId"] && formatList[i]["Width"] == f["Width"] && formatList[i]["ContentType"] == f["ContentType"])
{
formatList[i]["Fragments"].AddRange(f["Fragments"]);
break;
@@ -486,14 +502,14 @@ namespace N_m3u8DL_CLI
var audioLangList = new List<string>();
formatList.ForEach(f =>
{
if (f["Width"] == -1 && !audioLangList.Contains(f["Language"])) audioLangList.Add(f["Language"]);
if (f["ContentType"] == "audio" && !audioLangList.Contains(f["Language"])) audioLangList.Add(f["Language"]);
});
if (audioLangList.Count > 1)
{
string Stringify(Dictionary<string, dynamic> f)
{
var type = f["Width"] == -1 && f["Height"] == -1 ? "Audio" : "Video";
var type = f["ContentType"] == "aduio" ? "Audio" : "Video";
var res = type == "Video" ? $"[{f["Width"]}x{f["Height"]}]" : "";
var id = $"[{f["FormatId"]}] ";
var tbr = $"[{((int)f["Tbr"]).ToString().PadLeft(4)} Kbps] ";
@@ -504,6 +520,7 @@ namespace N_m3u8DL_CLI
return $"{type} => {id}{tbr}{asr}{fps}{lang}{codecs}{res}";
}
var startCursorIndex = LOGGER.CursorIndex;
for (int i = 0; i < formatList.Count; i++)
{
Console.WriteLine("".PadRight(13) + $"[{i.ToString().PadLeft(2)}]. {Stringify(formatList[i])}");
@@ -515,6 +532,12 @@ namespace N_m3u8DL_CLI
var input = Console.ReadLine();
LOGGER.CursorIndex += 2;
Console.CursorVisible = false;
for (int i = startCursorIndex; i < LOGGER.CursorIndex; i++)
{
Console.SetCursorPosition(0, i);
Console.Write("".PadRight(300));
}
LOGGER.CursorIndex = startCursorIndex;
if (!string.IsNullOrEmpty(input))
{
bestVideo = new Dictionary<string, dynamic>() { ["Tbr"] = 0 };
@@ -523,7 +546,7 @@ namespace N_m3u8DL_CLI
{
var n = 0;
int.TryParse(index, out n);
if (formatList[n]["Width"] == -1)
if (formatList[n]["ContentType"] == "audio")
{
bestAudio = formatList[n];
}
@@ -625,7 +648,7 @@ namespace N_m3u8DL_CLI
sb.AppendLine("#CREATED-BY:N_m3u8DL-CLI");
//Video
if (f["Width"] != -1 && f["Height"] != -1)
if (f["ContentType"] != "audio")
{
sb.AppendLine($"#EXT-VIDEO-WIDTH:{f["Width"]}");
sb.AppendLine($"#EXT-VIDEO-HEIGHT:{f["Height"]}");
@@ -635,7 +658,19 @@ namespace N_m3u8DL_CLI
sb.AppendLine($"#EXT-TBR:{f["Tbr"]}");
if (f.ContainsKey("InitializationUrl"))
{
sb.AppendLine($"#EXT-X-MAP:URI=\"{f["InitializationUrl"]}\"");
string initUrl = f["InitializationUrl"];
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 + 1 - start}@{start}\"");
}
else
{
sb.AppendLine($"#EXT-X-MAP:URI=\"{initUrl}\"");
}
}
sb.AppendLine("#EXT-X-KEY:METHOD=PLZ-KEEP-RAW,URI=\"None\""); //使下载器使用二进制合并
@@ -645,7 +680,19 @@ namespace N_m3u8DL_CLI
var dur = seg.ContainsKey("duration") ? seg["duration"] : 0.0;
var url = seg.ContainsKey("url") ? seg["url"] : seg["path"];
sb.AppendLine($"#EXTINF:{dur.ToString("0.00")}");
sb.AppendLine(url);
if (Regex.IsMatch(url, "\\$\\$Range=(\\d+)-(\\d+)"))
{
var match = Regex.Match(url, "\\$\\$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-BYTERANGE:{end + 1 - start}@{start}");
sb.AppendLine(url.Replace(rangeStr, ""));
}
else
{
sb.AppendLine(url);
}
}
sb.AppendLine("#EXT-X-ENDLIST");
@@ -663,7 +710,7 @@ namespace N_m3u8DL_CLI
{
var w = f["Width"];
var h = f["Height"];
if (w != -1 && h != -1)
if (f["ContentType"] == "video")
{
if (f["Tbr"] > bandwidth && w > width)
{
@@ -684,9 +731,7 @@ namespace N_m3u8DL_CLI
foreach (var f in fs)
{
var w = f["Width"];
var h = f["Height"];
if (w == -1 && h == -1)
if (f["ContentType"] == "audio")
{
if (f["Tbr"] > bandwidth)
{
@@ -731,6 +776,10 @@ namespace N_m3u8DL_CLI
/// <returns></returns>
static string CombineURL(string baseurl, string url)
{
if (url.StartsWith("$$Range"))
{
return baseurl + url;
}
Uri uri1 = new Uri(baseurl);
Uri uri2 = new Uri(uri1, url);
url = uri2.ToString();

View File

@@ -11,6 +11,29 @@ namespace N_m3u8DL_CLI
{
class Parser
{
struct Audio
{
public string Name;
public string Language;
public string Uri;
public string Channels;
public override string ToString()
{
return $"[{Name}] [{Language}] [{(string.IsNullOrEmpty(Channels) ? "" : $"{Channels}ch")}]".Replace("[]", "");
}
}
struct Subtitle
{
public string Name;
public string Language;
public string Uri;
public override string ToString()
{
return $"[{Name}] [{Language}]";
}
}
//存储上一行key的信息如果一样就跳过下载key这一步
private string lastKeyLine = string.Empty;
//METHOD, KEY, IV
@@ -21,9 +44,9 @@ namespace N_m3u8DL_CLI
private string bestUrl = string.Empty;
private string bestUrlAudio = string.Empty;
private string bestUrlSub = string.Empty;
Dictionary<string, string> MEDIA_AUDIO = new Dictionary<string, string>();
Dictionary<string, List<Audio>> MEDIA_AUDIO_GROUP = new Dictionary<string, List<Audio>>(); //外挂音频所有分组信息
private string audioUrl = string.Empty; //音轨地址
Dictionary<string, string> MEDIA_SUB = new Dictionary<string, string>();
Dictionary<string, List<Subtitle>> MEDIA_SUB_GROUP = new Dictionary<string, List<Subtitle>>(); //外挂字幕所有分组信息
private string subUrl = string.Empty; //字幕地址
//存放多轨道的信息
private ArrayList extLists = new ArrayList();
@@ -65,6 +88,8 @@ namespace N_m3u8DL_CLI
JArray segments = new JArray();
JObject segInfo = new JObject();
extLists.Clear();
MEDIA_AUDIO_GROUP.Clear();
MEDIA_SUB_GROUP.Clear();
string m3u8Content = string.Empty;
string m3u8Method = string.Empty;
string[] extMAP = { "", "" };
@@ -143,6 +168,16 @@ namespace N_m3u8DL_CLI
}
}
//正对Disney+修正
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && M3u8Url.Contains("media.dssott.com/"))
{
Regex ykmap = new Regex("#EXT-X-MAP:URI=\\\".*?BUMPER/[\\s\\S]+?#EXT-X-DISCONTINUITY");
if (ykmap.IsMatch(m3u8Content))
{
m3u8Content = m3u8Content.Replace(ykmap.Match(m3u8Content).Value, "#XXX");
}
}
//如果BaseUrl为空则截取字符串充当
if (BaseUrl == "")
{
@@ -332,12 +367,37 @@ namespace N_m3u8DL_CLI
else if (line.StartsWith(HLSTags.ext_x_i_frame_stream_inf)) ;
else if (line.StartsWith(HLSTags.ext_x_media))
{
if (Global.GetTagAttribute(line, "TYPE") == "AUDIO" && !MEDIA_AUDIO.ContainsKey(Global.GetTagAttribute(line, "GROUP-ID")))
MEDIA_AUDIO.Add(Global.GetTagAttribute(line, "GROUP-ID"), CombineURL(BaseUrl, Global.GetTagAttribute(line, "URI")));
if (Global.GetTagAttribute(line, "TYPE") == "SUBTITLES")
var groupId = Global.GetTagAttribute(line, "GROUP-ID");
if (Global.GetTagAttribute(line, "TYPE") == "AUDIO")
{
if (!MEDIA_SUB.ContainsKey(Global.GetTagAttribute(line, "GROUP-ID")))
MEDIA_SUB.Add(Global.GetTagAttribute(line, "GROUP-ID"), CombineURL(BaseUrl, Global.GetTagAttribute(line, "URI")));
var audio = new Audio();
audio.Channels = Global.GetTagAttribute(line, "CHANNELS");
audio.Language = Global.GetTagAttribute(line, "LANGUAGE");
audio.Name = Global.GetTagAttribute(line, "NAME");
audio.Uri = CombineURL(BaseUrl, Global.GetTagAttribute(line, "URI"));
if (!MEDIA_AUDIO_GROUP.ContainsKey(groupId))
{
MEDIA_AUDIO_GROUP.Add(groupId, new List<Audio>() { audio });
}
else
{
MEDIA_AUDIO_GROUP[groupId].Add(audio);
}
}
else if (Global.GetTagAttribute(line, "TYPE") == "SUBTITLES")
{
var sub = new Subtitle();
sub.Language = Global.GetTagAttribute(line, "LANGUAGE");
sub.Name = Global.GetTagAttribute(line, "NAME");
sub.Uri = CombineURL(BaseUrl, Global.GetTagAttribute(line, "URI"));
if (!MEDIA_SUB_GROUP.ContainsKey(groupId))
{
MEDIA_SUB_GROUP.Add(groupId, new List<Subtitle>() { sub });
}
else
{
MEDIA_SUB_GROUP[groupId].Add(sub);
}
}
}
else if (line.StartsWith(HLSTags.ext_x_playlist_type)) ;
@@ -504,13 +564,71 @@ namespace N_m3u8DL_CLI
jsonM3u8Info.Add("vod", isEndlist);
jsonM3u8Info.Add("targetDuration", targetDuration);
jsonM3u8Info.Add("totalDuration", totalDuration);
if (bestUrlAudio != "" && MEDIA_AUDIO.ContainsKey(bestUrlAudio))
if (audioUrl != "")
jsonM3u8Info.Add("audio", audioUrl);
if (subUrl != "")
jsonM3u8Info.Add("sub", subUrl);
if (bestUrlAudio != "" && MEDIA_AUDIO_GROUP.ContainsKey(bestUrlAudio))
{
jsonM3u8Info.Add("audio", MEDIA_AUDIO[bestUrlAudio]);
if (MEDIA_AUDIO_GROUP[bestUrlAudio].Count == 1)
{
audioUrl = MEDIA_AUDIO_GROUP[bestUrlAudio][0].Uri;
}
//多种音频语言 让用户选择
else
{
var startCursorIndex = LOGGER.CursorIndex;
LOGGER.PrintLine("Found Multiple Language Audio Tracks.", LOGGER.Warning);
for (int i = 0; i < MEDIA_AUDIO_GROUP[bestUrlAudio].Count; i++)
{
Console.WriteLine("".PadRight(13) + $"[{i.ToString().PadLeft(2)}]. {bestUrlAudio} => {MEDIA_AUDIO_GROUP[bestUrlAudio][i]}");
LOGGER.CursorIndex++;
}
Console.CursorVisible = true;
LOGGER.PrintLine("Please Select What You Want.(Up To 1 Track)");
Console.Write("".PadRight(13) + "Enter Number: ");
var input = Console.ReadLine();
LOGGER.CursorIndex += 2;
Console.CursorVisible = false;
for (int i = startCursorIndex; i < LOGGER.CursorIndex; i++)
{
Console.SetCursorPosition(0, i);
Console.Write("".PadRight(300));
}
LOGGER.CursorIndex = startCursorIndex;
audioUrl = MEDIA_AUDIO_GROUP[bestUrlAudio][int.Parse(input)].Uri;
}
}
if (bestUrlSub != "" && MEDIA_SUB.ContainsKey(bestUrlSub))
if (bestUrlSub != "" && MEDIA_SUB_GROUP.ContainsKey(bestUrlSub))
{
jsonM3u8Info.Add("sub", MEDIA_SUB[bestUrlSub]);
if (MEDIA_SUB_GROUP[bestUrlSub].Count == 1)
{
subUrl = MEDIA_SUB_GROUP[bestUrlSub][0].Uri;
}
//多种字幕语言 让用户选择
else
{
var startCursorIndex = LOGGER.CursorIndex;
LOGGER.PrintLine("Found Multiple Language Subtitle Tracks.", LOGGER.Warning);
for (int i = 0; i < MEDIA_SUB_GROUP[bestUrlSub].Count; i++)
{
Console.WriteLine("".PadRight(13) + $"[{i.ToString().PadLeft(2)}]. {bestUrlSub} => {MEDIA_SUB_GROUP[bestUrlSub][i]}");
LOGGER.CursorIndex++;
}
Console.CursorVisible = true;
LOGGER.PrintLine("Please Select What You Want.(Up To 1 Track)");
Console.Write("".PadRight(13) + "Enter Number: ");
var input = Console.ReadLine();
LOGGER.CursorIndex += 2;
Console.CursorVisible = false;
for (int i = startCursorIndex; i < LOGGER.CursorIndex; i++)
{
Console.SetCursorPosition(0, i);
Console.Write("".PadRight(300));
}
LOGGER.CursorIndex = startCursorIndex;
subUrl = MEDIA_SUB_GROUP[bestUrlSub][int.Parse(input)].Uri;
}
}
if (extMAP[0] != "")
{
@@ -807,13 +925,24 @@ namespace N_m3u8DL_CLI
File.Copy(m3u8SavePath, Path.GetDirectoryName(m3u8SavePath) + "\\master.m3u8", true);
LOGGER.WriteLine("Master List Found");
LOGGER.PrintLine(strings.masterListFound, LOGGER.Warning);
string t = "{" + "\"masterUri\":\"" + M3u8Url + "\","
+ "\"updateTime\":\"" + DateTime.Now.ToString("o") + "\","
+ "\"playLists:\":[" + string.Join(",", extLists.ToArray()) + "]" + "}";
var json = new JObject();
json.Add("masterUri", M3u8Url);
json.Add("updateTime", DateTime.Now.ToString("o"));
json.Add("playLists", JArray.Parse("[" + string.Join(",", extLists.ToArray()) + "]"));
if (MEDIA_AUDIO_GROUP.Keys.Count > 0)
{
var audioGroup = JObject.FromObject(MEDIA_AUDIO_GROUP);
json.Add("audioTracks", audioGroup);
}
if (MEDIA_SUB_GROUP.Keys.Count > 0)
{
var subGroup = JObject.FromObject(MEDIA_SUB_GROUP);
json.Add("subtitleTracks", subGroup);
}
//输出json文件
LOGGER.WriteLine(strings.wrtingMasterMeta);
LOGGER.PrintLine(strings.wrtingMasterMeta);
File.WriteAllText(Path.GetDirectoryName(jsonSavePath) + "\\playLists.json", Global.ConvertJsonString(t));
File.WriteAllText(Path.GetDirectoryName(jsonSavePath) + "\\playLists.json", json.ToString());
LOGGER.WriteLine(strings.selectPlaylist + ": " + bestUrl);
LOGGER.PrintLine(strings.selectPlaylist);
LOGGER.WriteLine(strings.startReParsing);

View File

@@ -305,4 +305,11 @@
2020年12月12日
- 修复MPD下同一个ID分散在不同Period导致下载不完全问题
2020年12月20日
- 支持解密虎课网
- 支持解密虎课网
2021年1月18日
- 完善MPD下载相关
- 重新打包多语言资源
2021年1月24日
- 适配Disney+资源
- MPD选择流行为优化
- 修复二进制合并时vtt字幕被合并为ts后缀问题