调整部分代码结构
This commit is contained in:
parent
7d8e7c6402
commit
b9d3b57b39
|
@ -5,10 +5,10 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
|
|
||||||
|
public class EncryptInfo
|
||||||
{
|
{
|
||||||
public class EncryptInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 加密方式,默认无加密
|
/// 加密方式,默认无加密
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -39,5 +39,4 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
return EncryptMethod.UNKNOWN;
|
return EncryptMethod.UNKNOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,13 +1,7 @@
|
||||||
using System;
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
public class MSSData
|
||||||
{
|
{
|
||||||
public class MSSData
|
|
||||||
{
|
|
||||||
public string FourCC { get; set; } = "";
|
public string FourCC { get; set; } = "";
|
||||||
public string CodecPrivateData { get; set; } = "";
|
public string CodecPrivateData { get; set; } = "";
|
||||||
public string Type { get; set; } = "";
|
public string Type { get; set; } = "";
|
||||||
|
@ -21,5 +15,4 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
public bool IsProtection { get; set; } = false;
|
public bool IsProtection { get; set; } = false;
|
||||||
public string ProtectionSystemID { get; set; } = "";
|
public string ProtectionSystemID { get; set; } = "";
|
||||||
public string ProtectionData { get; set; } = "";
|
public string ProtectionData { get; set; } = "";
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,14 +1,7 @@
|
||||||
using System;
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
// 主要处理 EXT-X-DISCONTINUITY
|
||||||
|
public class MediaPart
|
||||||
{
|
{
|
||||||
//主要处理 EXT-X-DISCONTINUITY
|
|
||||||
public class MediaPart
|
|
||||||
{
|
|
||||||
public List<MediaSegment> MediaSegments { get; set; } = new List<MediaSegment>();
|
public List<MediaSegment> MediaSegments { get; set; } = new List<MediaSegment>();
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,33 +1,27 @@
|
||||||
using System;
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
public class MediaSegment
|
||||||
{
|
{
|
||||||
public class MediaSegment
|
|
||||||
{
|
|
||||||
public long Index { get; set; }
|
public long Index { get; set; }
|
||||||
public double Duration { get; set; }
|
public double Duration { get; set; }
|
||||||
public string? Title { get; set; }
|
public string? Title { get; set; }
|
||||||
public DateTime? DateTime { get; set; }
|
public DateTime? DateTime { get; set; }
|
||||||
|
|
||||||
public long? StartRange { get; set; }
|
public long? StartRange { get; set; }
|
||||||
public long? StopRange { get => (StartRange != null && ExpectLength != null) ? StartRange + ExpectLength - 1 : null; }
|
public long? StopRange => (StartRange != null && ExpectLength != null) ? StartRange + ExpectLength - 1 : null;
|
||||||
public long? ExpectLength { get; set; }
|
public long? ExpectLength { get; set; }
|
||||||
|
|
||||||
public EncryptInfo EncryptInfo { get; set; } = new EncryptInfo();
|
public EncryptInfo EncryptInfo { get; set; } = new EncryptInfo();
|
||||||
|
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
|
|
||||||
public string? NameFromVar { get; set; } //MPD分段文件名
|
public string? NameFromVar { get; set; } // MPD分段文件名
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
return obj is MediaSegment segment &&
|
return obj is MediaSegment segment &&
|
||||||
Index == segment.Index &&
|
Index == segment.Index &&
|
||||||
Duration == segment.Duration &&
|
Math.Abs(Duration - segment.Duration) < 0.001 &&
|
||||||
Title == segment.Title &&
|
Title == segment.Title &&
|
||||||
StartRange == segment.StartRange &&
|
StartRange == segment.StartRange &&
|
||||||
StopRange == segment.StopRange &&
|
StopRange == segment.StopRange &&
|
||||||
|
@ -39,5 +33,4 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
{
|
{
|
||||||
return HashCode.Combine(Index, Duration, Title, StartRange, StopRange, ExpectLength, Url);
|
return HashCode.Combine(Index, Duration, Title, StartRange, StopRange, ExpectLength, Url);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,27 +1,19 @@
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
public class Playlist
|
||||||
{
|
{
|
||||||
public class Playlist
|
// 对应Url信息
|
||||||
{
|
|
||||||
//对应Url信息
|
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
//是否直播
|
// 是否直播
|
||||||
public bool IsLive { get; set; } = false;
|
public bool IsLive { get; set; } = false;
|
||||||
//直播刷新间隔毫秒(默认15秒)
|
// 直播刷新间隔毫秒(默认15秒)
|
||||||
public double RefreshIntervalMs { get; set; } = 15000;
|
public double RefreshIntervalMs { get; set; } = 15000;
|
||||||
//所有分片时长总和
|
// 所有分片时长总和
|
||||||
public double TotalDuration { get => MediaParts.Sum(x => x.MediaSegments.Sum(m => m.Duration)); }
|
public double TotalDuration { get => MediaParts.Sum(x => x.MediaSegments.Sum(m => m.Duration)); }
|
||||||
//所有分片中最长时长
|
// 所有分片中最长时长
|
||||||
public double? TargetDuration { get; set; }
|
public double? TargetDuration { get; set; }
|
||||||
//INIT信息
|
// INIT信息
|
||||||
public MediaSegment? MediaInit { get; set; }
|
public MediaSegment? MediaInit { get; set; }
|
||||||
//分片信息
|
// 分片信息
|
||||||
public List<MediaPart> MediaParts { get; set; } = new List<MediaPart>();
|
public List<MediaPart> MediaParts { get; set; } = new List<MediaPart>();
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,29 +1,24 @@
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
|
|
||||||
|
public class StreamSpec
|
||||||
{
|
{
|
||||||
public class StreamSpec
|
|
||||||
{
|
|
||||||
public MediaType? MediaType { get; set; }
|
public MediaType? MediaType { get; set; }
|
||||||
public string? GroupId { get; set; }
|
public string? GroupId { get; set; }
|
||||||
public string? Language { get; set; }
|
public string? Language { get; set; }
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
public Choise? Default { get; set; }
|
public Choise? Default { get; set; }
|
||||||
|
|
||||||
//由于用户选择 被跳过的分片总时长
|
// 由于用户选择 被跳过的分片总时长
|
||||||
public double? SkippedDuration { get; set; }
|
public double? SkippedDuration { get; set; }
|
||||||
|
|
||||||
//MSS信息
|
// MSS信息
|
||||||
public MSSData? MSSData { get; set; }
|
public MSSData? MSSData { get; set; }
|
||||||
|
|
||||||
//基本信息
|
// 基本信息
|
||||||
public int? Bandwidth { get; set; }
|
public int? Bandwidth { get; set; }
|
||||||
public string? Codecs { get; set; }
|
public string? Codecs { get; set; }
|
||||||
public string? Resolution { get; set; }
|
public string? Resolution { get; set; }
|
||||||
|
@ -31,17 +26,17 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
public string? Channels { get; set; }
|
public string? Channels { get; set; }
|
||||||
public string? Extension { get; set; }
|
public string? Extension { get; set; }
|
||||||
|
|
||||||
//Dash
|
// Dash
|
||||||
public RoleType? Role { get; set; }
|
public RoleType? Role { get; set; }
|
||||||
|
|
||||||
//补充信息-色域
|
// 补充信息-色域
|
||||||
public string? VideoRange { get; set; }
|
public string? VideoRange { get; set; }
|
||||||
//补充信息-特征
|
// 补充信息-特征
|
||||||
public string? Characteristics { get; set; }
|
public string? Characteristics { get; set; }
|
||||||
//发布时间(仅MPD需要)
|
// 发布时间(仅MPD需要)
|
||||||
public DateTime? PublishTime { get; set; }
|
public DateTime? PublishTime { get; set; }
|
||||||
|
|
||||||
//外部轨道GroupId (后续寻找对应轨道信息)
|
// 外部轨道GroupId (后续寻找对应轨道信息)
|
||||||
public string? AudioId { get; set; }
|
public string? AudioId { get; set; }
|
||||||
public string? VideoId { get; set; }
|
public string? VideoId { get; set; }
|
||||||
public string? SubtitleId { get; set; }
|
public string? SubtitleId { get; set; }
|
||||||
|
@ -143,7 +138,7 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
var encStr = string.Empty;
|
var encStr = string.Empty;
|
||||||
var segmentsCountStr = SegmentsCount == 0 ? "" : (SegmentsCount > 1 ? $"{SegmentsCount} Segments" : $"{SegmentsCount} Segment");
|
var segmentsCountStr = SegmentsCount == 0 ? "" : (SegmentsCount > 1 ? $"{SegmentsCount} Segments" : $"{SegmentsCount} Segment");
|
||||||
|
|
||||||
//增加加密标志
|
// 增加加密标志
|
||||||
if (Playlist != null && Playlist.MediaParts.Any(m => m.MediaSegments.Any(s => s.EncryptInfo.Method != EncryptMethod.NONE)))
|
if (Playlist != null && Playlist.MediaParts.Any(m => m.MediaSegments.Any(s => s.EncryptInfo.Method != EncryptMethod.NONE)))
|
||||||
{
|
{
|
||||||
var ms = Playlist.MediaParts.SelectMany(m => m.MediaSegments.Select(s => s.EncryptInfo.Method)).Where(e => e != EncryptMethod.NONE).Distinct();
|
var ms = Playlist.MediaParts.SelectMany(m => m.MediaSegments.Select(s => s.EncryptInfo.Method)).Where(e => e != EncryptMethod.NONE).Distinct();
|
||||||
|
@ -175,7 +170,7 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
returnStr = returnStr.Replace("| |", "|");
|
returnStr = returnStr.Replace("| |", "|");
|
||||||
}
|
}
|
||||||
|
|
||||||
//计算时长
|
// 计算时长
|
||||||
if (Playlist != null)
|
if (Playlist != null)
|
||||||
{
|
{
|
||||||
var total = Playlist.TotalDuration;
|
var total = Playlist.TotalDuration;
|
||||||
|
@ -184,5 +179,4 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
|
|
||||||
return returnStr.TrimEnd().TrimEnd('|').TrimEnd();
|
return returnStr.TrimEnd().TrimEnd('|').TrimEnd();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,13 +1,7 @@
|
||||||
using System;
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
public class SubCue
|
||||||
{
|
{
|
||||||
public class SubCue
|
|
||||||
{
|
|
||||||
public TimeSpan StartTime { get; set; }
|
public TimeSpan StartTime { get; set; }
|
||||||
public TimeSpan EndTime { get; set; }
|
public TimeSpan EndTime { get; set; }
|
||||||
public required string Payload { get; set; }
|
public required string Payload { get; set; }
|
||||||
|
@ -26,5 +20,4 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
{
|
{
|
||||||
return HashCode.Combine(StartTime, EndTime, Payload, Settings);
|
return HashCode.Combine(StartTime, EndTime, Payload, Settings);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,14 +1,10 @@
|
||||||
using System;
|
using System.Text;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
|
|
||||||
|
public partial class WebVttSub
|
||||||
{
|
{
|
||||||
public partial class WebVttSub
|
|
||||||
{
|
|
||||||
[GeneratedRegex("X-TIMESTAMP-MAP.*")]
|
[GeneratedRegex("X-TIMESTAMP-MAP.*")]
|
||||||
private static partial Regex TSMapRegex();
|
private static partial Regex TSMapRegex();
|
||||||
[GeneratedRegex("MPEGTS:(\\d+)")]
|
[GeneratedRegex("MPEGTS:(\\d+)")]
|
||||||
|
@ -80,7 +76,7 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
if (string.IsNullOrEmpty(line.Trim()))
|
if (string.IsNullOrEmpty(line.Trim()))
|
||||||
{
|
{
|
||||||
var payload = string.Join(Environment.NewLine, payloads);
|
var payload = string.Join(Environment.NewLine, payloads);
|
||||||
if (string.IsNullOrEmpty(payload.Trim())) continue; //没获取到payload 跳过添加
|
if (string.IsNullOrEmpty(payload.Trim())) continue; // 没获取到payload 跳过添加
|
||||||
|
|
||||||
var arr = SplitRegex().Split(timeLine.Replace("-->", "")).Where(s => !string.IsNullOrEmpty(s)).ToList();
|
var arr = SplitRegex().Split(timeLine.Replace("-->", "")).Where(s => !string.IsNullOrEmpty(s)).ToList();
|
||||||
var startTime = ConvertToTS(arr[0]);
|
var startTime = ConvertToTS(arr[0]);
|
||||||
|
@ -90,7 +86,7 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
{
|
{
|
||||||
StartTime = startTime,
|
StartTime = startTime,
|
||||||
EndTime = endTime,
|
EndTime = endTime,
|
||||||
Payload = RemoveClassTag(string.Join("", payload.Where(c => c != 8203))), //Remove Zero Width Space!
|
Payload = RemoveClassTag(string.Join("", payload.Where(c => c != 8203))), // Remove Zero Width Space!
|
||||||
Settings = style
|
Settings = style
|
||||||
});
|
});
|
||||||
payloads.Clear();
|
payloads.Clear();
|
||||||
|
@ -146,7 +142,7 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
{
|
{
|
||||||
if (!this.Cues.Contains(item))
|
if (!this.Cues.Contains(item))
|
||||||
{
|
{
|
||||||
//如果相差只有1ms,且payload相同,则拼接
|
// 如果相差只有1ms,且payload相同,则拼接
|
||||||
var last = this.Cues.LastOrDefault();
|
var last = this.Cues.LastOrDefault();
|
||||||
if (last != null && this.Cues.Count > 0 && (item.StartTime - last.EndTime).TotalMilliseconds <= 1 && item.Payload == last.Payload)
|
if (last != null && this.Cues.Count > 0 && (item.StartTime - last.EndTime).TotalMilliseconds <= 1 && item.Payload == last.Payload)
|
||||||
{
|
{
|
||||||
|
@ -168,13 +164,13 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//确实存在时间轴错误的情况,才修复
|
// 确实存在时间轴错误的情况,才修复
|
||||||
if ((this.Cues.Count > 0 && sub.Cues.Count > 0 && sub.Cues.First().StartTime < this.Cues.Last().EndTime && sub.Cues.First().EndTime != this.Cues.Last().EndTime) || this.Cues.Count == 0)
|
if ((this.Cues.Count > 0 && sub.Cues.Count > 0 && sub.Cues.First().StartTime < this.Cues.Last().EndTime && sub.Cues.First().EndTime != this.Cues.Last().EndTime) || this.Cues.Count == 0)
|
||||||
{
|
{
|
||||||
//The MPEG2 transport stream clocks (PCR, PTS, DTS) all have units of 1/90000 second
|
// The MPEG2 transport stream clocks (PCR, PTS, DTS) all have units of 1/90000 second
|
||||||
var seconds = (sub.MpegtsTimestamp - baseTimestamp) / 90000;
|
var seconds = (sub.MpegtsTimestamp - baseTimestamp) / 90000;
|
||||||
var offset = TimeSpan.FromSeconds(seconds);
|
var offset = TimeSpan.FromSeconds(seconds);
|
||||||
//当前预添加的字幕的起始时间小于实际上已经走过的时间(如offset已经是100秒,而字幕起始却是2秒),才修复
|
// 当前预添加的字幕的起始时间小于实际上已经走过的时间(如offset已经是100秒,而字幕起始却是2秒),才修复
|
||||||
if (sub.Cues.Count > 0 && sub.Cues.First().StartTime < offset)
|
if (sub.Cues.Count > 0 && sub.Cues.First().StartTime < offset)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < sub.Cues.Count; i++)
|
for (int i = 0; i < sub.Cues.Count; i++)
|
||||||
|
@ -193,7 +189,7 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
|
|
||||||
private static TimeSpan ConvertToTS(string str)
|
private static TimeSpan ConvertToTS(string str)
|
||||||
{
|
{
|
||||||
//17.0s
|
// 17.0s
|
||||||
if (str.EndsWith('s'))
|
if (str.EndsWith('s'))
|
||||||
{
|
{
|
||||||
double sec = Convert.ToDouble(str[..^1]);
|
double sec = Convert.ToDouble(str[..^1]);
|
||||||
|
@ -219,7 +215,7 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
foreach (var c in GetCues()) //输出时去除空串
|
foreach (var c in GetCues()) // 输出时去除空串
|
||||||
{
|
{
|
||||||
sb.AppendLine(c.StartTime.ToString(@"hh\:mm\:ss\.fff") + " --> " + c.EndTime.ToString(@"hh\:mm\:ss\.fff") + " " + c.Settings);
|
sb.AppendLine(c.StartTime.ToString(@"hh\:mm\:ss\.fff") + " --> " + c.EndTime.ToString(@"hh\:mm\:ss\.fff") + " " + c.Settings);
|
||||||
sb.AppendLine(c.Payload);
|
sb.AppendLine(c.Payload);
|
||||||
|
@ -267,10 +263,9 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(srt.Trim()))
|
if (string.IsNullOrEmpty(srt.Trim()))
|
||||||
{
|
{
|
||||||
srt = "1\r\n00:00:00,000 --> 00:00:01,000"; //空字幕
|
srt = "1\r\n00:00:00,000 --> 00:00:01,000"; // 空字幕
|
||||||
}
|
}
|
||||||
|
|
||||||
return srt;
|
return srt;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,14 +1,7 @@
|
||||||
using System;
|
namespace N_m3u8DL_RE.Common.Enum;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Enum
|
public enum Choise
|
||||||
{
|
{
|
||||||
public enum Choise
|
|
||||||
{
|
|
||||||
YES = 1,
|
YES = 1,
|
||||||
NO = 0
|
NO = 0
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,13 +1,7 @@
|
||||||
using System;
|
namespace N_m3u8DL_RE.Common.Enum;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Enum
|
public enum EncryptMethod
|
||||||
{
|
{
|
||||||
public enum EncryptMethod
|
|
||||||
{
|
|
||||||
NONE,
|
NONE,
|
||||||
AES_128,
|
AES_128,
|
||||||
AES_128_ECB,
|
AES_128_ECB,
|
||||||
|
@ -16,5 +10,4 @@ namespace N_m3u8DL_RE.Common.Enum
|
||||||
CENC,
|
CENC,
|
||||||
CHACHA20,
|
CHACHA20,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,16 +1,9 @@
|
||||||
using System;
|
namespace N_m3u8DL_RE.Common.Enum;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Enum
|
public enum ExtractorType
|
||||||
{
|
{
|
||||||
public enum ExtractorType
|
|
||||||
{
|
|
||||||
MPEG_DASH,
|
MPEG_DASH,
|
||||||
HLS,
|
HLS,
|
||||||
HTTP_LIVE,
|
HTTP_LIVE,
|
||||||
MSS
|
MSS
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,16 +1,9 @@
|
||||||
using System;
|
namespace N_m3u8DL_RE.Common.Enum;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Enum
|
public enum MediaType
|
||||||
{
|
{
|
||||||
public enum MediaType
|
|
||||||
{
|
|
||||||
AUDIO = 0,
|
AUDIO = 0,
|
||||||
VIDEO = 1,
|
VIDEO = 1,
|
||||||
SUBTITLES = 2,
|
SUBTITLES = 2,
|
||||||
CLOSED_CAPTIONS = 3
|
CLOSED_CAPTIONS = 3
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
namespace N_m3u8DL_RE.Common.Enum
|
namespace N_m3u8DL_RE.Common.Enum;
|
||||||
|
|
||||||
|
public enum RoleType
|
||||||
{
|
{
|
||||||
public enum RoleType
|
|
||||||
{
|
|
||||||
Subtitle = 0,
|
Subtitle = 0,
|
||||||
Main = 1,
|
Main = 1,
|
||||||
Alternate = 2,
|
Alternate = 2,
|
||||||
|
@ -11,5 +11,4 @@
|
||||||
Description = 6,
|
Description = 6,
|
||||||
Sign = 7,
|
Sign = 7,
|
||||||
Metadata = 8,
|
Metadata = 8,
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -2,21 +2,20 @@
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common
|
namespace N_m3u8DL_RE.Common;
|
||||||
{
|
|
||||||
[JsonSourceGenerationOptions(
|
[JsonSourceGenerationOptions(
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
|
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
|
||||||
GenerationMode = JsonSourceGenerationMode.Metadata)]
|
GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||||
[JsonSerializable(typeof(MediaType))]
|
[JsonSerializable(typeof(MediaType))]
|
||||||
[JsonSerializable(typeof(EncryptMethod))]
|
[JsonSerializable(typeof(EncryptMethod))]
|
||||||
[JsonSerializable(typeof(ExtractorType))]
|
[JsonSerializable(typeof(ExtractorType))]
|
||||||
[JsonSerializable(typeof(Choise))]
|
[JsonSerializable(typeof(Choise))]
|
||||||
[JsonSerializable(typeof(StreamSpec))]
|
[JsonSerializable(typeof(StreamSpec))]
|
||||||
[JsonSerializable(typeof(IOrderedEnumerable<StreamSpec>))]
|
[JsonSerializable(typeof(IOrderedEnumerable<StreamSpec>))]
|
||||||
[JsonSerializable(typeof(IEnumerable<MediaSegment>))]
|
[JsonSerializable(typeof(IEnumerable<MediaSegment>))]
|
||||||
[JsonSerializable(typeof(List<StreamSpec>))]
|
[JsonSerializable(typeof(List<StreamSpec>))]
|
||||||
[JsonSerializable(typeof(List<MediaSegment>))]
|
[JsonSerializable(typeof(List<MediaSegment>))]
|
||||||
[JsonSerializable(typeof(Dictionary<string, string>))]
|
[JsonSerializable(typeof(Dictionary<string, string>))]
|
||||||
internal partial class JsonContext : JsonSerializerContext { }
|
internal partial class JsonContext : JsonSerializerContext { }
|
||||||
}
|
|
|
@ -6,12 +6,11 @@ using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.JsonConverter
|
namespace N_m3u8DL_RE.Common.JsonConverter;
|
||||||
|
|
||||||
|
internal class BytesBase64Converter : JsonConverter<byte[]>
|
||||||
{
|
{
|
||||||
internal class BytesBase64Converter : JsonConverter<byte[]>
|
|
||||||
{
|
|
||||||
public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.GetBytesFromBase64();
|
public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.GetBytesFromBase64();
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options) => writer.WriteStringValue(Convert.ToBase64String(value));
|
public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options) => writer.WriteStringValue(Convert.ToBase64String(value));
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -8,27 +8,27 @@ public class NonAnsiWriter : TextWriter
|
||||||
{
|
{
|
||||||
public override Encoding Encoding => Console.OutputEncoding;
|
public override Encoding Encoding => Console.OutputEncoding;
|
||||||
|
|
||||||
private string lastOut = "";
|
private string? _lastOut = "";
|
||||||
|
|
||||||
public override void Write(char value)
|
public override void Write(char value)
|
||||||
{
|
{
|
||||||
Console.Write(value);
|
Console.Write(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(string value)
|
public override void Write(string? value)
|
||||||
{
|
{
|
||||||
if (lastOut == value)
|
if (_lastOut == value)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastOut = value;
|
_lastOut = value;
|
||||||
RemoveAnsiEscapeSequences(value);
|
RemoveAnsiEscapeSequences(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveAnsiEscapeSequences(string input)
|
private void RemoveAnsiEscapeSequences(string? input)
|
||||||
{
|
{
|
||||||
// Use regular expression to remove ANSI escape sequences
|
// Use regular expression to remove ANSI escape sequences
|
||||||
string output = Regex.Replace(input, @"\x1B\[(\d+;?)+m", "");
|
string output = Regex.Replace(input ?? "", @"\x1B\[(\d+;?)+m", "");
|
||||||
output = Regex.Replace(output, @"\[\??\d+[AKlh]", "");
|
output = Regex.Replace(output, @"\[\??\d+[AKlh]", "");
|
||||||
output = Regex.Replace(output,"[\r\n] +","");
|
output = Regex.Replace(output,"[\r\n] +","");
|
||||||
if (string.IsNullOrWhiteSpace(output))
|
if (string.IsNullOrWhiteSpace(output))
|
||||||
|
|
|
@ -4,14 +4,13 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Log
|
namespace N_m3u8DL_RE.Common.Log;
|
||||||
|
|
||||||
|
public enum LogLevel
|
||||||
{
|
{
|
||||||
public enum LogLevel
|
|
||||||
{
|
|
||||||
OFF,
|
OFF,
|
||||||
ERROR,
|
ERROR,
|
||||||
WARN,
|
WARN,
|
||||||
INFO,
|
INFO,
|
||||||
DEBUG,
|
DEBUG,
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,17 +1,11 @@
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using static System.Net.Mime.MediaTypeNames;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Log
|
namespace N_m3u8DL_RE.Common.Log;
|
||||||
|
|
||||||
|
public partial class Logger
|
||||||
{
|
{
|
||||||
public partial class Logger
|
|
||||||
{
|
|
||||||
[GeneratedRegex("{}")]
|
[GeneratedRegex("{}")]
|
||||||
private static partial Regex VarsRepRegex();
|
private static partial Regex VarsRepRegex();
|
||||||
|
|
||||||
|
@ -30,7 +24,7 @@ namespace N_m3u8DL_RE.Common.Log
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static string? LogFilePath { get; set; }
|
private static string? LogFilePath { get; set; }
|
||||||
|
|
||||||
//读写锁
|
// 读写锁
|
||||||
static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
|
static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
|
||||||
|
|
||||||
public static void InitLogFile()
|
public static void InitLogFile()
|
||||||
|
@ -54,7 +48,7 @@ namespace N_m3u8DL_RE.Common.Log
|
||||||
+ "Task Start: " + now.ToString("yyyy/MM/dd HH:mm:ss") + Environment.NewLine
|
+ "Task Start: " + now.ToString("yyyy/MM/dd HH:mm:ss") + Environment.NewLine
|
||||||
+ "Task CommandLine: " + Environment.CommandLine;
|
+ "Task CommandLine: " + Environment.CommandLine;
|
||||||
init += $"{Environment.NewLine}{Environment.NewLine}";
|
init += $"{Environment.NewLine}{Environment.NewLine}";
|
||||||
//若文件存在则加序号
|
// 若文件存在则加序号
|
||||||
while (File.Exists(LogFilePath))
|
while (File.Exists(LogFilePath))
|
||||||
{
|
{
|
||||||
LogFilePath = Path.Combine(Path.GetDirectoryName(LogFilePath)!, $"{fileName}-{index++}.log");
|
LogFilePath = Path.Combine(Path.GetDirectoryName(LogFilePath)!, $"{fileName}-{index++}.log");
|
||||||
|
@ -91,7 +85,7 @@ namespace N_m3u8DL_RE.Common.Log
|
||||||
var plain = write.RemoveMarkup() + subWrite.RemoveMarkup();
|
var plain = write.RemoveMarkup() + subWrite.RemoveMarkup();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//进入写入
|
// 进入写入
|
||||||
LogWriteLock.EnterWriteLock();
|
LogWriteLock.EnterWriteLock();
|
||||||
using (StreamWriter sw = File.AppendText(LogFilePath))
|
using (StreamWriter sw = File.AppendText(LogFilePath))
|
||||||
{
|
{
|
||||||
|
@ -100,7 +94,7 @@ namespace N_m3u8DL_RE.Common.Log
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
//释放占用
|
// 释放占用
|
||||||
LogWriteLock.ExitWriteLock();
|
LogWriteLock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,7 +219,7 @@ namespace N_m3u8DL_RE.Common.Log
|
||||||
var plain = GetCurrTime() + " " + "EXTRA: " + data.RemoveMarkup();
|
var plain = GetCurrTime() + " " + "EXTRA: " + data.RemoveMarkup();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//进入写入
|
// 进入写入
|
||||||
LogWriteLock.EnterWriteLock();
|
LogWriteLock.EnterWriteLock();
|
||||||
using (StreamWriter sw = File.AppendText(LogFilePath))
|
using (StreamWriter sw = File.AppendText(LogFilePath))
|
||||||
{
|
{
|
||||||
|
@ -234,10 +228,9 @@ namespace N_m3u8DL_RE.Common.Log
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
//释放占用
|
// 释放占用
|
||||||
LogWriteLock.ExitWriteLock();
|
LogWriteLock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,138 +1,131 @@
|
||||||
using System;
|
namespace N_m3u8DL_RE.Common.Resource;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Resource
|
public class ResString
|
||||||
{
|
{
|
||||||
public class ResString
|
public static readonly string ReLiveTs = "<RE_LIVE_TS>";
|
||||||
{
|
public static string singleFileRealtimeDecryptWarn => GetText("singleFileRealtimeDecryptWarn");
|
||||||
public readonly static string ReLiveTs = "<RE_LIVE_TS>";
|
public static string singleFileSplitWarn => GetText("singleFileSplitWarn");
|
||||||
public static string singleFileRealtimeDecryptWarn { get => GetText("singleFileRealtimeDecryptWarn"); }
|
public static string customRangeWarn => GetText("customRangeWarn");
|
||||||
public static string singleFileSplitWarn { get => GetText("singleFileSplitWarn"); }
|
public static string customRangeFound => GetText("customRangeFound");
|
||||||
public static string customRangeWarn { get => GetText("customRangeWarn"); }
|
public static string customAdKeywordsFound => GetText("customAdKeywordsFound");
|
||||||
public static string customRangeFound { get => GetText("customRangeFound"); }
|
public static string customRangeInvalid => GetText("customRangeInvalid");
|
||||||
public static string customAdKeywordsFound { get => GetText("customAdKeywordsFound"); }
|
public static string consoleRedirected => GetText("consoleRedirected");
|
||||||
public static string customRangeInvalid { get => GetText("customRangeInvalid"); }
|
public static string autoBinaryMerge => GetText("autoBinaryMerge");
|
||||||
public static string consoleRedirected { get => GetText("consoleRedirected"); }
|
public static string autoBinaryMerge2 => GetText("autoBinaryMerge2");
|
||||||
public static string autoBinaryMerge { get => GetText("autoBinaryMerge"); }
|
public static string autoBinaryMerge3 => GetText("autoBinaryMerge3");
|
||||||
public static string autoBinaryMerge2 { get => GetText("autoBinaryMerge2"); }
|
public static string autoBinaryMerge4 => GetText("autoBinaryMerge4");
|
||||||
public static string autoBinaryMerge3 { get => GetText("autoBinaryMerge3"); }
|
public static string autoBinaryMerge5 => GetText("autoBinaryMerge5");
|
||||||
public static string autoBinaryMerge4 { get => GetText("autoBinaryMerge4"); }
|
public static string autoBinaryMerge6 => GetText("autoBinaryMerge6");
|
||||||
public static string autoBinaryMerge5 { get => GetText("autoBinaryMerge5"); }
|
public static string badM3u8 => GetText("badM3u8");
|
||||||
public static string autoBinaryMerge6 { get => GetText("autoBinaryMerge6"); }
|
public static string binaryMerge => GetText("binaryMerge");
|
||||||
public static string badM3u8 { get => GetText("badM3u8"); }
|
public static string checkingLast => GetText("checkingLast");
|
||||||
public static string binaryMerge { get => GetText("binaryMerge"); }
|
public static string cmd_appendUrlParams => GetText("cmd_appendUrlParams");
|
||||||
public static string checkingLast { get => GetText("checkingLast"); }
|
public static string cmd_autoSelect => GetText("cmd_autoSelect");
|
||||||
public static string cmd_appendUrlParams { get => GetText("cmd_appendUrlParams"); }
|
public static string cmd_binaryMerge => GetText("cmd_binaryMerge");
|
||||||
public static string cmd_autoSelect { get => GetText("cmd_autoSelect"); }
|
public static string cmd_useFFmpegConcatDemuxer => GetText("cmd_useFFmpegConcatDemuxer");
|
||||||
public static string cmd_binaryMerge { get => GetText("cmd_binaryMerge"); }
|
public static string cmd_checkSegmentsCount => GetText("cmd_checkSegmentsCount");
|
||||||
public static string cmd_useFFmpegConcatDemuxer { get => GetText("cmd_useFFmpegConcatDemuxer"); }
|
public static string cmd_decryptionBinaryPath => GetText("cmd_decryptionBinaryPath");
|
||||||
public static string cmd_checkSegmentsCount { get => GetText("cmd_checkSegmentsCount"); }
|
public static string cmd_delAfterDone => GetText("cmd_delAfterDone");
|
||||||
public static string cmd_decryptionBinaryPath { get => GetText("cmd_decryptionBinaryPath"); }
|
public static string cmd_ffmpegBinaryPath => GetText("cmd_ffmpegBinaryPath");
|
||||||
public static string cmd_delAfterDone { get => GetText("cmd_delAfterDone"); }
|
public static string cmd_mkvmergeBinaryPath => GetText("cmd_mkvmergeBinaryPath");
|
||||||
public static string cmd_ffmpegBinaryPath { get => GetText("cmd_ffmpegBinaryPath"); }
|
public static string cmd_baseUrl => GetText("cmd_baseUrl");
|
||||||
public static string cmd_mkvmergeBinaryPath { get => GetText("cmd_mkvmergeBinaryPath"); }
|
public static string cmd_maxSpeed => GetText("cmd_maxSpeed");
|
||||||
public static string cmd_baseUrl { get => GetText("cmd_baseUrl"); }
|
public static string cmd_adKeyword => GetText("cmd_adKeyword");
|
||||||
public static string cmd_maxSpeed { get => GetText("cmd_maxSpeed"); }
|
public static string cmd_moreHelp => GetText("cmd_moreHelp");
|
||||||
public static string cmd_adKeyword { get => GetText("cmd_adKeyword"); }
|
public static string cmd_header => GetText("cmd_header");
|
||||||
public static string cmd_moreHelp { get => GetText("cmd_moreHelp"); }
|
public static string cmd_muxImport => GetText("cmd_muxImport");
|
||||||
public static string cmd_header { get => GetText("cmd_header"); }
|
public static string cmd_muxImport_more => GetText("cmd_muxImport_more");
|
||||||
public static string cmd_muxImport { get => GetText("cmd_muxImport"); }
|
public static string cmd_selectVideo => GetText("cmd_selectVideo");
|
||||||
public static string cmd_muxImport_more { get => GetText("cmd_muxImport_more"); }
|
public static string cmd_dropVideo => GetText("cmd_dropVideo");
|
||||||
public static string cmd_selectVideo { get => GetText("cmd_selectVideo"); }
|
public static string cmd_selectVideo_more => GetText("cmd_selectVideo_more");
|
||||||
public static string cmd_dropVideo { get => GetText("cmd_dropVideo"); }
|
public static string cmd_selectAudio => GetText("cmd_selectAudio");
|
||||||
public static string cmd_selectVideo_more { get => GetText("cmd_selectVideo_more"); }
|
public static string cmd_dropAudio => GetText("cmd_dropAudio");
|
||||||
public static string cmd_selectAudio { get => GetText("cmd_selectAudio"); }
|
public static string cmd_selectAudio_more => GetText("cmd_selectAudio_more");
|
||||||
public static string cmd_dropAudio { get => GetText("cmd_dropAudio"); }
|
public static string cmd_selectSubtitle => GetText("cmd_selectSubtitle");
|
||||||
public static string cmd_selectAudio_more { get => GetText("cmd_selectAudio_more"); }
|
public static string cmd_dropSubtitle => GetText("cmd_dropSubtitle");
|
||||||
public static string cmd_selectSubtitle { get => GetText("cmd_selectSubtitle"); }
|
public static string cmd_selectSubtitle_more => GetText("cmd_selectSubtitle_more");
|
||||||
public static string cmd_dropSubtitle { get => GetText("cmd_dropSubtitle"); }
|
public static string cmd_custom_range => GetText("cmd_custom_range");
|
||||||
public static string cmd_selectSubtitle_more { get => GetText("cmd_selectSubtitle_more"); }
|
public static string cmd_customHLSMethod => GetText("cmd_customHLSMethod");
|
||||||
public static string cmd_custom_range { get => GetText("cmd_custom_range"); }
|
public static string cmd_customHLSKey => GetText("cmd_customHLSKey");
|
||||||
public static string cmd_customHLSMethod { get => GetText("cmd_customHLSMethod"); }
|
public static string cmd_customHLSIv => GetText("cmd_customHLSIv");
|
||||||
public static string cmd_customHLSKey { get => GetText("cmd_customHLSKey"); }
|
public static string cmd_Input => GetText("cmd_Input");
|
||||||
public static string cmd_customHLSIv { get => GetText("cmd_customHLSIv"); }
|
public static string cmd_forceAnsiConsole => GetText("cmd_forceAnsiConsole");
|
||||||
public static string cmd_Input { get => GetText("cmd_Input"); }
|
public static string cmd_noAnsiColor => GetText("cmd_noAnsiColor");
|
||||||
public static string cmd_forceAnsiConsole { get => GetText("cmd_forceAnsiConsole"); }
|
public static string cmd_keys => GetText("cmd_keys");
|
||||||
public static string cmd_noAnsiColor { get => GetText("cmd_noAnsiColor"); }
|
public static string cmd_keyText => GetText("cmd_keyText");
|
||||||
public static string cmd_keys { get => GetText("cmd_keys"); }
|
public static string cmd_loadKeyFailed => GetText("cmd_loadKeyFailed");
|
||||||
public static string cmd_keyText { get => GetText("cmd_keyText"); }
|
public static string cmd_logLevel => GetText("cmd_logLevel");
|
||||||
public static string cmd_loadKeyFailed { get => GetText("cmd_loadKeyFailed"); }
|
public static string cmd_MP4RealTimeDecryption => GetText("cmd_MP4RealTimeDecryption");
|
||||||
public static string cmd_logLevel { get => GetText("cmd_logLevel"); }
|
public static string cmd_saveDir => GetText("cmd_saveDir");
|
||||||
public static string cmd_MP4RealTimeDecryption { get => GetText("cmd_MP4RealTimeDecryption"); }
|
public static string cmd_saveName => GetText("cmd_saveName");
|
||||||
public static string cmd_saveDir { get => GetText("cmd_saveDir"); }
|
public static string cmd_savePattern => GetText("cmd_savePattern");
|
||||||
public static string cmd_saveName { get => GetText("cmd_saveName"); }
|
public static string cmd_skipDownload => GetText("cmd_skipDownload");
|
||||||
public static string cmd_savePattern { get => GetText("cmd_savePattern"); }
|
public static string cmd_noDateInfo => GetText("cmd_noDateInfo");
|
||||||
public static string cmd_skipDownload { get => GetText("cmd_skipDownload"); }
|
public static string cmd_noLog => GetText("cmd_noLog");
|
||||||
public static string cmd_noDateInfo { get => GetText("cmd_noDateInfo"); }
|
public static string cmd_skipMerge => GetText("cmd_skipMerge");
|
||||||
public static string cmd_noLog { get => GetText("cmd_noLog"); }
|
public static string cmd_subFormat => GetText("cmd_subFormat");
|
||||||
public static string cmd_skipMerge { get => GetText("cmd_skipMerge"); }
|
public static string cmd_subOnly => GetText("cmd_subOnly");
|
||||||
public static string cmd_subFormat { get => GetText("cmd_subFormat"); }
|
public static string cmd_subtitleFix => GetText("cmd_subtitleFix");
|
||||||
public static string cmd_subOnly { get => GetText("cmd_subOnly"); }
|
public static string cmd_threadCount => GetText("cmd_threadCount");
|
||||||
public static string cmd_subtitleFix { get => GetText("cmd_subtitleFix"); }
|
public static string cmd_downloadRetryCount => GetText("cmd_downloadRetryCount");
|
||||||
public static string cmd_threadCount { get => GetText("cmd_threadCount"); }
|
public static string cmd_tmpDir => GetText("cmd_tmpDir");
|
||||||
public static string cmd_downloadRetryCount { get => GetText("cmd_downloadRetryCount"); }
|
public static string cmd_uiLanguage => GetText("cmd_uiLanguage");
|
||||||
public static string cmd_tmpDir { get => GetText("cmd_tmpDir"); }
|
public static string cmd_urlProcessorArgs => GetText("cmd_urlProcessorArgs");
|
||||||
public static string cmd_uiLanguage { get => GetText("cmd_uiLanguage"); }
|
public static string cmd_useShakaPackager => GetText("cmd_useShakaPackager");
|
||||||
public static string cmd_urlProcessorArgs { get => GetText("cmd_urlProcessorArgs"); }
|
public static string cmd_concurrentDownload => GetText("cmd_concurrentDownload");
|
||||||
public static string cmd_useShakaPackager { get => GetText("cmd_useShakaPackager"); }
|
public static string cmd_useSystemProxy => GetText("cmd_useSystemProxy");
|
||||||
public static string cmd_concurrentDownload { get => GetText("cmd_concurrentDownload"); }
|
public static string cmd_customProxy => GetText("cmd_customProxy");
|
||||||
public static string cmd_useSystemProxy { get => GetText("cmd_useSystemProxy"); }
|
public static string cmd_customRange => GetText("cmd_customRange");
|
||||||
public static string cmd_customProxy { get => GetText("cmd_customProxy"); }
|
public static string cmd_liveKeepSegments => GetText("cmd_liveKeepSegments");
|
||||||
public static string cmd_customRange { get => GetText("cmd_customRange"); }
|
public static string cmd_livePipeMux => GetText("cmd_livePipeMux");
|
||||||
public static string cmd_liveKeepSegments { get => GetText("cmd_liveKeepSegments"); }
|
public static string cmd_liveRecordLimit => GetText("cmd_liveRecordLimit");
|
||||||
public static string cmd_livePipeMux { get => GetText("cmd_livePipeMux"); }
|
public static string cmd_taskStartAt => GetText("cmd_taskStartAt");
|
||||||
public static string cmd_liveRecordLimit { get => GetText("cmd_liveRecordLimit"); }
|
public static string cmd_liveWaitTime => GetText("cmd_liveWaitTime");
|
||||||
public static string cmd_taskStartAt { get => GetText("cmd_taskStartAt"); }
|
public static string cmd_liveTakeCount => GetText("cmd_liveTakeCount");
|
||||||
public static string cmd_liveWaitTime { get => GetText("cmd_liveWaitTime"); }
|
public static string cmd_liveFixVttByAudio => GetText("cmd_liveFixVttByAudio");
|
||||||
public static string cmd_liveTakeCount { get => GetText("cmd_liveTakeCount"); }
|
public static string cmd_liveRealTimeMerge => GetText("cmd_liveRealTimeMerge");
|
||||||
public static string cmd_liveFixVttByAudio { get => GetText("cmd_liveFixVttByAudio"); }
|
public static string cmd_livePerformAsVod => GetText("cmd_livePerformAsVod");
|
||||||
public static string cmd_liveRealTimeMerge { get => GetText("cmd_liveRealTimeMerge"); }
|
public static string cmd_muxAfterDone => GetText("cmd_muxAfterDone");
|
||||||
public static string cmd_livePerformAsVod { get => GetText("cmd_livePerformAsVod"); }
|
public static string cmd_muxAfterDone_more => GetText("cmd_muxAfterDone_more");
|
||||||
public static string cmd_muxAfterDone { get => GetText("cmd_muxAfterDone"); }
|
public static string cmd_writeMetaJson => GetText("cmd_writeMetaJson");
|
||||||
public static string cmd_muxAfterDone_more { get => GetText("cmd_muxAfterDone_more"); }
|
public static string liveLimit => GetText("liveLimit");
|
||||||
public static string cmd_writeMetaJson { get => GetText("cmd_writeMetaJson"); }
|
public static string realTimeDecMessage => GetText("realTimeDecMessage");
|
||||||
public static string liveLimit { get => GetText("liveLimit"); }
|
public static string liveLimitReached => GetText("liveLimitReached");
|
||||||
public static string realTimeDecMessage { get => GetText("realTimeDecMessage"); }
|
public static string saveName => GetText("saveName");
|
||||||
public static string liveLimitReached { get => GetText("liveLimitReached"); }
|
public static string taskStartAt => GetText("taskStartAt");
|
||||||
public static string saveName { get => GetText("saveName"); }
|
public static string namedPipeCreated => GetText("namedPipeCreated");
|
||||||
public static string taskStartAt { get => GetText("taskStartAt"); }
|
public static string namedPipeMux => GetText("namedPipeMux");
|
||||||
public static string namedPipeCreated { get => GetText("namedPipeCreated"); }
|
public static string partMerge => GetText("partMerge");
|
||||||
public static string namedPipeMux { get => GetText("namedPipeMux"); }
|
public static string fetch => GetText("fetch");
|
||||||
public static string partMerge { get => GetText("partMerge"); }
|
public static string ffmpegMerge => GetText("ffmpegMerge");
|
||||||
public static string fetch { get => GetText("fetch"); }
|
public static string ffmpegNotFound => GetText("ffmpegNotFound");
|
||||||
public static string ffmpegMerge { get => GetText("ffmpegMerge"); }
|
public static string fixingTTML => GetText("fixingTTML");
|
||||||
public static string ffmpegNotFound { get => GetText("ffmpegNotFound"); }
|
public static string fixingTTMLmp4 => GetText("fixingTTMLmp4");
|
||||||
public static string fixingTTML { get => GetText("fixingTTML"); }
|
public static string fixingVTT => GetText("fixingVTT");
|
||||||
public static string fixingTTMLmp4 { get => GetText("fixingTTMLmp4"); }
|
public static string fixingVTTmp4 => GetText("fixingVTTmp4");
|
||||||
public static string fixingVTT { get => GetText("fixingVTT"); }
|
public static string keyProcessorNotFound => GetText("keyProcessorNotFound");
|
||||||
public static string fixingVTTmp4 { get => GetText("fixingVTTmp4"); }
|
public static string liveFound => GetText("liveFound");
|
||||||
public static string keyProcessorNotFound { get => GetText("keyProcessorNotFound"); }
|
public static string loadingUrl => GetText("loadingUrl");
|
||||||
public static string liveFound { get => GetText("liveFound"); }
|
public static string masterM3u8Found => GetText("masterM3u8Found");
|
||||||
public static string loadingUrl { get => GetText("loadingUrl"); }
|
public static string matchDASH => GetText("matchDASH");
|
||||||
public static string masterM3u8Found { get => GetText("masterM3u8Found"); }
|
public static string matchMSS => GetText("matchMSS");
|
||||||
public static string matchDASH { get => GetText("matchDASH"); }
|
public static string matchTS => GetText("matchTS");
|
||||||
public static string matchMSS { get => GetText("matchMSS"); }
|
public static string matchHLS => GetText("matchHLS");
|
||||||
public static string matchTS { get => GetText("matchTS"); }
|
public static string notSupported => GetText("notSupported");
|
||||||
public static string matchHLS { get => GetText("matchHLS"); }
|
public static string parsingStream => GetText("parsingStream");
|
||||||
public static string notSupported { get => GetText("notSupported"); }
|
public static string promptChoiceText => GetText("promptChoiceText");
|
||||||
public static string parsingStream { get => GetText("parsingStream"); }
|
public static string promptInfo => GetText("promptInfo");
|
||||||
public static string promptChoiceText { get => GetText("promptChoiceText"); }
|
public static string promptTitle => GetText("promptTitle");
|
||||||
public static string promptInfo { get => GetText("promptInfo"); }
|
public static string readingInfo => GetText("readingInfo");
|
||||||
public static string promptTitle { get => GetText("promptTitle"); }
|
public static string searchKey => GetText("searchKey");
|
||||||
public static string readingInfo { get => GetText("readingInfo"); }
|
public static string segmentCountCheckNotPass => GetText("segmentCountCheckNotPass");
|
||||||
public static string searchKey { get => GetText("searchKey"); }
|
public static string selectedStream => GetText("selectedStream");
|
||||||
public static string segmentCountCheckNotPass { get => GetText("segmentCountCheckNotPass"); }
|
public static string startDownloading => GetText("startDownloading");
|
||||||
public static string selectedStream { get => GetText("selectedStream"); }
|
public static string streamsInfo => GetText("streamsInfo");
|
||||||
public static string startDownloading { get => GetText("startDownloading"); }
|
public static string writeJson => GetText("writeJson");
|
||||||
public static string streamsInfo { get => GetText("streamsInfo"); }
|
public static string noStreamsToDownload => GetText("noStreamsToDownload");
|
||||||
public static string writeJson { get => GetText("writeJson"); }
|
public static string newVersionFound => GetText("newVersionFound");
|
||||||
public static string noStreamsToDownload { get => GetText("noStreamsToDownload"); }
|
public static string processImageSub => GetText("processImageSub");
|
||||||
public static string newVersionFound { get => GetText("newVersionFound"); }
|
|
||||||
public static string processImageSub { get => GetText("processImageSub"); }
|
|
||||||
|
|
||||||
private static string GetText(string key)
|
private static string GetText(string key)
|
||||||
{
|
{
|
||||||
|
@ -142,10 +135,8 @@ namespace N_m3u8DL_RE.Common.Resource
|
||||||
var current = Thread.CurrentThread.CurrentUICulture.Name;
|
var current = Thread.CurrentThread.CurrentUICulture.Name;
|
||||||
if (current == "zh-CN" || current == "zh-SG" || current == "zh-Hans")
|
if (current == "zh-CN" || current == "zh-SG" || current == "zh-Hans")
|
||||||
return StaticText.LANG_DIC[key].ZH_CN;
|
return StaticText.LANG_DIC[key].ZH_CN;
|
||||||
else if (current.StartsWith("zh-"))
|
if (current.StartsWith("zh-"))
|
||||||
return StaticText.LANG_DIC[key].ZH_TW;
|
return StaticText.LANG_DIC[key].ZH_TW;
|
||||||
else
|
|
||||||
return StaticText.LANG_DIC[key].EN_US;
|
return StaticText.LANG_DIC[key].EN_US;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -4,10 +4,10 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Resource
|
namespace N_m3u8DL_RE.Common.Resource;
|
||||||
|
|
||||||
|
internal class StaticText
|
||||||
{
|
{
|
||||||
internal class StaticText
|
|
||||||
{
|
|
||||||
public static Dictionary<string, TextContainer> LANG_DIC = new()
|
public static Dictionary<string, TextContainer> LANG_DIC = new()
|
||||||
{
|
{
|
||||||
["singleFileSplitWarn"] = new TextContainer
|
["singleFileSplitWarn"] = new TextContainer
|
||||||
|
@ -918,5 +918,4 @@ namespace N_m3u8DL_RE.Common.Resource
|
||||||
),
|
),
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,16 +1,10 @@
|
||||||
using System;
|
namespace N_m3u8DL_RE.Common.Resource;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Resource
|
internal class TextContainer
|
||||||
{
|
{
|
||||||
internal class TextContainer
|
public string ZH_CN { get; }
|
||||||
{
|
public string ZH_TW { get; }
|
||||||
public string ZH_CN { get; set; }
|
public string EN_US { get; }
|
||||||
public string ZH_TW { get; set; }
|
|
||||||
public string EN_US { get; set; }
|
|
||||||
|
|
||||||
public TextContainer(string zhCN, string zhTW, string enUS)
|
public TextContainer(string zhCN, string zhTW, string enUS)
|
||||||
{
|
{
|
||||||
|
@ -18,5 +12,4 @@ namespace N_m3u8DL_RE.Common.Resource
|
||||||
ZH_TW = zhTW;
|
ZH_TW = zhTW;
|
||||||
EN_US = enUS;
|
EN_US = enUS;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,18 +1,13 @@
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.JsonConverter;
|
using N_m3u8DL_RE.Common.JsonConverter;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Util;
|
namespace N_m3u8DL_RE.Common.Util;
|
||||||
|
|
||||||
public static class GlobalUtil
|
public static class GlobalUtil
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerOptions Options = new JsonSerializerOptions
|
private static readonly JsonSerializerOptions Options = new()
|
||||||
{
|
{
|
||||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
|
@ -27,15 +22,15 @@ public static class GlobalUtil
|
||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(s, Context.StreamSpec);
|
return JsonSerializer.Serialize(s, Context.StreamSpec);
|
||||||
}
|
}
|
||||||
else if (o is IOrderedEnumerable<StreamSpec> ss)
|
if (o is IOrderedEnumerable<StreamSpec> ss)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(ss, Context.IOrderedEnumerableStreamSpec);
|
return JsonSerializer.Serialize(ss, Context.IOrderedEnumerableStreamSpec);
|
||||||
}
|
}
|
||||||
else if (o is List<StreamSpec> sList)
|
if (o is List<StreamSpec> sList)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(sList, Context.ListStreamSpec);
|
return JsonSerializer.Serialize(sList, Context.ListStreamSpec);
|
||||||
}
|
}
|
||||||
else if (o is IEnumerable<MediaSegment> mList)
|
if (o is IEnumerable<MediaSegment> mList)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(mList, Context.IEnumerableMediaSegment);
|
return JsonSerializer.Serialize(mList, Context.IEnumerableMediaSegment);
|
||||||
}
|
}
|
||||||
|
@ -47,14 +42,14 @@ public static class GlobalUtil
|
||||||
return fileSize switch
|
return fileSize switch
|
||||||
{
|
{
|
||||||
< 0 => throw new ArgumentOutOfRangeException(nameof(fileSize)),
|
< 0 => throw new ArgumentOutOfRangeException(nameof(fileSize)),
|
||||||
>= 1024 * 1024 * 1024 => string.Format("{0:########0.00}GB", (double)fileSize / (1024 * 1024 * 1024)),
|
>= 1024 * 1024 * 1024 => $"{fileSize / (1024 * 1024 * 1024):########0.00}GB",
|
||||||
>= 1024 * 1024 => string.Format("{0:####0.00}MB", (double)fileSize / (1024 * 1024)),
|
>= 1024 * 1024 => $"{fileSize / (1024 * 1024):####0.00}MB",
|
||||||
>= 1024 => string.Format("{0:####0.00}KB", (double)fileSize / 1024),
|
>= 1024 => $"{fileSize / 1024:####0.00}KB",
|
||||||
_ => string.Format("{0:####0.00}B", fileSize)
|
_ => $"{fileSize:####0.00}B"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//此函数用于格式化输出时长
|
// 此函数用于格式化输出时长
|
||||||
public static string FormatTime(int time)
|
public static string FormatTime(int time)
|
||||||
{
|
{
|
||||||
TimeSpan ts = new TimeSpan(0, 0, time);
|
TimeSpan ts = new TimeSpan(0, 0, time);
|
||||||
|
@ -74,6 +69,6 @@ public static class GlobalUtil
|
||||||
var searchPath = new[] { Environment.CurrentDirectory, Path.GetDirectoryName(Environment.ProcessPath) };
|
var searchPath = new[] { Environment.CurrentDirectory, Path.GetDirectoryName(Environment.ProcessPath) };
|
||||||
var envPath = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ??
|
var envPath = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ??
|
||||||
Array.Empty<string>();
|
Array.Empty<string>();
|
||||||
return searchPath.Concat(envPath).Select(p => Path.Combine(p, name + fileExt)).FirstOrDefault(File.Exists);
|
return searchPath.Concat(envPath).Select(p => Path.Combine(p!, name + fileExt)).FirstOrDefault(File.Exists);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -37,13 +37,13 @@ public static class HTTPUtil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Logger.Debug(webRequest.Headers.ToString());
|
Logger.Debug(webRequest.Headers.ToString());
|
||||||
//手动处理跳转,以免自定义Headers丢失
|
// 手动处理跳转,以免自定义Headers丢失
|
||||||
var webResponse = await AppHttpClient.SendAsync(webRequest, HttpCompletionOption.ResponseHeadersRead);
|
var webResponse = await AppHttpClient.SendAsync(webRequest, HttpCompletionOption.ResponseHeadersRead);
|
||||||
if (((int)webResponse.StatusCode).ToString().StartsWith("30"))
|
if (((int)webResponse.StatusCode).ToString().StartsWith("30"))
|
||||||
{
|
{
|
||||||
HttpResponseHeaders respHeaders = webResponse.Headers;
|
HttpResponseHeaders respHeaders = webResponse.Headers;
|
||||||
Logger.Debug(respHeaders.ToString());
|
Logger.Debug(respHeaders.ToString());
|
||||||
if (respHeaders != null && respHeaders.Location != null)
|
if (respHeaders.Location != null)
|
||||||
{
|
{
|
||||||
var redirectedUrl = "";
|
var redirectedUrl = "";
|
||||||
if (!respHeaders.Location.IsAbsoluteUri)
|
if (!respHeaders.Location.IsAbsoluteUri)
|
||||||
|
@ -64,7 +64,7 @@ public static class HTTPUtil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//手动将跳转后的URL设置进去, 用于后续取用
|
// 手动将跳转后的URL设置进去, 用于后续取用
|
||||||
webResponse.Headers.Location = new Uri(url);
|
webResponse.Headers.Location = new Uri(url);
|
||||||
webResponse.EnsureSuccessStatusCode();
|
webResponse.EnsureSuccessStatusCode();
|
||||||
return webResponse;
|
return webResponse;
|
||||||
|
@ -76,9 +76,8 @@ public static class HTTPUtil
|
||||||
{
|
{
|
||||||
return await File.ReadAllBytesAsync(new Uri(url).LocalPath);
|
return await File.ReadAllBytesAsync(new Uri(url).LocalPath);
|
||||||
}
|
}
|
||||||
byte[] bytes = new byte[0];
|
|
||||||
var webResponse = await DoGetAsync(url, headers);
|
var webResponse = await DoGetAsync(url, headers);
|
||||||
bytes = await webResponse.Content.ReadAsByteArrayAsync();
|
var bytes = await webResponse.Content.ReadAsByteArrayAsync();
|
||||||
Logger.Debug(HexUtil.BytesToHex(bytes, " "));
|
Logger.Debug(HexUtil.BytesToHex(bytes, " "));
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
@ -91,9 +90,8 @@ public static class HTTPUtil
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static async Task<string> GetWebSourceAsync(string url, Dictionary<string, string>? headers = null)
|
public static async Task<string> GetWebSourceAsync(string url, Dictionary<string, string>? headers = null)
|
||||||
{
|
{
|
||||||
string htmlCode = string.Empty;
|
|
||||||
var webResponse = await DoGetAsync(url, headers);
|
var webResponse = await DoGetAsync(url, headers);
|
||||||
htmlCode = await webResponse.Content.ReadAsStringAsync();
|
string htmlCode = await webResponse.Content.ReadAsStringAsync();
|
||||||
Logger.Debug(htmlCode);
|
Logger.Debug(htmlCode);
|
||||||
return htmlCode;
|
return htmlCode;
|
||||||
}
|
}
|
||||||
|
@ -112,7 +110,7 @@ public static class HTTPUtil
|
||||||
/// <returns>(Source Code, RedirectedUrl)</returns>
|
/// <returns>(Source Code, RedirectedUrl)</returns>
|
||||||
public static async Task<(string, string)> GetWebSourceAndNewUrlAsync(string url, Dictionary<string, string>? headers = null)
|
public static async Task<(string, string)> GetWebSourceAndNewUrlAsync(string url, Dictionary<string, string>? headers = null)
|
||||||
{
|
{
|
||||||
string htmlCode = string.Empty;
|
string htmlCode;
|
||||||
var webResponse = await DoGetAsync(url, headers);
|
var webResponse = await DoGetAsync(url, headers);
|
||||||
if (CheckMPEG2TS(webResponse))
|
if (CheckMPEG2TS(webResponse))
|
||||||
{
|
{
|
||||||
|
@ -128,7 +126,7 @@ public static class HTTPUtil
|
||||||
|
|
||||||
public static async Task<string> GetPostResponseAsync(string Url, byte[] postData)
|
public static async Task<string> GetPostResponseAsync(string Url, byte[] postData)
|
||||||
{
|
{
|
||||||
string htmlCode = string.Empty;
|
string htmlCode;
|
||||||
using HttpRequestMessage request = new(HttpMethod.Post, Url);
|
using HttpRequestMessage request = new(HttpMethod.Post, Url);
|
||||||
request.Headers.TryAddWithoutValidation("Content-Type", "application/json");
|
request.Headers.TryAddWithoutValidation("Content-Type", "application/json");
|
||||||
request.Headers.TryAddWithoutValidation("Content-Length", postData.Length.ToString());
|
request.Headers.TryAddWithoutValidation("Content-Length", postData.Length.ToString());
|
||||||
|
|
|
@ -31,7 +31,7 @@ public static class HexUtil
|
||||||
var hexSpan = hex.AsSpan().Trim();
|
var hexSpan = hex.AsSpan().Trim();
|
||||||
if (hexSpan.StartsWith("0x") || hexSpan.StartsWith("0X"))
|
if (hexSpan.StartsWith("0x") || hexSpan.StartsWith("0X"))
|
||||||
{
|
{
|
||||||
hexSpan = hexSpan.Slice(2);
|
hexSpan = hexSpan[2..];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Convert.FromHexString(hexSpan);
|
return Convert.FromHexString(hexSpan);
|
||||||
|
|
|
@ -3,10 +3,10 @@ using N_m3u8DL_RE.Parser.Processor;
|
||||||
using N_m3u8DL_RE.Parser.Processor.DASH;
|
using N_m3u8DL_RE.Parser.Processor.DASH;
|
||||||
using N_m3u8DL_RE.Parser.Processor.HLS;
|
using N_m3u8DL_RE.Parser.Processor.HLS;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Config
|
namespace N_m3u8DL_RE.Parser.Config;
|
||||||
|
|
||||||
|
public class ParserConfig
|
||||||
{
|
{
|
||||||
public class ParserConfig
|
|
||||||
{
|
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
|
|
||||||
public string OriginalUrl { get; set; }
|
public string OriginalUrl { get; set; }
|
||||||
|
@ -64,5 +64,4 @@ namespace N_m3u8DL_RE.Parser.Config
|
||||||
/// KEY重试次数
|
/// KEY重试次数
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int KeyRetryCount { get; set; } = 3;
|
public int KeyRetryCount { get; set; } = 3;
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -4,13 +4,12 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Constants
|
namespace N_m3u8DL_RE.Parser.Constants;
|
||||||
|
|
||||||
|
internal class DASHTags
|
||||||
{
|
{
|
||||||
internal class DASHTags
|
|
||||||
{
|
|
||||||
public static string TemplateRepresentationID = "$RepresentationID$";
|
public static string TemplateRepresentationID = "$RepresentationID$";
|
||||||
public static string TemplateBandwidth = "$Bandwidth$";
|
public static string TemplateBandwidth = "$Bandwidth$";
|
||||||
public static string TemplateNumber = "$Number$";
|
public static string TemplateNumber = "$Number$";
|
||||||
public static string TemplateTime = "$Time$";
|
public static string TemplateTime = "$Time$";
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -4,10 +4,10 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Constants
|
namespace N_m3u8DL_RE.Parser.Constants;
|
||||||
|
|
||||||
|
internal class HLSTags
|
||||||
{
|
{
|
||||||
internal class HLSTags
|
|
||||||
{
|
|
||||||
public static string ext_m3u = "#EXTM3U";
|
public static string ext_m3u = "#EXTM3U";
|
||||||
public static string ext_x_targetduration = "#EXT-X-TARGETDURATION";
|
public static string ext_x_targetduration = "#EXT-X-TARGETDURATION";
|
||||||
public static string ext_x_media_sequence = "#EXT-X-MEDIA-SEQUENCE";
|
public static string ext_x_media_sequence = "#EXT-X-MEDIA-SEQUENCE";
|
||||||
|
@ -34,5 +34,4 @@ namespace N_m3u8DL_RE.Parser.Constants
|
||||||
public static string ext_x_cue_span = "#EXT-X-CUE-SPAN";
|
public static string ext_x_cue_span = "#EXT-X-CUE-SPAN";
|
||||||
public static string ext_x_map = "#EXT-X-MAP";
|
public static string ext_x_map = "#EXT-X-MAP";
|
||||||
public static string ext_x_start = "#EXT-X-START";
|
public static string ext_x_start = "#EXT-X-START";
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -4,13 +4,12 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Constants
|
namespace N_m3u8DL_RE.Parser.Constants;
|
||||||
|
|
||||||
|
internal class MSSTags
|
||||||
{
|
{
|
||||||
internal class MSSTags
|
|
||||||
{
|
|
||||||
public static string Bitrate = "{Bitrate}";
|
public static string Bitrate = "{Bitrate}";
|
||||||
public static string Bitrate_BK = "{bitrate}";
|
public static string Bitrate_BK = "{bitrate}";
|
||||||
public static string StartTime = "{start_time}";
|
public static string StartTime = "{start_time}";
|
||||||
public static string StartTime_BK = "{start time}";
|
public static string StartTime_BK = "{start time}";
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -13,11 +13,11 @@ using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Extractor
|
namespace N_m3u8DL_RE.Parser.Extractor;
|
||||||
|
|
||||||
|
// https://blog.csdn.net/leek5533/article/details/117750191
|
||||||
|
internal class DASHExtractor2 : IExtractor
|
||||||
{
|
{
|
||||||
//https://blog.csdn.net/leek5533/article/details/117750191
|
|
||||||
internal class DASHExtractor2 : IExtractor
|
|
||||||
{
|
|
||||||
private static EncryptMethod DEFAULT_METHOD = EncryptMethod.CENC;
|
private static EncryptMethod DEFAULT_METHOD = EncryptMethod.CENC;
|
||||||
|
|
||||||
public ExtractorType ExtractorType => ExtractorType.MPEG_DASH;
|
public ExtractorType ExtractorType => ExtractorType.MPEG_DASH;
|
||||||
|
@ -66,7 +66,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
public Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
||||||
{
|
{
|
||||||
var streamList = new List<StreamSpec>();
|
var streamList = new List<StreamSpec>();
|
||||||
|
|
||||||
|
@ -76,30 +76,30 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
|
|
||||||
var xmlDocument = XDocument.Parse(MpdContent);
|
var xmlDocument = XDocument.Parse(MpdContent);
|
||||||
|
|
||||||
//选中第一个MPD节点
|
// 选中第一个MPD节点
|
||||||
var mpdElement = xmlDocument.Elements().First(e => e.Name.LocalName == "MPD");
|
var mpdElement = xmlDocument.Elements().First(e => e.Name.LocalName == "MPD");
|
||||||
|
|
||||||
//类型 static点播, dynamic直播
|
// 类型 static点播, dynamic直播
|
||||||
var type = mpdElement.Attribute("type")?.Value;
|
var type = mpdElement.Attribute("type")?.Value;
|
||||||
bool isLive = type == "dynamic";
|
bool isLive = type == "dynamic";
|
||||||
|
|
||||||
//分片最大时长
|
// 分片最大时长
|
||||||
var maxSegmentDuration = mpdElement.Attribute("maxSegmentDuration")?.Value;
|
var maxSegmentDuration = mpdElement.Attribute("maxSegmentDuration")?.Value;
|
||||||
//分片从该时间起可用
|
// 分片从该时间起可用
|
||||||
var availabilityStartTime = mpdElement.Attribute("availabilityStartTime")?.Value;
|
var availabilityStartTime = mpdElement.Attribute("availabilityStartTime")?.Value;
|
||||||
//在availabilityStartTime的前XX段时间,分片有效
|
// 在availabilityStartTime的前XX段时间,分片有效
|
||||||
var timeShiftBufferDepth = mpdElement.Attribute("timeShiftBufferDepth")?.Value;
|
var timeShiftBufferDepth = mpdElement.Attribute("timeShiftBufferDepth")?.Value;
|
||||||
if (string.IsNullOrEmpty(timeShiftBufferDepth))
|
if (string.IsNullOrEmpty(timeShiftBufferDepth))
|
||||||
{
|
{
|
||||||
//如果没有 默认一分钟有效
|
// 如果没有 默认一分钟有效
|
||||||
timeShiftBufferDepth = "PT1M";
|
timeShiftBufferDepth = "PT1M";
|
||||||
}
|
}
|
||||||
//MPD发布时间
|
// MPD发布时间
|
||||||
var publishTime = mpdElement.Attribute("publishTime")?.Value;
|
var publishTime = mpdElement.Attribute("publishTime")?.Value;
|
||||||
//MPD总时长
|
// MPD总时长
|
||||||
var mediaPresentationDuration = mpdElement.Attribute("mediaPresentationDuration")?.Value;
|
var mediaPresentationDuration = mpdElement.Attribute("mediaPresentationDuration")?.Value;
|
||||||
|
|
||||||
//读取在MPD开头定义的<BaseURL>,并替换本身的URL
|
// 读取在MPD开头定义的<BaseURL>,并替换本身的URL
|
||||||
var baseUrlElement = mpdElement.Elements().Where(e => e.Name.LocalName == "BaseURL").FirstOrDefault();
|
var baseUrlElement = mpdElement.Elements().Where(e => e.Name.LocalName == "BaseURL").FirstOrDefault();
|
||||||
if (baseUrlElement != null)
|
if (baseUrlElement != null)
|
||||||
{
|
{
|
||||||
|
@ -108,40 +108,40 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
this.BaseUrl = ParserUtil.CombineURL(this.MpdUrl, baseUrl);
|
this.BaseUrl = ParserUtil.CombineURL(this.MpdUrl, baseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
//全部Period
|
// 全部Period
|
||||||
var periods = mpdElement.Elements().Where(e => e.Name.LocalName == "Period");
|
var periods = mpdElement.Elements().Where(e => e.Name.LocalName == "Period");
|
||||||
foreach (var period in periods)
|
foreach (var period in periods)
|
||||||
{
|
{
|
||||||
//本Period时长
|
// 本Period时长
|
||||||
var periodDuration = period.Attribute("duration")?.Value;
|
var periodDuration = period.Attribute("duration")?.Value;
|
||||||
|
|
||||||
//本Period ID
|
// 本Period ID
|
||||||
var periodId = period.Attribute("id")?.Value;
|
var periodId = period.Attribute("id")?.Value;
|
||||||
|
|
||||||
//最终分片会使用的baseurl
|
// 最终分片会使用的baseurl
|
||||||
var segBaseUrl = this.BaseUrl;
|
var segBaseUrl = this.BaseUrl;
|
||||||
|
|
||||||
//处理baseurl嵌套
|
// 处理baseurl嵌套
|
||||||
segBaseUrl = ExtendBaseUrl(period, segBaseUrl);
|
segBaseUrl = ExtendBaseUrl(period, segBaseUrl);
|
||||||
|
|
||||||
var adaptationSetsBaseUrl = segBaseUrl;
|
var adaptationSetsBaseUrl = segBaseUrl;
|
||||||
|
|
||||||
//本Period中的全部AdaptationSet
|
// 本Period中的全部AdaptationSet
|
||||||
var adaptationSets = period.Elements().Where(e => e.Name.LocalName == "AdaptationSet");
|
var adaptationSets = period.Elements().Where(e => e.Name.LocalName == "AdaptationSet");
|
||||||
foreach (var adaptationSet in adaptationSets)
|
foreach (var adaptationSet in adaptationSets)
|
||||||
{
|
{
|
||||||
//处理baseurl嵌套
|
// 处理baseurl嵌套
|
||||||
segBaseUrl = ExtendBaseUrl(adaptationSet, segBaseUrl);
|
segBaseUrl = ExtendBaseUrl(adaptationSet, segBaseUrl);
|
||||||
|
|
||||||
var representationsBaseUrl = segBaseUrl;
|
var representationsBaseUrl = segBaseUrl;
|
||||||
|
|
||||||
var mimeType = adaptationSet.Attribute("contentType")?.Value ?? adaptationSet.Attribute("mimeType")?.Value;
|
var mimeType = adaptationSet.Attribute("contentType")?.Value ?? adaptationSet.Attribute("mimeType")?.Value;
|
||||||
var frameRate = GetFrameRate(adaptationSet);
|
var frameRate = GetFrameRate(adaptationSet);
|
||||||
//本AdaptationSet中的全部Representation
|
// 本AdaptationSet中的全部Representation
|
||||||
var representations = adaptationSet.Elements().Where(e => e.Name.LocalName == "Representation");
|
var representations = adaptationSet.Elements().Where(e => e.Name.LocalName == "Representation");
|
||||||
foreach (var representation in representations)
|
foreach (var representation in representations)
|
||||||
{
|
{
|
||||||
//处理baseurl嵌套
|
// 处理baseurl嵌套
|
||||||
segBaseUrl = ExtendBaseUrl(representation, segBaseUrl);
|
segBaseUrl = ExtendBaseUrl(representation, segBaseUrl);
|
||||||
|
|
||||||
if (mimeType == null)
|
if (mimeType == null)
|
||||||
|
@ -167,24 +167,24 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
"audio" => MediaType.AUDIO,
|
"audio" => MediaType.AUDIO,
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
//特殊处理
|
// 特殊处理
|
||||||
if (representation.Attribute("volumeAdjust") != null)
|
if (representation.Attribute("volumeAdjust") != null)
|
||||||
{
|
{
|
||||||
streamSpec.GroupId += "-" + representation.Attribute("volumeAdjust")?.Value;
|
streamSpec.GroupId += "-" + representation.Attribute("volumeAdjust")?.Value;
|
||||||
}
|
}
|
||||||
//推测后缀名
|
// 推测后缀名
|
||||||
var mType = representation.Attribute("mimeType")?.Value ?? adaptationSet.Attribute("mimeType")?.Value;
|
var mType = representation.Attribute("mimeType")?.Value ?? adaptationSet.Attribute("mimeType")?.Value;
|
||||||
if (mType != null)
|
if (mType != null)
|
||||||
{
|
{
|
||||||
var mTypeSplit = mType.Split('/');
|
var mTypeSplit = mType.Split('/');
|
||||||
streamSpec.Extension = mTypeSplit.Length == 2 ? mTypeSplit[1] : null;
|
streamSpec.Extension = mTypeSplit.Length == 2 ? mTypeSplit[1] : null;
|
||||||
}
|
}
|
||||||
//优化字幕场景识别
|
// 优化字幕场景识别
|
||||||
if (streamSpec.Codecs == "stpp" || streamSpec.Codecs == "wvtt")
|
if (streamSpec.Codecs == "stpp" || streamSpec.Codecs == "wvtt")
|
||||||
{
|
{
|
||||||
streamSpec.MediaType = MediaType.SUBTITLES;
|
streamSpec.MediaType = MediaType.SUBTITLES;
|
||||||
}
|
}
|
||||||
//优化字幕场景识别
|
// 优化字幕场景识别
|
||||||
var role = representation.Elements().Where(e => e.Name.LocalName == "Role").FirstOrDefault() ?? adaptationSet.Elements().Where(e => e.Name.LocalName == "Role").FirstOrDefault();
|
var role = representation.Elements().Where(e => e.Name.LocalName == "Role").FirstOrDefault() ?? adaptationSet.Elements().Where(e => e.Name.LocalName == "Role").FirstOrDefault();
|
||||||
if (role != null)
|
if (role != null)
|
||||||
{
|
{
|
||||||
|
@ -202,31 +202,31 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
streamSpec.Playlist.IsLive = isLive;
|
streamSpec.Playlist.IsLive = isLive;
|
||||||
//设置刷新间隔 timeShiftBufferDepth / 2
|
// 设置刷新间隔 timeShiftBufferDepth / 2
|
||||||
if (timeShiftBufferDepth != null)
|
if (timeShiftBufferDepth != null)
|
||||||
{
|
{
|
||||||
streamSpec.Playlist.RefreshIntervalMs = XmlConvert.ToTimeSpan(timeShiftBufferDepth).TotalMilliseconds / 2;
|
streamSpec.Playlist.RefreshIntervalMs = XmlConvert.ToTimeSpan(timeShiftBufferDepth).TotalMilliseconds / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
//读取声道数量
|
// 读取声道数量
|
||||||
var audioChannelConfiguration = adaptationSet.Elements().Concat(representation.Elements()).Where(e => e.Name.LocalName == "AudioChannelConfiguration").FirstOrDefault();
|
var audioChannelConfiguration = adaptationSet.Elements().Concat(representation.Elements()).Where(e => e.Name.LocalName == "AudioChannelConfiguration").FirstOrDefault();
|
||||||
if (audioChannelConfiguration != null)
|
if (audioChannelConfiguration != null)
|
||||||
{
|
{
|
||||||
streamSpec.Channels = audioChannelConfiguration.Attribute("value")?.Value;
|
streamSpec.Channels = audioChannelConfiguration.Attribute("value")?.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
//发布时间
|
// 发布时间
|
||||||
if (!string.IsNullOrEmpty(publishTime))
|
if (!string.IsNullOrEmpty(publishTime))
|
||||||
{
|
{
|
||||||
streamSpec.PublishTime = DateTime.Parse(publishTime);
|
streamSpec.PublishTime = DateTime.Parse(publishTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//第一种形式 SegmentBase
|
// 第一种形式 SegmentBase
|
||||||
var segmentBaseElement = representation.Elements().Where(e => e.Name.LocalName == "SegmentBase").FirstOrDefault();
|
var segmentBaseElement = representation.Elements().Where(e => e.Name.LocalName == "SegmentBase").FirstOrDefault();
|
||||||
if (segmentBaseElement != null)
|
if (segmentBaseElement != null)
|
||||||
{
|
{
|
||||||
//处理init url
|
// 处理init url
|
||||||
var initialization = segmentBaseElement.Elements().Where(e => e.Name.LocalName == "Initialization").FirstOrDefault();
|
var initialization = segmentBaseElement.Elements().Where(e => e.Name.LocalName == "Initialization").FirstOrDefault();
|
||||||
if (initialization != null)
|
if (initialization != null)
|
||||||
{
|
{
|
||||||
|
@ -248,7 +248,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
var initUrl = ParserUtil.CombineURL(segBaseUrl, initialization.Attribute("sourceURL")?.Value!);
|
var initUrl = ParserUtil.CombineURL(segBaseUrl, initialization.Attribute("sourceURL")?.Value!);
|
||||||
var initRange = initialization.Attribute("range")?.Value;
|
var initRange = initialization.Attribute("range")?.Value;
|
||||||
streamSpec.Playlist.MediaInit = new MediaSegment();
|
streamSpec.Playlist.MediaInit = new MediaSegment();
|
||||||
streamSpec.Playlist.MediaInit.Index = -1; //便于排序
|
streamSpec.Playlist.MediaInit.Index = -1; // 便于排序
|
||||||
streamSpec.Playlist.MediaInit.Url = initUrl;
|
streamSpec.Playlist.MediaInit.Url = initUrl;
|
||||||
if (initRange != null)
|
if (initRange != null)
|
||||||
{
|
{
|
||||||
|
@ -260,19 +260,19 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//第二种形式 SegmentList.SegmentList
|
// 第二种形式 SegmentList.SegmentList
|
||||||
var segmentList = representation.Elements().Where(e => e.Name.LocalName == "SegmentList").FirstOrDefault();
|
var segmentList = representation.Elements().Where(e => e.Name.LocalName == "SegmentList").FirstOrDefault();
|
||||||
if (segmentList != null)
|
if (segmentList != null)
|
||||||
{
|
{
|
||||||
var durationStr = segmentList.Attribute("duration")?.Value;
|
var durationStr = segmentList.Attribute("duration")?.Value;
|
||||||
//处理init url
|
// 处理init url
|
||||||
var initialization = segmentList.Elements().Where(e => e.Name.LocalName == "Initialization").FirstOrDefault();
|
var initialization = segmentList.Elements().Where(e => e.Name.LocalName == "Initialization").FirstOrDefault();
|
||||||
if (initialization != null)
|
if (initialization != null)
|
||||||
{
|
{
|
||||||
var initUrl = ParserUtil.CombineURL(segBaseUrl, initialization.Attribute("sourceURL")?.Value!);
|
var initUrl = ParserUtil.CombineURL(segBaseUrl, initialization.Attribute("sourceURL")?.Value!);
|
||||||
var initRange = initialization.Attribute("range")?.Value;
|
var initRange = initialization.Attribute("range")?.Value;
|
||||||
streamSpec.Playlist.MediaInit = new MediaSegment();
|
streamSpec.Playlist.MediaInit = new MediaSegment();
|
||||||
streamSpec.Playlist.MediaInit.Index = -1; //便于排序
|
streamSpec.Playlist.MediaInit.Index = -1; // 便于排序
|
||||||
streamSpec.Playlist.MediaInit.Url = initUrl;
|
streamSpec.Playlist.MediaInit.Url = initUrl;
|
||||||
if (initRange != null)
|
if (initRange != null)
|
||||||
{
|
{
|
||||||
|
@ -281,7 +281,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
streamSpec.Playlist.MediaInit.ExpectLength = expect;
|
streamSpec.Playlist.MediaInit.ExpectLength = expect;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//处理分片
|
// 处理分片
|
||||||
var segmentURLs = segmentList.Elements().Where(e => e.Name.LocalName == "SegmentURL");
|
var segmentURLs = segmentList.Elements().Where(e => e.Name.LocalName == "SegmentURL");
|
||||||
var timescaleStr = segmentList.Attribute("timescale")?.Value ?? "1";
|
var timescaleStr = segmentList.Attribute("timescale")?.Value ?? "1";
|
||||||
for (int segmentIndex = 0; segmentIndex < segmentURLs.Count(); segmentIndex++)
|
for (int segmentIndex = 0; segmentIndex < segmentURLs.Count(); segmentIndex++)
|
||||||
|
@ -305,50 +305,50 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//第三种形式 SegmentTemplate+SegmentTimeline
|
// 第三种形式 SegmentTemplate+SegmentTimeline
|
||||||
//通配符有$RepresentationID$ $Bandwidth$ $Number$ $Time$
|
// 通配符有$RepresentationID$ $Bandwidth$ $Number$ $Time$
|
||||||
|
|
||||||
//adaptationSets中的segmentTemplate
|
// adaptationSets中的segmentTemplate
|
||||||
var segmentTemplateElementsOuter = adaptationSet.Elements().Where(e => e.Name.LocalName == "SegmentTemplate");
|
var segmentTemplateElementsOuter = adaptationSet.Elements().Where(e => e.Name.LocalName == "SegmentTemplate");
|
||||||
//representation中的segmentTemplate
|
// representation中的segmentTemplate
|
||||||
var segmentTemplateElements = representation.Elements().Where(e => e.Name.LocalName == "SegmentTemplate");
|
var segmentTemplateElements = representation.Elements().Where(e => e.Name.LocalName == "SegmentTemplate");
|
||||||
if (segmentTemplateElements.Any() || segmentTemplateElementsOuter.Any())
|
if (segmentTemplateElements.Any() || segmentTemplateElementsOuter.Any())
|
||||||
{
|
{
|
||||||
//优先使用最近的元素
|
// 优先使用最近的元素
|
||||||
var segmentTemplate = (segmentTemplateElements.FirstOrDefault() ?? segmentTemplateElementsOuter.FirstOrDefault())!;
|
var segmentTemplate = (segmentTemplateElements.FirstOrDefault() ?? segmentTemplateElementsOuter.FirstOrDefault())!;
|
||||||
var segmentTemplateOuter = (segmentTemplateElementsOuter.FirstOrDefault() ?? segmentTemplateElements.FirstOrDefault())!;
|
var segmentTemplateOuter = (segmentTemplateElementsOuter.FirstOrDefault() ?? segmentTemplateElements.FirstOrDefault())!;
|
||||||
var varDic = new Dictionary<string, object?>();
|
var varDic = new Dictionary<string, object?>();
|
||||||
varDic[DASHTags.TemplateRepresentationID] = streamSpec.GroupId;
|
varDic[DASHTags.TemplateRepresentationID] = streamSpec.GroupId;
|
||||||
varDic[DASHTags.TemplateBandwidth] = bandwidth?.Value;
|
varDic[DASHTags.TemplateBandwidth] = bandwidth?.Value;
|
||||||
//presentationTimeOffset
|
// presentationTimeOffset
|
||||||
var presentationTimeOffsetStr = segmentTemplate.Attribute("presentationTimeOffset")?.Value ?? segmentTemplateOuter.Attribute("presentationTimeOffset")?.Value ?? "0";
|
var presentationTimeOffsetStr = segmentTemplate.Attribute("presentationTimeOffset")?.Value ?? segmentTemplateOuter.Attribute("presentationTimeOffset")?.Value ?? "0";
|
||||||
//timesacle
|
// timesacle
|
||||||
var timescaleStr = segmentTemplate.Attribute("timescale")?.Value ?? segmentTemplateOuter.Attribute("timescale")?.Value ?? "1";
|
var timescaleStr = segmentTemplate.Attribute("timescale")?.Value ?? segmentTemplateOuter.Attribute("timescale")?.Value ?? "1";
|
||||||
var durationStr = segmentTemplate.Attribute("duration")?.Value ?? segmentTemplateOuter.Attribute("duration")?.Value;
|
var durationStr = segmentTemplate.Attribute("duration")?.Value ?? segmentTemplateOuter.Attribute("duration")?.Value;
|
||||||
var startNumberStr = segmentTemplate.Attribute("startNumber")?.Value ?? segmentTemplateOuter.Attribute("startNumber")?.Value ?? "1";
|
var startNumberStr = segmentTemplate.Attribute("startNumber")?.Value ?? segmentTemplateOuter.Attribute("startNumber")?.Value ?? "1";
|
||||||
//处理init url
|
// 处理init url
|
||||||
var initialization = segmentTemplate.Attribute("initialization")?.Value ?? segmentTemplateOuter.Attribute("initialization")?.Value;
|
var initialization = segmentTemplate.Attribute("initialization")?.Value ?? segmentTemplateOuter.Attribute("initialization")?.Value;
|
||||||
if (initialization != null)
|
if (initialization != null)
|
||||||
{
|
{
|
||||||
var _init = ParserUtil.ReplaceVars(initialization, varDic);
|
var _init = ParserUtil.ReplaceVars(initialization, varDic);
|
||||||
var initUrl = ParserUtil.CombineURL(segBaseUrl, _init);
|
var initUrl = ParserUtil.CombineURL(segBaseUrl, _init);
|
||||||
streamSpec.Playlist.MediaInit = new MediaSegment();
|
streamSpec.Playlist.MediaInit = new MediaSegment();
|
||||||
streamSpec.Playlist.MediaInit.Index = -1; //便于排序
|
streamSpec.Playlist.MediaInit.Index = -1; // 便于排序
|
||||||
streamSpec.Playlist.MediaInit.Url = initUrl;
|
streamSpec.Playlist.MediaInit.Url = initUrl;
|
||||||
}
|
}
|
||||||
//处理分片
|
// 处理分片
|
||||||
var mediaTemplate = segmentTemplate.Attribute("media")?.Value ?? segmentTemplateOuter.Attribute("media")?.Value;
|
var mediaTemplate = segmentTemplate.Attribute("media")?.Value ?? segmentTemplateOuter.Attribute("media")?.Value;
|
||||||
var segmentTimeline = segmentTemplate.Elements().Where(e => e.Name.LocalName == "SegmentTimeline").FirstOrDefault();
|
var segmentTimeline = segmentTemplate.Elements().Where(e => e.Name.LocalName == "SegmentTimeline").FirstOrDefault();
|
||||||
if (segmentTimeline != null)
|
if (segmentTimeline != null)
|
||||||
{
|
{
|
||||||
//使用了SegmentTimeline 结果精确
|
// 使用了SegmentTimeline 结果精确
|
||||||
var segNumber = Convert.ToInt64(startNumberStr);
|
var segNumber = Convert.ToInt64(startNumberStr);
|
||||||
var Ss = segmentTimeline.Elements().Where(e => e.Name.LocalName == "S");
|
var Ss = segmentTimeline.Elements().Where(e => e.Name.LocalName == "S");
|
||||||
var currentTime = 0L;
|
var currentTime = 0L;
|
||||||
var segIndex = 0;
|
var segIndex = 0;
|
||||||
foreach (var S in Ss)
|
foreach (var S in Ss)
|
||||||
{
|
{
|
||||||
//每个S元素包含三个属性:@t(start time)\@r(repeat count)\@d(duration)
|
// 每个S元素包含三个属性:@t(start time)\@r(repeat count)\@d(duration)
|
||||||
var _startTimeStr = S.Attribute("t")?.Value;
|
var _startTimeStr = S.Attribute("t")?.Value;
|
||||||
var _durationStr = S.Attribute("d")?.Value;
|
var _durationStr = S.Attribute("d")?.Value;
|
||||||
var _repeatCountStr = S.Attribute("r")?.Value;
|
var _repeatCountStr = S.Attribute("r")?.Value;
|
||||||
|
@ -371,7 +371,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
streamSpec.Playlist.MediaParts[0].MediaSegments.Add(mediaSegment);
|
streamSpec.Playlist.MediaParts[0].MediaSegments.Add(mediaSegment);
|
||||||
if (_repeatCount < 0)
|
if (_repeatCount < 0)
|
||||||
{
|
{
|
||||||
//负数表示一直重复 直到period结束 注意减掉已经加入的1个片段
|
// 负数表示一直重复 直到period结束 注意减掉已经加入的1个片段
|
||||||
_repeatCount = (long)Math.Ceiling(XmlConvert.ToTimeSpan(periodDuration ?? mediaPresentationDuration ?? "PT0S").TotalSeconds * timescale / _duration) - 1;
|
_repeatCount = (long)Math.Ceiling(XmlConvert.ToTimeSpan(periodDuration ?? mediaPresentationDuration ?? "PT0S").TotalSeconds * timescale / _duration) - 1;
|
||||||
}
|
}
|
||||||
for (long i = 0; i < _repeatCount; i++)
|
for (long i = 0; i < _repeatCount; i++)
|
||||||
|
@ -395,22 +395,22 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//没用SegmentTimeline 需要计算总分片数量 不精确
|
// 没用SegmentTimeline 需要计算总分片数量 不精确
|
||||||
var timescale = Convert.ToInt32(timescaleStr);
|
var timescale = Convert.ToInt32(timescaleStr);
|
||||||
var startNumber = Convert.ToInt64(startNumberStr);
|
var startNumber = Convert.ToInt64(startNumberStr);
|
||||||
var duration = Convert.ToInt32(durationStr);
|
var duration = Convert.ToInt32(durationStr);
|
||||||
var totalNumber = (long)Math.Ceiling(XmlConvert.ToTimeSpan(periodDuration ?? mediaPresentationDuration ?? "PT0S").TotalSeconds * timescale / duration);
|
var totalNumber = (long)Math.Ceiling(XmlConvert.ToTimeSpan(periodDuration ?? mediaPresentationDuration ?? "PT0S").TotalSeconds * timescale / duration);
|
||||||
//直播的情况,需要自己计算totalNumber
|
// 直播的情况,需要自己计算totalNumber
|
||||||
if (totalNumber == 0 && isLive)
|
if (totalNumber == 0 && isLive)
|
||||||
{
|
{
|
||||||
var now = DateTime.Now;
|
var now = DateTime.Now;
|
||||||
var availableTime = DateTime.Parse(availabilityStartTime!);
|
var availableTime = DateTime.Parse(availabilityStartTime!);
|
||||||
//可用时间+偏移量
|
// 可用时间+偏移量
|
||||||
var offsetMs = TimeSpan.FromMilliseconds(Convert.ToInt64(presentationTimeOffsetStr) / 1000);
|
var offsetMs = TimeSpan.FromMilliseconds(Convert.ToInt64(presentationTimeOffsetStr) / 1000);
|
||||||
availableTime = availableTime.Add(offsetMs);
|
availableTime = availableTime.Add(offsetMs);
|
||||||
var ts = now - availableTime;
|
var ts = now - availableTime;
|
||||||
var updateTs = XmlConvert.ToTimeSpan(timeShiftBufferDepth!);
|
var updateTs = XmlConvert.ToTimeSpan(timeShiftBufferDepth!);
|
||||||
//(当前时间到发布时间的时间差 - 最小刷新间隔) / 分片时长
|
// (当前时间到发布时间的时间差 - 最小刷新间隔) / 分片时长
|
||||||
startNumber += (long)((ts.TotalSeconds - updateTs.TotalSeconds) * timescale / duration);
|
startNumber += (long)((ts.TotalSeconds - updateTs.TotalSeconds) * timescale / duration);
|
||||||
totalNumber = (long)(updateTs.TotalSeconds * timescale / duration);
|
totalNumber = (long)(updateTs.TotalSeconds * timescale / duration);
|
||||||
}
|
}
|
||||||
|
@ -424,14 +424,14 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
mediaSegment.Url = mediaUrl;
|
mediaSegment.Url = mediaUrl;
|
||||||
if (hasNumber)
|
if (hasNumber)
|
||||||
mediaSegment.NameFromVar = index.ToString();
|
mediaSegment.NameFromVar = index.ToString();
|
||||||
mediaSegment.Index = isLive ? index : segIndex; //直播直接用startNumber
|
mediaSegment.Index = isLive ? index : segIndex; // 直播直接用startNumber
|
||||||
mediaSegment.Duration = duration / (double)timescale;
|
mediaSegment.Duration = duration / (double)timescale;
|
||||||
streamSpec.Playlist.MediaParts[0].MediaSegments.Add(mediaSegment);
|
streamSpec.Playlist.MediaParts[0].MediaSegments.Add(mediaSegment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//如果依旧没被添加分片,直接把BaseUrl塞进去就好
|
// 如果依旧没被添加分片,直接把BaseUrl塞进去就好
|
||||||
if (streamSpec.Playlist.MediaParts[0].MediaSegments.Count == 0)
|
if (streamSpec.Playlist.MediaParts[0].MediaSegments.Count == 0)
|
||||||
{
|
{
|
||||||
streamSpec.Playlist.MediaParts[0].MediaSegments.Add
|
streamSpec.Playlist.MediaParts[0].MediaSegments.Add
|
||||||
|
@ -445,7 +445,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//判断加密情况
|
// 判断加密情况
|
||||||
if (adaptationSet.Elements().Concat(representation.Elements()).Any(e => e.Name.LocalName == "ContentProtection"))
|
if (adaptationSet.Elements().Concat(representation.Elements()).Any(e => e.Name.LocalName == "ContentProtection"))
|
||||||
{
|
{
|
||||||
if (streamSpec.Playlist.MediaInit != null)
|
if (streamSpec.Playlist.MediaInit != null)
|
||||||
|
@ -458,17 +458,17 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//处理同一ID分散在不同Period的情况
|
// 处理同一ID分散在不同Period的情况
|
||||||
var _index = streamList.FindIndex(_f => _f.PeriodId != streamSpec.PeriodId && _f.GroupId == streamSpec.GroupId && _f.Resolution == streamSpec.Resolution && _f.MediaType == streamSpec.MediaType);
|
var _index = streamList.FindIndex(_f => _f.PeriodId != streamSpec.PeriodId && _f.GroupId == streamSpec.GroupId && _f.Resolution == streamSpec.Resolution && _f.MediaType == streamSpec.MediaType);
|
||||||
if (_index > -1)
|
if (_index > -1)
|
||||||
{
|
{
|
||||||
if (isLive)
|
if (isLive)
|
||||||
{
|
{
|
||||||
//直播,这种情况直接略过新的
|
// 直播,这种情况直接略过新的
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//点播,这种情况如果URL不同则作为新的part出现,否则仅把时间加起来
|
// 点播,这种情况如果URL不同则作为新的part出现,否则仅把时间加起来
|
||||||
var url1 = streamList[_index].Playlist!.MediaParts.Last().MediaSegments.Last().Url;
|
var url1 = streamList[_index].Playlist!.MediaParts.Last().MediaSegments.Last().Url;
|
||||||
var url2 = streamSpec.Playlist.MediaParts[0].MediaSegments.LastOrDefault()?.Url;
|
var url2 = streamSpec.Playlist.MediaParts[0].MediaSegments.LastOrDefault()?.Url;
|
||||||
if (url1 != url2)
|
if (url1 != url2)
|
||||||
|
@ -492,27 +492,27 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//修复mp4类型字幕
|
// 修复mp4类型字幕
|
||||||
if (streamSpec.MediaType == MediaType.SUBTITLES && streamSpec.Extension == "mp4")
|
if (streamSpec.MediaType == MediaType.SUBTITLES && streamSpec.Extension == "mp4")
|
||||||
{
|
{
|
||||||
streamSpec.Extension = "m4s";
|
streamSpec.Extension = "m4s";
|
||||||
}
|
}
|
||||||
//分片默认后缀m4s
|
// 分片默认后缀m4s
|
||||||
if (streamSpec.MediaType != MediaType.SUBTITLES && (streamSpec.Extension == null || streamSpec.Playlist.MediaParts.Sum(x => x.MediaSegments.Count) > 1))
|
if (streamSpec.MediaType != MediaType.SUBTITLES && (streamSpec.Extension == null || streamSpec.Playlist.MediaParts.Sum(x => x.MediaSegments.Count) > 1))
|
||||||
{
|
{
|
||||||
streamSpec.Extension = "m4s";
|
streamSpec.Extension = "m4s";
|
||||||
}
|
}
|
||||||
streamList.Add(streamSpec);
|
streamList.Add(streamSpec);
|
||||||
}
|
}
|
||||||
//恢复BaseURL相对位置
|
// 恢复BaseURL相对位置
|
||||||
segBaseUrl = representationsBaseUrl;
|
segBaseUrl = representationsBaseUrl;
|
||||||
}
|
}
|
||||||
//恢复BaseURL相对位置
|
// 恢复BaseURL相对位置
|
||||||
segBaseUrl = adaptationSetsBaseUrl;
|
segBaseUrl = adaptationSetsBaseUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//为视频设置默认轨道
|
// 为视频设置默认轨道
|
||||||
var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO);
|
var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO);
|
||||||
var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES);
|
var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES);
|
||||||
foreach (var item in streamList)
|
foreach (var item in streamList)
|
||||||
|
@ -530,7 +530,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return streamList;
|
return Task.FromResult(streamList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -556,7 +556,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
catch (HttpRequestException) when (ParserConfig.Url!= ParserConfig.OriginalUrl)
|
catch (HttpRequestException) when (ParserConfig.Url!= ParserConfig.OriginalUrl)
|
||||||
{
|
{
|
||||||
//当URL无法访问时,再请求原始URL
|
// 当URL无法访问时,再请求原始URL
|
||||||
(rawText, url) = await HTTPUtil.GetWebSourceAndNewUrlAsync(ParserConfig.OriginalUrl, ParserConfig.Headers);
|
(rawText, url) = await HTTPUtil.GetWebSourceAndNewUrlAsync(ParserConfig.OriginalUrl, ParserConfig.Headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,20 +566,20 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
var newStreams = await ExtractStreamsAsync(rawText);
|
var newStreams = await ExtractStreamsAsync(rawText);
|
||||||
foreach (var streamSpec in streamSpecs)
|
foreach (var streamSpec in streamSpecs)
|
||||||
{
|
{
|
||||||
//有的网站每次请求MPD返回的码率不一致,导致ToShortString()无法匹配 无法更新playlist
|
// 有的网站每次请求MPD返回的码率不一致,导致ToShortString()无法匹配 无法更新playlist
|
||||||
//故增加通过init url来匹配 (如果有的话)
|
// 故增加通过init url来匹配 (如果有的话)
|
||||||
var match = newStreams.Where(n => n.ToShortString() == streamSpec.ToShortString());
|
var match = newStreams.Where(n => n.ToShortString() == streamSpec.ToShortString());
|
||||||
if (!match.Any())
|
if (!match.Any())
|
||||||
match = newStreams.Where(n => n.Playlist?.MediaInit?.Url == streamSpec.Playlist?.MediaInit?.Url);
|
match = newStreams.Where(n => n.Playlist?.MediaInit?.Url == streamSpec.Playlist?.MediaInit?.Url);
|
||||||
|
|
||||||
if (match.Any())
|
if (match.Any())
|
||||||
streamSpec.Playlist!.MediaParts = match.First().Playlist!.MediaParts; //不更新init
|
streamSpec.Playlist!.MediaParts = match.First().Playlist!.MediaParts; // 不更新init
|
||||||
}
|
}
|
||||||
//这里才调用URL预处理器,节省开销
|
// 这里才调用URL预处理器,节省开销
|
||||||
await ProcessUrlAsync(streamSpecs);
|
await ProcessUrlAsync(streamSpecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessUrlAsync(List<StreamSpec> streamSpecs)
|
private Task ProcessUrlAsync(List<StreamSpec> streamSpecs)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < streamSpecs.Count; i++)
|
for (int i = 0; i < streamSpecs.Count; i++)
|
||||||
{
|
{
|
||||||
|
@ -600,11 +600,13 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
public async Task FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
||||||
{
|
{
|
||||||
//这里才调用URL预处理器,节省开销
|
// 这里才调用URL预处理器,节省开销
|
||||||
await ProcessUrlAsync(streamSpecs);
|
await ProcessUrlAsync(streamSpecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -631,5 +633,4 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -13,10 +13,10 @@ using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Extractor
|
namespace N_m3u8DL_RE.Parser.Extractor;
|
||||||
|
|
||||||
|
internal class HLSExtractor : IExtractor
|
||||||
{
|
{
|
||||||
internal class HLSExtractor : IExtractor
|
|
||||||
{
|
|
||||||
public ExtractorType ExtractorType => ExtractorType.HLS;
|
public ExtractorType ExtractorType => ExtractorType.HLS;
|
||||||
|
|
||||||
private string M3u8Url = string.Empty;
|
private string M3u8Url = string.Empty;
|
||||||
|
@ -83,7 +83,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<StreamSpec>> ParseMasterListAsync()
|
private Task<List<StreamSpec>> ParseMasterListAsync()
|
||||||
{
|
{
|
||||||
MasterM3u8Flag = true;
|
MasterM3u8Flag = true;
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
if (!string.IsNullOrEmpty(videoRange))
|
if (!string.IsNullOrEmpty(videoRange))
|
||||||
streamSpec.VideoRange = videoRange;
|
streamSpec.VideoRange = videoRange;
|
||||||
|
|
||||||
//清除多余的编码信息 dvh1.05.06,ec-3 => dvh1.05.06
|
// 清除多余的编码信息 dvh1.05.06,ec-3 => dvh1.05.06
|
||||||
if (!string.IsNullOrEmpty(streamSpec.Codecs) && !string.IsNullOrEmpty(streamSpec.AudioId))
|
if (!string.IsNullOrEmpty(streamSpec.Codecs) && !string.IsNullOrEmpty(streamSpec.AudioId))
|
||||||
{
|
{
|
||||||
streamSpec.Codecs = streamSpec.Codecs.Split(',')[0];
|
streamSpec.Codecs = streamSpec.Codecs.Split(',')[0];
|
||||||
|
@ -145,7 +145,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
streamSpec.MediaType = mediaType;
|
streamSpec.MediaType = mediaType;
|
||||||
}
|
}
|
||||||
|
|
||||||
//跳过CLOSED_CAPTIONS类型(目前不支持)
|
// 跳过CLOSED_CAPTIONS类型(目前不支持)
|
||||||
if (streamSpec.MediaType == MediaType.CLOSED_CAPTIONS)
|
if (streamSpec.MediaType == MediaType.CLOSED_CAPTIONS)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
@ -214,12 +214,12 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return streams;
|
return Task.FromResult(streams);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Playlist> ParseListAsync()
|
private Task<Playlist> ParseListAsync()
|
||||||
{
|
{
|
||||||
//标记是否已清除优酷广告分片
|
// 标记是否已清除广告分片
|
||||||
bool hasAd = false;
|
bool hasAd = false;
|
||||||
|
|
||||||
using StringReader sr = new StringReader(M3u8Content);
|
using StringReader sr = new StringReader(M3u8Content);
|
||||||
|
@ -233,7 +233,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
Playlist playlist = new();
|
Playlist playlist = new();
|
||||||
List<MediaPart> mediaParts = new();
|
List<MediaPart> mediaParts = new();
|
||||||
|
|
||||||
//当前的加密信息
|
// 当前的加密信息
|
||||||
EncryptInfo currentEncryptInfo = new();
|
EncryptInfo currentEncryptInfo = new();
|
||||||
if (ParserConfig.CustomMethod != null)
|
if (ParserConfig.CustomMethod != null)
|
||||||
currentEncryptInfo.Method = ParserConfig.CustomMethod.Value;
|
currentEncryptInfo.Method = ParserConfig.CustomMethod.Value;
|
||||||
|
@ -241,7 +241,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
currentEncryptInfo.Key = ParserConfig.CustomeKey;
|
currentEncryptInfo.Key = ParserConfig.CustomeKey;
|
||||||
if (ParserConfig.CustomeIV != null && ParserConfig.CustomeIV.Length > 0)
|
if (ParserConfig.CustomeIV != null && ParserConfig.CustomeIV.Length > 0)
|
||||||
currentEncryptInfo.IV = ParserConfig.CustomeIV;
|
currentEncryptInfo.IV = ParserConfig.CustomeIV;
|
||||||
//上次读取到的加密行,#EXT-X-KEY:……
|
// 上次读取到的加密行,#EXT-X-KEY:……
|
||||||
string lastKeyLine = "";
|
string lastKeyLine = "";
|
||||||
|
|
||||||
MediaPart mediaPart = new();
|
MediaPart mediaPart = new();
|
||||||
|
@ -254,7 +254,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
if (string.IsNullOrEmpty(line))
|
if (string.IsNullOrEmpty(line))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
//只下载部分字节
|
// 只下载部分字节
|
||||||
if (line.StartsWith(HLSTags.ext_x_byterange))
|
if (line.StartsWith(HLSTags.ext_x_byterange))
|
||||||
{
|
{
|
||||||
var p = ParserUtil.GetAttribute(line);
|
var p = ParserUtil.GetAttribute(line);
|
||||||
|
@ -263,7 +263,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
segment.StartRange = o ?? segments.Last().StartRange + segments.Last().ExpectLength;
|
segment.StartRange = o ?? segments.Last().StartRange + segments.Last().ExpectLength;
|
||||||
expectSegment = true;
|
expectSegment = true;
|
||||||
}
|
}
|
||||||
//国家地理去广告
|
// 国家地理去广告
|
||||||
else if (line.StartsWith("#UPLYNK-SEGMENT"))
|
else if (line.StartsWith("#UPLYNK-SEGMENT"))
|
||||||
{
|
{
|
||||||
if (line.Contains(",ad"))
|
if (line.Contains(",ad"))
|
||||||
|
@ -271,31 +271,31 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
else if (line.Contains(",segment"))
|
else if (line.Contains(",segment"))
|
||||||
isAd = false;
|
isAd = false;
|
||||||
}
|
}
|
||||||
//国家地理去广告
|
// 国家地理去广告
|
||||||
else if (isAd)
|
else if (isAd)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
//解析定义的分段长度
|
// 解析定义的分段长度
|
||||||
else if (line.StartsWith(HLSTags.ext_x_targetduration))
|
else if (line.StartsWith(HLSTags.ext_x_targetduration))
|
||||||
{
|
{
|
||||||
playlist.TargetDuration = Convert.ToDouble(ParserUtil.GetAttribute(line));
|
playlist.TargetDuration = Convert.ToDouble(ParserUtil.GetAttribute(line));
|
||||||
}
|
}
|
||||||
//解析起始编号
|
// 解析起始编号
|
||||||
else if (line.StartsWith(HLSTags.ext_x_media_sequence))
|
else if (line.StartsWith(HLSTags.ext_x_media_sequence))
|
||||||
{
|
{
|
||||||
segIndex = Convert.ToInt64(ParserUtil.GetAttribute(line));
|
segIndex = Convert.ToInt64(ParserUtil.GetAttribute(line));
|
||||||
startIndex = segIndex;
|
startIndex = segIndex;
|
||||||
}
|
}
|
||||||
//program date time
|
// program date time
|
||||||
else if (line.StartsWith(HLSTags.ext_x_program_date_time))
|
else if (line.StartsWith(HLSTags.ext_x_program_date_time))
|
||||||
{
|
{
|
||||||
segment.DateTime = DateTime.Parse(ParserUtil.GetAttribute(line));
|
segment.DateTime = DateTime.Parse(ParserUtil.GetAttribute(line));
|
||||||
}
|
}
|
||||||
//解析不连续标记,需要单独合并(timestamp不同)
|
// 解析不连续标记,需要单独合并(timestamp不同)
|
||||||
else if (line.StartsWith(HLSTags.ext_x_discontinuity))
|
else if (line.StartsWith(HLSTags.ext_x_discontinuity))
|
||||||
{
|
{
|
||||||
//修复优酷去除广告后的遗留问题
|
// 修复YK去除广告后的遗留问题
|
||||||
if (hasAd && mediaParts.Count > 0)
|
if (hasAd && mediaParts.Count > 0)
|
||||||
{
|
{
|
||||||
segments = mediaParts[mediaParts.Count - 1].MediaSegments;
|
segments = mediaParts[mediaParts.Count - 1].MediaSegments;
|
||||||
|
@ -303,7 +303,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
hasAd = false;
|
hasAd = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
//常规情况的#EXT-X-DISCONTINUITY标记,新建part
|
// 常规情况的#EXT-X-DISCONTINUITY标记,新建part
|
||||||
if (!hasAd && segments.Count >= 1)
|
if (!hasAd && segments.Count >= 1)
|
||||||
{
|
{
|
||||||
mediaParts.Add(new MediaPart()
|
mediaParts.Add(new MediaPart()
|
||||||
|
@ -313,16 +313,16 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
segments = new();
|
segments = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//解析KEY
|
// 解析KEY
|
||||||
else if (line.StartsWith(HLSTags.ext_x_key))
|
else if (line.StartsWith(HLSTags.ext_x_key))
|
||||||
{
|
{
|
||||||
var uri = ParserUtil.GetAttribute(line, "URI");
|
var uri = ParserUtil.GetAttribute(line, "URI");
|
||||||
var uri_last = ParserUtil.GetAttribute(lastKeyLine, "URI");
|
var uri_last = ParserUtil.GetAttribute(lastKeyLine, "URI");
|
||||||
|
|
||||||
//如果KEY URL相同,不进行重复解析
|
// 如果KEY URL相同,不进行重复解析
|
||||||
if (uri != uri_last)
|
if (uri != uri_last)
|
||||||
{
|
{
|
||||||
//调用处理器进行解析
|
// 调用处理器进行解析
|
||||||
var parsedInfo = ParseKey(line);
|
var parsedInfo = ParseKey(line);
|
||||||
currentEncryptInfo.Method = parsedInfo.Method;
|
currentEncryptInfo.Method = parsedInfo.Method;
|
||||||
currentEncryptInfo.Key = parsedInfo.Key;
|
currentEncryptInfo.Key = parsedInfo.Key;
|
||||||
|
@ -330,13 +330,13 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
lastKeyLine = line;
|
lastKeyLine = line;
|
||||||
}
|
}
|
||||||
//解析分片时长
|
// 解析分片时长
|
||||||
else if (line.StartsWith(HLSTags.extinf))
|
else if (line.StartsWith(HLSTags.extinf))
|
||||||
{
|
{
|
||||||
string[] tmp = ParserUtil.GetAttribute(line).Split(',');
|
string[] tmp = ParserUtil.GetAttribute(line).Split(',');
|
||||||
segment.Duration = Convert.ToDouble(tmp[0]);
|
segment.Duration = Convert.ToDouble(tmp[0]);
|
||||||
segment.Index = segIndex;
|
segment.Index = segIndex;
|
||||||
//是否有加密,有的话写入KEY和IV
|
// 是否有加密,有的话写入KEY和IV
|
||||||
if (currentEncryptInfo.Method != EncryptMethod.NONE)
|
if (currentEncryptInfo.Method != EncryptMethod.NONE)
|
||||||
{
|
{
|
||||||
segment.EncryptInfo.Method = currentEncryptInfo.Method;
|
segment.EncryptInfo.Method = currentEncryptInfo.Method;
|
||||||
|
@ -346,7 +346,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
expectSegment = true;
|
expectSegment = true;
|
||||||
segIndex++;
|
segIndex++;
|
||||||
}
|
}
|
||||||
//m3u8主体结束
|
// m3u8主体结束
|
||||||
else if (line.StartsWith(HLSTags.ext_x_endlist))
|
else if (line.StartsWith(HLSTags.ext_x_endlist))
|
||||||
{
|
{
|
||||||
if (segments.Count > 0)
|
if (segments.Count > 0)
|
||||||
|
@ -359,7 +359,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
segments = new();
|
segments = new();
|
||||||
isEndlist = true;
|
isEndlist = true;
|
||||||
}
|
}
|
||||||
//#EXT-X-MAP
|
// #EXT-X-MAP
|
||||||
else if (line.StartsWith(HLSTags.ext_x_map))
|
else if (line.StartsWith(HLSTags.ext_x_map))
|
||||||
{
|
{
|
||||||
if (playlist.MediaInit == null)
|
if (playlist.MediaInit == null)
|
||||||
|
@ -367,7 +367,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
playlist.MediaInit = new MediaSegment()
|
playlist.MediaInit = new MediaSegment()
|
||||||
{
|
{
|
||||||
Url = PreProcessUrl(ParserUtil.CombineURL(BaseUrl, ParserUtil.GetAttribute(line, "URI"))),
|
Url = PreProcessUrl(ParserUtil.CombineURL(BaseUrl, ParserUtil.GetAttribute(line, "URI"))),
|
||||||
Index = -1, //便于排序
|
Index = -1, // 便于排序
|
||||||
};
|
};
|
||||||
if (line.Contains("BYTERANGE"))
|
if (line.Contains("BYTERANGE"))
|
||||||
{
|
{
|
||||||
|
@ -376,7 +376,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
playlist.MediaInit.ExpectLength = n;
|
playlist.MediaInit.ExpectLength = n;
|
||||||
playlist.MediaInit.StartRange = o ?? 0L;
|
playlist.MediaInit.StartRange = o ?? 0L;
|
||||||
}
|
}
|
||||||
//是否有加密,有的话写入KEY和IV
|
// 是否有加密,有的话写入KEY和IV
|
||||||
if (currentEncryptInfo.Method != EncryptMethod.NONE)
|
if (currentEncryptInfo.Method != EncryptMethod.NONE)
|
||||||
{
|
{
|
||||||
playlist.MediaInit.EncryptInfo.Method = currentEncryptInfo.Method;
|
playlist.MediaInit.EncryptInfo.Method = currentEncryptInfo.Method;
|
||||||
|
@ -384,7 +384,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
playlist.MediaInit.EncryptInfo.IV = currentEncryptInfo.IV ?? HexUtil.HexToBytes(Convert.ToString(segIndex, 16).PadLeft(32, '0'));
|
playlist.MediaInit.EncryptInfo.IV = currentEncryptInfo.IV ?? HexUtil.HexToBytes(Convert.ToString(segIndex, 16).PadLeft(32, '0'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//遇到了其他的map,说明已经不是一个视频了,全部丢弃即可
|
// 遇到了其他的map,说明已经不是一个视频了,全部丢弃即可
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (segments.Count > 0)
|
if (segments.Count > 0)
|
||||||
|
@ -399,27 +399,27 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//评论行不解析
|
// 评论行不解析
|
||||||
else if (line.StartsWith("#")) continue;
|
else if (line.StartsWith("#")) continue;
|
||||||
//空白行不解析
|
// 空白行不解析
|
||||||
else if (line.StartsWith("\r\n")) continue;
|
else if (line.StartsWith("\r\n")) continue;
|
||||||
//解析分片的地址
|
// 解析分片的地址
|
||||||
else if (expectSegment)
|
else if (expectSegment)
|
||||||
{
|
{
|
||||||
var segUrl = PreProcessUrl(ParserUtil.CombineURL(BaseUrl, line));
|
var segUrl = PreProcessUrl(ParserUtil.CombineURL(BaseUrl, line));
|
||||||
segment.Url = segUrl;
|
segment.Url = segUrl;
|
||||||
segments.Add(segment);
|
segments.Add(segment);
|
||||||
segment = new();
|
segment = new();
|
||||||
//优酷的广告分段则清除此分片
|
// YK的广告分段则清除此分片
|
||||||
//需要注意,遇到广告说明程序对上文的#EXT-X-DISCONTINUITY做出的动作是不必要的,
|
// 需要注意,遇到广告说明程序对上文的#EXT-X-DISCONTINUITY做出的动作是不必要的,
|
||||||
//其实上下文是同一种编码,需要恢复到原先的part上
|
// 其实上下文是同一种编码,需要恢复到原先的part上
|
||||||
if (segUrl.Contains("ccode=") && segUrl.Contains("/ad/") && segUrl.Contains("duration="))
|
if (segUrl.Contains("ccode=") && segUrl.Contains("/ad/") && segUrl.Contains("duration="))
|
||||||
{
|
{
|
||||||
segments.RemoveAt(segments.Count - 1);
|
segments.RemoveAt(segments.Count - 1);
|
||||||
segIndex--;
|
segIndex--;
|
||||||
hasAd = true;
|
hasAd = true;
|
||||||
}
|
}
|
||||||
//优酷广告(4K分辨率测试)
|
// YK广告(4K分辨率测试)
|
||||||
if (segUrl.Contains("ccode=0902") && segUrl.Contains("duration="))
|
if (segUrl.Contains("ccode=0902") && segUrl.Contains("duration="))
|
||||||
{
|
{
|
||||||
segments.RemoveAt(segments.Count - 1);
|
segments.RemoveAt(segments.Count - 1);
|
||||||
|
@ -430,7 +430,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//直播的情况,无法遇到m3u8结束标记,需要手动将segments加入parts
|
// 直播的情况,无法遇到m3u8结束标记,需要手动将segments加入parts
|
||||||
if (!isEndlist)
|
if (!isEndlist)
|
||||||
{
|
{
|
||||||
mediaParts.Add(new MediaPart()
|
mediaParts.Add(new MediaPart()
|
||||||
|
@ -442,14 +442,14 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
playlist.MediaParts = mediaParts;
|
playlist.MediaParts = mediaParts;
|
||||||
playlist.IsLive = !isEndlist;
|
playlist.IsLive = !isEndlist;
|
||||||
|
|
||||||
//直播刷新间隔
|
// 直播刷新间隔
|
||||||
if (playlist.IsLive)
|
if (playlist.IsLive)
|
||||||
{
|
{
|
||||||
//由于播放器默认从最后3个分片开始播放 此处设置刷新间隔为TargetDuration的2倍
|
// 由于播放器默认从最后3个分片开始播放 此处设置刷新间隔为TargetDuration的2倍
|
||||||
playlist.RefreshIntervalMs = (int)((playlist.TargetDuration ?? 5) * 2 * 1000);
|
playlist.RefreshIntervalMs = (int)((playlist.TargetDuration ?? 5) * 2 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
return playlist;
|
return Task.FromResult(playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EncryptInfo ParseKey(string keyLine)
|
private EncryptInfo ParseKey(string keyLine)
|
||||||
|
@ -458,7 +458,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
{
|
{
|
||||||
if (p.CanProcess(ExtractorType, keyLine, M3u8Url, M3u8Content, ParserConfig))
|
if (p.CanProcess(ExtractorType, keyLine, M3u8Url, M3u8Content, ParserConfig))
|
||||||
{
|
{
|
||||||
//匹配到对应处理器后不再继续
|
// 匹配到对应处理器后不再继续
|
||||||
return p.Process(keyLine, M3u8Url, M3u8Content, ParserConfig);
|
return p.Process(keyLine, M3u8Url, M3u8Content, ParserConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -477,12 +477,11 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
lists = lists.DistinctBy(p => p.Url).ToList();
|
lists = lists.DistinctBy(p => p.Url).ToList();
|
||||||
return lists;
|
return lists;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var playlist = await ParseListAsync();
|
var playlist = await ParseListAsync();
|
||||||
return new List<StreamSpec>()
|
return new List<StreamSpec>
|
||||||
{
|
{
|
||||||
new StreamSpec()
|
new()
|
||||||
{
|
{
|
||||||
Url = ParserConfig.Url,
|
Url = ParserConfig.Url,
|
||||||
Playlist = playlist,
|
Playlist = playlist,
|
||||||
|
@ -490,11 +489,10 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadM3u8FromUrlAsync(string url)
|
private async Task LoadM3u8FromUrlAsync(string url)
|
||||||
{
|
{
|
||||||
//Logger.Info(ResString.loadingUrl + url);
|
// Logger.Info(ResString.loadingUrl + url);
|
||||||
if (url.StartsWith("file:"))
|
if (url.StartsWith("file:"))
|
||||||
{
|
{
|
||||||
var uri = new Uri(url);
|
var uri = new Uri(url);
|
||||||
|
@ -508,7 +506,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
catch (HttpRequestException) when (url != ParserConfig.OriginalUrl)
|
catch (HttpRequestException) when (url != ParserConfig.OriginalUrl)
|
||||||
{
|
{
|
||||||
//当URL无法访问时,再请求原始URL
|
// 当URL无法访问时,再请求原始URL
|
||||||
(this.M3u8Content, url) = await HTTPUtil.GetWebSourceAndNewUrlAsync(ParserConfig.OriginalUrl, ParserConfig.Headers);
|
(this.M3u8Content, url) = await HTTPUtil.GetWebSourceAndNewUrlAsync(ParserConfig.OriginalUrl, ParserConfig.Headers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -525,13 +523,13 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async Task RefreshUrlFromMaster(List<StreamSpec> lists)
|
private async Task RefreshUrlFromMaster(List<StreamSpec> lists)
|
||||||
{
|
{
|
||||||
//重新加载master m3u8, 刷新选中流的URL
|
// 重新加载master m3u8, 刷新选中流的URL
|
||||||
await LoadM3u8FromUrlAsync(ParserConfig.Url);
|
await LoadM3u8FromUrlAsync(ParserConfig.Url);
|
||||||
var newStreams = await ParseMasterListAsync();
|
var newStreams = await ParseMasterListAsync();
|
||||||
newStreams = newStreams.DistinctBy(p => p.Url).ToList();
|
newStreams = newStreams.DistinctBy(p => p.Url).ToList();
|
||||||
foreach (var l in lists)
|
foreach (var l in lists)
|
||||||
{
|
{
|
||||||
var match = newStreams.Where(n => n.ToShortString() == l.ToShortString());
|
var match = newStreams.Where(n => n.ToShortString() == l.ToShortString()).ToList();
|
||||||
if (match.Any())
|
if (match.Any())
|
||||||
{
|
{
|
||||||
Logger.DebugMarkUp($"{l.Url} => {match.First().Url}");
|
Logger.DebugMarkUp($"{l.Url} => {match.First().Url}");
|
||||||
|
@ -546,20 +544,20 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//直接重新加载m3u8
|
// 直接重新加载m3u8
|
||||||
await LoadM3u8FromUrlAsync(lists[i].Url!);
|
await LoadM3u8FromUrlAsync(lists[i].Url!);
|
||||||
}
|
}
|
||||||
catch (HttpRequestException) when (MasterM3u8Flag == true)
|
catch (HttpRequestException) when (MasterM3u8Flag == true)
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp("Can not load m3u8. Try refreshing url from master url...");
|
Logger.WarnMarkUp("Can not load m3u8. Try refreshing url from master url...");
|
||||||
//当前URL无法加载 尝试从Master链接中刷新URL
|
// 当前URL无法加载 尝试从Master链接中刷新URL
|
||||||
await RefreshUrlFromMaster(lists);
|
await RefreshUrlFromMaster(lists);
|
||||||
await LoadM3u8FromUrlAsync(lists[i].Url!);
|
await LoadM3u8FromUrlAsync(lists[i].Url!);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newPlaylist = await ParseListAsync();
|
var newPlaylist = await ParseListAsync();
|
||||||
if (lists[i].Playlist?.MediaInit != null)
|
if (lists[i].Playlist?.MediaInit != null)
|
||||||
lists[i].Playlist!.MediaParts = newPlaylist.MediaParts; //不更新init
|
lists[i].Playlist!.MediaParts = newPlaylist.MediaParts; // 不更新init
|
||||||
else
|
else
|
||||||
lists[i].Playlist = newPlaylist;
|
lists[i].Playlist = newPlaylist;
|
||||||
|
|
||||||
|
@ -581,5 +579,4 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
{
|
{
|
||||||
await FetchPlayListAsync(streamSpecs);
|
await FetchPlayListAsync(streamSpecs);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -7,10 +7,10 @@ using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Extractor
|
namespace N_m3u8DL_RE.Parser.Extractor;
|
||||||
|
|
||||||
|
public interface IExtractor
|
||||||
{
|
{
|
||||||
public interface IExtractor
|
|
||||||
{
|
|
||||||
ExtractorType ExtractorType { get; }
|
ExtractorType ExtractorType { get; }
|
||||||
|
|
||||||
ParserConfig ParserConfig { get; set; }
|
ParserConfig ParserConfig { get; set; }
|
||||||
|
@ -23,5 +23,4 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
string PreProcessUrl(string url);
|
string PreProcessUrl(string url);
|
||||||
|
|
||||||
void PreProcessContent();
|
void PreProcessContent();
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -3,10 +3,10 @@ using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Extractor
|
namespace N_m3u8DL_RE.Parser.Extractor;
|
||||||
|
|
||||||
|
internal class LiveTSExtractor : IExtractor
|
||||||
{
|
{
|
||||||
internal class LiveTSExtractor : IExtractor
|
|
||||||
{
|
|
||||||
public ExtractorType ExtractorType => ExtractorType.HTTP_LIVE;
|
public ExtractorType ExtractorType => ExtractorType.HTTP_LIVE;
|
||||||
|
|
||||||
public ParserConfig ParserConfig {get; set;}
|
public ParserConfig ParserConfig {get; set;}
|
||||||
|
@ -16,26 +16,26 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
this.ParserConfig = parserConfig;
|
this.ParserConfig = parserConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
public Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
||||||
{
|
{
|
||||||
return new List<StreamSpec>()
|
return Task.FromResult(new List<StreamSpec>
|
||||||
{
|
{
|
||||||
new StreamSpec()
|
new()
|
||||||
{
|
{
|
||||||
OriginalUrl = ParserConfig.OriginalUrl,
|
OriginalUrl = ParserConfig.OriginalUrl,
|
||||||
Url = ParserConfig.Url,
|
Url = ParserConfig.Url,
|
||||||
Playlist = new Playlist(),
|
Playlist = new Playlist(),
|
||||||
GroupId = ResString.ReLiveTs
|
GroupId = ResString.ReLiveTs
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
public Task FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void PreProcessContent()
|
public void PreProcessContent()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
@ -49,5 +49,4 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -16,13 +16,13 @@ using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Extractor
|
namespace N_m3u8DL_RE.Parser.Extractor;
|
||||||
|
|
||||||
|
// Microsoft Smooth Streaming
|
||||||
|
// https://test.playready.microsoft.com/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/manifest
|
||||||
|
// file:///C:/Users/nilaoda/Downloads/[MS-SSTR]-180316.pdf
|
||||||
|
internal partial class MSSExtractor : IExtractor
|
||||||
{
|
{
|
||||||
//Microsoft Smooth Streaming
|
|
||||||
//https://test.playready.microsoft.com/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/manifest
|
|
||||||
//file:///C:/Users/nilaoda/Downloads/[MS-SSTR]-180316.pdf
|
|
||||||
internal partial class MSSExtractor : IExtractor
|
|
||||||
{
|
|
||||||
[GeneratedRegex("00000001\\d7([0-9a-fA-F]{6})")]
|
[GeneratedRegex("00000001\\d7([0-9a-fA-F]{6})")]
|
||||||
private static partial Regex VCodecsRegex();
|
private static partial Regex VCodecsRegex();
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
this.BaseUrl = this.IsmUrl;
|
this.BaseUrl = this.IsmUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
public Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
||||||
{
|
{
|
||||||
var streamList = new List<StreamSpec>();
|
var streamList = new List<StreamSpec>();
|
||||||
this.IsmContent = rawText;
|
this.IsmContent = rawText;
|
||||||
|
@ -60,7 +60,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
|
|
||||||
var xmlDocument = XDocument.Parse(IsmContent);
|
var xmlDocument = XDocument.Parse(IsmContent);
|
||||||
|
|
||||||
//选中第一个SmoothStreamingMedia节点
|
// 选中第一个SmoothStreamingMedia节点
|
||||||
var ssmElement = xmlDocument.Elements().First(e => e.Name.LocalName == "SmoothStreamingMedia");
|
var ssmElement = xmlDocument.Elements().First(e => e.Name.LocalName == "SmoothStreamingMedia");
|
||||||
var timeScaleStr = ssmElement.Attribute("TimeScale")?.Value ?? "10000000";
|
var timeScaleStr = ssmElement.Attribute("TimeScale")?.Value ?? "10000000";
|
||||||
var durationStr = ssmElement.Attribute("Duration")?.Value;
|
var durationStr = ssmElement.Attribute("Duration")?.Value;
|
||||||
|
@ -72,7 +72,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
var protectionSystemId = "";
|
var protectionSystemId = "";
|
||||||
var protectionData = "";
|
var protectionData = "";
|
||||||
|
|
||||||
//加密检测
|
// 加密检测
|
||||||
var protectElement = ssmElement.Elements().FirstOrDefault(e => e.Name.LocalName == "Protection");
|
var protectElement = ssmElement.Elements().FirstOrDefault(e => e.Name.LocalName == "Protection");
|
||||||
if (protectElement != null)
|
if (protectElement != null)
|
||||||
{
|
{
|
||||||
|
@ -85,26 +85,26 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//所有StreamIndex节点
|
// 所有StreamIndex节点
|
||||||
var streamIndexElements = ssmElement.Elements().Where(e => e.Name.LocalName == "StreamIndex");
|
var streamIndexElements = ssmElement.Elements().Where(e => e.Name.LocalName == "StreamIndex");
|
||||||
|
|
||||||
foreach (var streamIndex in streamIndexElements)
|
foreach (var streamIndex in streamIndexElements)
|
||||||
{
|
{
|
||||||
var type = streamIndex.Attribute("Type")?.Value; //"video" / "audio" / "text"
|
var type = streamIndex.Attribute("Type")?.Value; // "video" / "audio" / "text"
|
||||||
var name = streamIndex.Attribute("Name")?.Value;
|
var name = streamIndex.Attribute("Name")?.Value;
|
||||||
var subType = streamIndex.Attribute("Subtype")?.Value; //text track
|
var subType = streamIndex.Attribute("Subtype")?.Value; // text track
|
||||||
//如果有则不从QualityLevel读取
|
// 如果有则不从QualityLevel读取
|
||||||
//Bitrate = "{bitrate}" / "{Bitrate}"
|
// Bitrate = "{bitrate}" / "{Bitrate}"
|
||||||
//StartTimeSubstitution = "{start time}" / "{start_time}"
|
// StartTimeSubstitution = "{start time}" / "{start_time}"
|
||||||
var urlPattern = streamIndex.Attribute("Url")?.Value;
|
var urlPattern = streamIndex.Attribute("Url")?.Value;
|
||||||
var language = streamIndex.Attribute("Language")?.Value;
|
var language = streamIndex.Attribute("Language")?.Value;
|
||||||
//去除不规范的语言标签
|
// 去除不规范的语言标签
|
||||||
if (language?.Length != 3) language = null;
|
if (language?.Length != 3) language = null;
|
||||||
|
|
||||||
//所有c节点
|
// 所有c节点
|
||||||
var cElements = streamIndex.Elements().Where(e => e.Name.LocalName == "c");
|
var cElements = streamIndex.Elements().Where(e => e.Name.LocalName == "c");
|
||||||
|
|
||||||
//所有QualityLevel节点
|
// 所有QualityLevel节点
|
||||||
var qualityLevelElements = streamIndex.Elements().Where(e => e.Name.LocalName == "QualityLevel");
|
var qualityLevelElements = streamIndex.Elements().Where(e => e.Name.LocalName == "QualityLevel");
|
||||||
|
|
||||||
foreach (var qualityLevel in qualityLevelElements)
|
foreach (var qualityLevel in qualityLevelElements)
|
||||||
|
@ -124,7 +124,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
var channels = qualityLevel.Attribute("Channels")?.Value;
|
var channels = qualityLevel.Attribute("Channels")?.Value;
|
||||||
|
|
||||||
StreamSpec streamSpec = new();
|
StreamSpec streamSpec = new();
|
||||||
streamSpec.PublishTime = DateTime.Now; //发布时间默认现在
|
streamSpec.PublishTime = DateTime.Now; // 发布时间默认现在
|
||||||
streamSpec.Extension = "m4s";
|
streamSpec.Extension = "m4s";
|
||||||
streamSpec.OriginalUrl = ParserConfig.OriginalUrl;
|
streamSpec.OriginalUrl = ParserConfig.OriginalUrl;
|
||||||
streamSpec.PeriodId = indexStr;
|
streamSpec.PeriodId = indexStr;
|
||||||
|
@ -148,7 +148,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
streamSpec.Playlist.MediaInit = new MediaSegment();
|
streamSpec.Playlist.MediaInit = new MediaSegment();
|
||||||
if (!string.IsNullOrEmpty(codecPrivateData))
|
if (!string.IsNullOrEmpty(codecPrivateData))
|
||||||
{
|
{
|
||||||
streamSpec.Playlist.MediaInit.Index = -1; //便于排序
|
streamSpec.Playlist.MediaInit.Index = -1; // 便于排序
|
||||||
streamSpec.Playlist.MediaInit.Url = $"hex://{codecPrivateData}";
|
streamSpec.Playlist.MediaInit.Url = $"hex://{codecPrivateData}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
|
|
||||||
foreach (var c in cElements)
|
foreach (var c in cElements)
|
||||||
{
|
{
|
||||||
//每个C元素包含三个属性:@t(start time)\@r(repeat count)\@d(duration)
|
// 每个C元素包含三个属性:@t(start time)\@r(repeat count)\@d(duration)
|
||||||
var _startTimeStr = c.Attribute("t")?.Value;
|
var _startTimeStr = c.Attribute("t")?.Value;
|
||||||
var _durationStr = c.Attribute("d")?.Value;
|
var _durationStr = c.Attribute("d")?.Value;
|
||||||
var _repeatCountStr = c.Attribute("r")?.Value;
|
var _repeatCountStr = c.Attribute("r")?.Value;
|
||||||
|
@ -185,7 +185,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
streamSpec.Playlist.MediaParts[0].MediaSegments.Add(mediaSegment);
|
streamSpec.Playlist.MediaParts[0].MediaSegments.Add(mediaSegment);
|
||||||
if (_repeatCount < 0)
|
if (_repeatCount < 0)
|
||||||
{
|
{
|
||||||
//负数表示一直重复 直到period结束 注意减掉已经加入的1个片段
|
// 负数表示一直重复 直到period结束 注意减掉已经加入的1个片段
|
||||||
_repeatCount = (long)Math.Ceiling(Convert.ToInt64(durationStr) / (double)_duration) - 1;
|
_repeatCount = (long)Math.Ceiling(Convert.ToInt64(durationStr) / (double)_duration) - 1;
|
||||||
}
|
}
|
||||||
for (long i = 0; i < _repeatCount; i++)
|
for (long i = 0; i < _repeatCount; i++)
|
||||||
|
@ -205,7 +205,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
currentTime += _duration;
|
currentTime += _duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
//生成MOOV数据
|
// 生成MOOV数据
|
||||||
if (MSSMoovProcessor.CanHandle(fourCC!))
|
if (MSSMoovProcessor.CanHandle(fourCC!))
|
||||||
{
|
{
|
||||||
streamSpec.MSSData = new MSSData()
|
streamSpec.MSSData = new MSSData()
|
||||||
|
@ -224,9 +224,9 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
ProtectionSystemID = protectionSystemId,
|
ProtectionSystemID = protectionSystemId,
|
||||||
};
|
};
|
||||||
var processor = new MSSMoovProcessor(streamSpec);
|
var processor = new MSSMoovProcessor(streamSpec);
|
||||||
var header = processor.GenHeader(); //trackId可能不正确
|
var header = processor.GenHeader(); // trackId可能不正确
|
||||||
streamSpec.Playlist!.MediaInit!.Url = $"base64://{Convert.ToBase64String(header)}";
|
streamSpec.Playlist!.MediaInit!.Url = $"base64://{Convert.ToBase64String(header)}";
|
||||||
//为音视频写入加密信息
|
// 为音视频写入加密信息
|
||||||
if (isProtection && type != "text")
|
if (isProtection && type != "text")
|
||||||
{
|
{
|
||||||
if (streamSpec.Playlist.MediaInit != null)
|
if (streamSpec.Playlist.MediaInit != null)
|
||||||
|
@ -243,12 +243,11 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp($"[green]{fourCC}[/] not supported! Skiped.");
|
Logger.WarnMarkUp($"[green]{fourCC}[/] not supported! Skiped.");
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//为视频设置默认轨道
|
// 为视频设置默认轨道
|
||||||
var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO);
|
var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO);
|
||||||
var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES);
|
var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES);
|
||||||
foreach (var item in streamList)
|
foreach (var item in streamList)
|
||||||
|
@ -266,7 +265,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return streamList;
|
return Task.FromResult(streamList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -281,11 +280,11 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
|
|
||||||
return fourCC switch
|
return fourCC switch
|
||||||
{
|
{
|
||||||
//AVC视频
|
// AVC视频
|
||||||
"H264" or "X264" or "DAVC" or "AVC1" => ParseAVCCodecs(privateData),
|
"H264" or "X264" or "DAVC" or "AVC1" => ParseAVCCodecs(privateData),
|
||||||
//AAC音频
|
// AAC音频
|
||||||
"AAC" or "AACL" or "AACH" or "AACP" => ParseAACCodecs(fourCC, privateData),
|
"AAC" or "AACL" or "AACH" or "AACP" => ParseAACCodecs(fourCC, privateData),
|
||||||
//默认返回fourCC本身
|
// 默认返回fourCC本身
|
||||||
_ => fourCC.ToLower()
|
_ => fourCC.ToLower()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -313,11 +312,11 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
|
|
||||||
public async Task FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
public async Task FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
||||||
{
|
{
|
||||||
//这里才调用URL预处理器,节省开销
|
// 这里才调用URL预处理器,节省开销
|
||||||
await ProcessUrlAsync(streamSpecs);
|
await ProcessUrlAsync(streamSpecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessUrlAsync(List<StreamSpec> streamSpecs)
|
private Task ProcessUrlAsync(List<StreamSpec> streamSpecs)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < streamSpecs.Count; i++)
|
for (int i = 0; i < streamSpecs.Count; i++)
|
||||||
{
|
{
|
||||||
|
@ -338,6 +337,8 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string PreProcessUrl(string url)
|
public string PreProcessUrl(string url)
|
||||||
|
@ -375,7 +376,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
}
|
}
|
||||||
catch (HttpRequestException) when (ParserConfig.Url != ParserConfig.OriginalUrl)
|
catch (HttpRequestException) when (ParserConfig.Url != ParserConfig.OriginalUrl)
|
||||||
{
|
{
|
||||||
//当URL无法访问时,再请求原始URL
|
// 当URL无法访问时,再请求原始URL
|
||||||
(rawText, url) = await HTTPUtil.GetWebSourceAndNewUrlAsync(ParserConfig.OriginalUrl, ParserConfig.Headers);
|
(rawText, url) = await HTTPUtil.GetWebSourceAndNewUrlAsync(ParserConfig.OriginalUrl, ParserConfig.Headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,17 +386,16 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
var newStreams = await ExtractStreamsAsync(rawText);
|
var newStreams = await ExtractStreamsAsync(rawText);
|
||||||
foreach (var streamSpec in streamSpecs)
|
foreach (var streamSpec in streamSpecs)
|
||||||
{
|
{
|
||||||
//有的网站每次请求MPD返回的码率不一致,导致ToShortString()无法匹配 无法更新playlist
|
// 有的网站每次请求MPD返回的码率不一致,导致ToShortString()无法匹配 无法更新playlist
|
||||||
//故增加通过init url来匹配 (如果有的话)
|
// 故增加通过init url来匹配 (如果有的话)
|
||||||
var match = newStreams.Where(n => n.ToShortString() == streamSpec.ToShortString());
|
var match = newStreams.Where(n => n.ToShortString() == streamSpec.ToShortString());
|
||||||
if (!match.Any())
|
if (!match.Any())
|
||||||
match = newStreams.Where(n => n.Playlist?.MediaInit?.Url == streamSpec.Playlist?.MediaInit?.Url);
|
match = newStreams.Where(n => n.Playlist?.MediaInit?.Url == streamSpec.Playlist?.MediaInit?.Url);
|
||||||
|
|
||||||
if (match.Any())
|
if (match.Any())
|
||||||
streamSpec.Playlist!.MediaParts = match.First().Playlist!.MediaParts; //不更新init
|
streamSpec.Playlist!.MediaParts = match.First().Playlist!.MediaParts; // 不更新init
|
||||||
}
|
}
|
||||||
//这里才调用URL预处理器,节省开销
|
// 这里才调用URL预处理器,节省开销
|
||||||
await ProcessUrlAsync(streamSpecs);
|
await ProcessUrlAsync(streamSpecs);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Mp4SubtitleParser
|
namespace Mp4SubtitleParser
|
||||||
{
|
{
|
||||||
//make BinaryReader in Big Endian
|
// make BinaryReader in Big Endian
|
||||||
class BinaryReader2 : BinaryReader
|
class BinaryReader2 : BinaryReader
|
||||||
{
|
{
|
||||||
public BinaryReader2(System.IO.Stream stream) : base(stream) { }
|
public BinaryReader2(System.IO.Stream stream) : base(stream) { }
|
||||||
|
|
|
@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Mp4SubtitleParser
|
namespace Mp4SubtitleParser
|
||||||
{
|
{
|
||||||
//make BinaryWriter in Big Endian
|
// make BinaryWriter in Big Endian
|
||||||
class BinaryWriter2 : BinaryWriter
|
class BinaryWriter2 : BinaryWriter
|
||||||
{
|
{
|
||||||
private static bool IsLittleEndian = BitConverter.IsLittleEndian;
|
private static bool IsLittleEndian = BitConverter.IsLittleEndian;
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace Mp4SubtitleParser
|
||||||
{
|
{
|
||||||
var info = new ParsedMP4Info();
|
var info = new ParsedMP4Info();
|
||||||
|
|
||||||
//parse init
|
// parse init
|
||||||
new MP4Parser()
|
new MP4Parser()
|
||||||
.Box("moov", MP4Parser.Children)
|
.Box("moov", MP4Parser.Children)
|
||||||
.Box("trak", MP4Parser.Children)
|
.Box("trak", MP4Parser.Children)
|
||||||
|
@ -56,7 +56,7 @@ namespace Mp4SubtitleParser
|
||||||
|
|
||||||
private static void ReadBox(byte[] data, ParsedMP4Info info)
|
private static void ReadBox(byte[] data, ParsedMP4Info info)
|
||||||
{
|
{
|
||||||
//find schm
|
// find schm
|
||||||
var schmBytes = new byte[4] { 0x73, 0x63, 0x68, 0x6d };
|
var schmBytes = new byte[4] { 0x73, 0x63, 0x68, 0x6d };
|
||||||
var schmIndex = 0;
|
var schmIndex = 0;
|
||||||
for (int i = 0; i < data.Length - 4; i++)
|
for (int i = 0; i < data.Length - 4; i++)
|
||||||
|
@ -72,9 +72,9 @@ namespace Mp4SubtitleParser
|
||||||
info.Scheme = System.Text.Encoding.UTF8.GetString(data[schmIndex..][8..12]);
|
info.Scheme = System.Text.Encoding.UTF8.GetString(data[schmIndex..][8..12]);
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (info.Scheme != "cenc") return;
|
// if (info.Scheme != "cenc") return;
|
||||||
|
|
||||||
//find KID
|
// find KID
|
||||||
var tencBytes = new byte[4] { 0x74, 0x65, 0x6E, 0x63 };
|
var tencBytes = new byte[4] { 0x74, 0x65, 0x6E, 0x63 };
|
||||||
var tencIndex = -1;
|
var tencIndex = -1;
|
||||||
for (int i = 0; i < data.Length - 4; i++)
|
for (int i = 0; i < data.Length - 4; i++)
|
||||||
|
|
|
@ -111,7 +111,7 @@ namespace Mp4SubtitleParser
|
||||||
var name = TypeToString(type);
|
var name = TypeToString(type);
|
||||||
var has64BitSize = false;
|
var has64BitSize = false;
|
||||||
|
|
||||||
//Console.WriteLine($"Parsing MP4 box: {name}");
|
// Console.WriteLine($"Parsing MP4 box: {name}");
|
||||||
|
|
||||||
switch (size)
|
switch (size)
|
||||||
{
|
{
|
||||||
|
@ -129,8 +129,7 @@ namespace Mp4SubtitleParser
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
BoxHandler boxDefinition = null;
|
this.BoxDefinitions.TryGetValue(type, out BoxHandler? boxDefinition);
|
||||||
this.BoxDefinitions.TryGetValue(type, out boxDefinition);
|
|
||||||
|
|
||||||
if (boxDefinition != null)
|
if (boxDefinition != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,8 +10,8 @@ namespace Mp4SubtitleParser
|
||||||
public string Begin { get; set; }
|
public string Begin { get; set; }
|
||||||
public string End { get; set; }
|
public string End { get; set; }
|
||||||
public string Region { get; set; }
|
public string Region { get; set; }
|
||||||
public List<XmlElement> Contents { get; set; } = new List<XmlElement>();
|
public List<XmlElement> Contents { get; set; } = new();
|
||||||
public List<string> ContentStrings { get; set; } = new List<string>();
|
public List<string> ContentStrings { get; set; } = new();
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
|
@ -43,7 +43,7 @@ namespace Mp4SubtitleParser
|
||||||
{
|
{
|
||||||
bool sawSTPP = false;
|
bool sawSTPP = false;
|
||||||
|
|
||||||
//parse init
|
// parse init
|
||||||
new MP4Parser()
|
new MP4Parser()
|
||||||
.Box("moov", MP4Parser.Children)
|
.Box("moov", MP4Parser.Children)
|
||||||
.Box("trak", MP4Parser.Children)
|
.Box("trak", MP4Parser.Children)
|
||||||
|
@ -85,12 +85,12 @@ namespace Mp4SubtitleParser
|
||||||
return xmlSrc;
|
return xmlSrc;
|
||||||
|
|
||||||
var _div = bodyNode.SelectSingleNode("ns:div", nsMgr);
|
var _div = bodyNode.SelectSingleNode("ns:div", nsMgr);
|
||||||
//Parse <p> label
|
// Parse <p> label
|
||||||
foreach (XmlElement _p in _div!.SelectNodes("ns:p", nsMgr)!)
|
foreach (XmlElement _p in _div!.SelectNodes("ns:p", nsMgr)!)
|
||||||
{
|
{
|
||||||
var _begin = _p.GetAttribute("begin");
|
var _begin = _p.GetAttribute("begin");
|
||||||
var _end = _p.GetAttribute("end");
|
var _end = _p.GetAttribute("end");
|
||||||
//Handle namespace
|
// Handle namespace
|
||||||
foreach (XmlAttribute attr in _p.Attributes)
|
foreach (XmlAttribute attr in _p.Attributes)
|
||||||
{
|
{
|
||||||
if (attr.LocalName == "begin") _begin = attr.Value;
|
if (attr.LocalName == "begin") _begin = attr.Value;
|
||||||
|
@ -98,8 +98,8 @@ namespace Mp4SubtitleParser
|
||||||
}
|
}
|
||||||
_p.SetAttribute("begin", Add(_begin));
|
_p.SetAttribute("begin", Add(_begin));
|
||||||
_p.SetAttribute("end", Add(_end));
|
_p.SetAttribute("end", Add(_end));
|
||||||
//Console.WriteLine($"{_begin} {_p.GetAttribute("begin")}");
|
// Console.WriteLine($"{_begin} {_p.GetAttribute("begin")}");
|
||||||
//Console.WriteLine($"{_end} {_p.GetAttribute("begin")}");
|
// Console.WriteLine($"{_end} {_p.GetAttribute("begin")}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return xmlDoc.OuterXml;
|
return xmlDoc.OuterXml;
|
||||||
|
@ -135,7 +135,7 @@ namespace Mp4SubtitleParser
|
||||||
|
|
||||||
public static WebVttSub ExtractFromMp4s(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
|
public static WebVttSub ExtractFromMp4s(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
|
||||||
{
|
{
|
||||||
//read ttmls
|
// read ttmls
|
||||||
List<string> xmls = new List<string>();
|
List<string> xmls = new List<string>();
|
||||||
int segIndex = 0;
|
int segIndex = 0;
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
|
@ -143,7 +143,7 @@ namespace Mp4SubtitleParser
|
||||||
var dataSeg = File.ReadAllBytes(item);
|
var dataSeg = File.ReadAllBytes(item);
|
||||||
|
|
||||||
var sawMDAT = false;
|
var sawMDAT = false;
|
||||||
//parse media
|
// parse media
|
||||||
new MP4Parser()
|
new MP4Parser()
|
||||||
.Box("mdat", MP4Parser.AllData((data) =>
|
.Box("mdat", MP4Parser.AllData((data) =>
|
||||||
{
|
{
|
||||||
|
@ -161,10 +161,7 @@ namespace Mp4SubtitleParser
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var datas = SplitMultipleRootElements(Encoding.UTF8.GetString(data));
|
var datas = SplitMultipleRootElements(Encoding.UTF8.GetString(data));
|
||||||
foreach (var item in datas)
|
xmls.AddRange(datas);
|
||||||
{
|
|
||||||
xmls.Add(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.Parse(dataSeg,/* partialOkay= */ false);
|
.Parse(dataSeg,/* partialOkay= */ false);
|
||||||
|
@ -181,7 +178,7 @@ namespace Mp4SubtitleParser
|
||||||
|
|
||||||
public static WebVttSub ExtractFromTTMLs(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
|
public static WebVttSub ExtractFromTTMLs(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
|
||||||
{
|
{
|
||||||
//read ttmls
|
// read ttmls
|
||||||
List<string> xmls = new List<string>();
|
List<string> xmls = new List<string>();
|
||||||
int segIndex = 0;
|
int segIndex = 0;
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
|
@ -203,7 +200,7 @@ namespace Mp4SubtitleParser
|
||||||
|
|
||||||
private static WebVttSub ExtractSub(List<string> xmls, long baseTimestamp)
|
private static WebVttSub ExtractSub(List<string> xmls, long baseTimestamp)
|
||||||
{
|
{
|
||||||
//parsing
|
// parsing
|
||||||
var xmlDoc = new XmlDocument();
|
var xmlDoc = new XmlDocument();
|
||||||
var finalSubs = new List<SubEntity>();
|
var finalSubs = new List<SubEntity>();
|
||||||
XmlNode? headNode = null;
|
XmlNode? headNode = null;
|
||||||
|
@ -215,7 +212,7 @@ namespace Mp4SubtitleParser
|
||||||
var xmlContent = item;
|
var xmlContent = item;
|
||||||
if (!xmlContent.Contains("<tt")) continue;
|
if (!xmlContent.Contains("<tt")) continue;
|
||||||
|
|
||||||
//fix non-standard xml
|
// fix non-standard xml
|
||||||
var xmlContentFix = xmlContent;
|
var xmlContentFix = xmlContent;
|
||||||
if (regex.IsMatch(xmlContent))
|
if (regex.IsMatch(xmlContent))
|
||||||
{
|
{
|
||||||
|
@ -256,8 +253,8 @@ namespace Mp4SubtitleParser
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
||||||
//PNG Subs
|
// PNG Subs
|
||||||
var imageDic = new Dictionary<string, string>(); //id, Base64
|
var imageDic = new Dictionary<string, string>(); // id, Base64
|
||||||
if (ImageRegex().IsMatch(xmlDoc.InnerXml))
|
if (ImageRegex().IsMatch(xmlDoc.InnerXml))
|
||||||
{
|
{
|
||||||
foreach (Match img in ImageRegex().Matches(xmlDoc.InnerXml))
|
foreach (Match img in ImageRegex().Matches(xmlDoc.InnerXml))
|
||||||
|
@ -266,7 +263,7 @@ namespace Mp4SubtitleParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//convert <div> to <p>
|
// convert <div> to <p>
|
||||||
if (_div!.SelectNodes("ns:p", nsMgr) == null || _div!.SelectNodes("ns:p", nsMgr)!.Count == 0)
|
if (_div!.SelectNodes("ns:p", nsMgr) == null || _div!.SelectNodes("ns:p", nsMgr)!.Count == 0)
|
||||||
{
|
{
|
||||||
foreach (XmlElement _tDiv in bodyNode.SelectNodes("ns:div", nsMgr)!)
|
foreach (XmlElement _tDiv in bodyNode.SelectNodes("ns:div", nsMgr)!)
|
||||||
|
@ -277,14 +274,14 @@ namespace Mp4SubtitleParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Parse <p> label
|
// Parse <p> label
|
||||||
foreach (XmlElement _p in _div!.SelectNodes("ns:p", nsMgr)!)
|
foreach (XmlElement _p in _div!.SelectNodes("ns:p", nsMgr)!)
|
||||||
{
|
{
|
||||||
var _begin = _p.GetAttribute("begin");
|
var _begin = _p.GetAttribute("begin");
|
||||||
var _end = _p.GetAttribute("end");
|
var _end = _p.GetAttribute("end");
|
||||||
var _region = _p.GetAttribute("region");
|
var _region = _p.GetAttribute("region");
|
||||||
var _bgImg = _p.GetAttribute("smpte:backgroundImage");
|
var _bgImg = _p.GetAttribute("smpte:backgroundImage");
|
||||||
//Handle namespace
|
// Handle namespace
|
||||||
foreach (XmlAttribute attr in _p.Attributes)
|
foreach (XmlAttribute attr in _p.Attributes)
|
||||||
{
|
{
|
||||||
if (attr.LocalName == "begin") _begin = attr.Value;
|
if (attr.LocalName == "begin") _begin = attr.Value;
|
||||||
|
@ -301,7 +298,7 @@ namespace Mp4SubtitleParser
|
||||||
if (string.IsNullOrEmpty(_bgImg))
|
if (string.IsNullOrEmpty(_bgImg))
|
||||||
{
|
{
|
||||||
var _spans = _p.ChildNodes;
|
var _spans = _p.ChildNodes;
|
||||||
//Collect <span>
|
// Collect <span>
|
||||||
foreach (XmlNode _node in _spans)
|
foreach (XmlNode _node in _spans)
|
||||||
{
|
{
|
||||||
if (_node.NodeType == XmlNodeType.Element)
|
if (_node.NodeType == XmlNodeType.Element)
|
||||||
|
@ -333,12 +330,12 @@ namespace Mp4SubtitleParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if one <p> has been splitted
|
// Check if one <p> has been splitted
|
||||||
var index = finalSubs.FindLastIndex(s => s.End == _begin && s.Region == _region && s.ContentStrings.SequenceEqual(sub.ContentStrings));
|
var index = finalSubs.FindLastIndex(s => s.End == _begin && s.Region == _region && s.ContentStrings.SequenceEqual(sub.ContentStrings));
|
||||||
//Skip empty lines
|
// Skip empty lines
|
||||||
if (sub.ContentStrings.Count > 0)
|
if (sub.ContentStrings.Count > 0)
|
||||||
{
|
{
|
||||||
//Extend <p> duration
|
// Extend <p> duration
|
||||||
if (index != -1)
|
if (index != -1)
|
||||||
finalSubs[index].End = sub.End;
|
finalSubs[index].End = sub.End;
|
||||||
else if (!finalSubs.Contains(sub))
|
else if (!finalSubs.Contains(sub))
|
||||||
|
@ -372,7 +369,7 @@ namespace Mp4SubtitleParser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StringBuilder vtt = new StringBuilder();
|
var vtt = new StringBuilder();
|
||||||
vtt.AppendLine("WEBVTT");
|
vtt.AppendLine("WEBVTT");
|
||||||
foreach (var item in dic)
|
foreach (var item in dic)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Mp4SubtitleParser
|
namespace Mp4SubtitleParser;
|
||||||
|
|
||||||
|
public class MP4VttUtil
|
||||||
{
|
{
|
||||||
public class MP4VttUtil
|
|
||||||
{
|
|
||||||
public static (bool, uint) CheckInit(byte[] data)
|
public static (bool, uint) CheckInit(byte[] data)
|
||||||
{
|
{
|
||||||
uint timescale = 0;
|
uint timescale = 0;
|
||||||
bool sawWVTT = false;
|
bool sawWVTT = false;
|
||||||
|
|
||||||
//parse init
|
// parse init
|
||||||
new MP4Parser()
|
new MP4Parser()
|
||||||
.Box("moov", MP4Parser.Children)
|
.Box("moov", MP4Parser.Children)
|
||||||
.Box("trak", MP4Parser.Children)
|
.Box("trak", MP4Parser.Children)
|
||||||
|
@ -53,7 +53,7 @@ namespace Mp4SubtitleParser
|
||||||
List<Sample> presentations = new();
|
List<Sample> presentations = new();
|
||||||
|
|
||||||
|
|
||||||
//parse media
|
// parse media
|
||||||
new MP4Parser()
|
new MP4Parser()
|
||||||
.Box("moof", MP4Parser.Children)
|
.Box("moof", MP4Parser.Children)
|
||||||
.Box("traf", MP4Parser.Children)
|
.Box("traf", MP4Parser.Children)
|
||||||
|
@ -145,7 +145,7 @@ namespace Mp4SubtitleParser
|
||||||
payload,
|
payload,
|
||||||
0 + (double)startTime / timescale,
|
0 + (double)startTime / timescale,
|
||||||
0 + (double)currentTime / timescale);
|
0 + (double)currentTime / timescale);
|
||||||
//Check if same subtitle has been splitted
|
// Check if same subtitle has been splitted
|
||||||
if (cue != null)
|
if (cue != null)
|
||||||
{
|
{
|
||||||
var index = cues.FindLastIndex(s => s.EndTime == cue.StartTime && s.Settings == cue.Settings && s.Payload == cue.Payload);
|
var index = cues.FindLastIndex(s => s.EndTime == cue.StartTime && s.Settings == cue.Settings && s.Payload == cue.Payload);
|
||||||
|
@ -174,7 +174,7 @@ namespace Mp4SubtitleParser
|
||||||
|
|
||||||
if (reader.HasMoreData())
|
if (reader.HasMoreData())
|
||||||
{
|
{
|
||||||
//throw new Exception("MDAT which contain VTT cues and non-VTT data are not currently supported!");
|
// throw new Exception("MDAT which contain VTT cues and non-VTT data are not currently supported!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,5 +212,4 @@ namespace Mp4SubtitleParser
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -4,15 +4,15 @@ using N_m3u8DL_RE.Common.Util;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
//https://github.com/canalplus/rx-player/blob/48d1f845064cea5c5a3546d2c53b1855c2be149d/src/parsers/manifest/smooth/get_codecs.ts
|
// https://github.com/canalplus/rx-player/blob/48d1f845064cea5c5a3546d2c53b1855c2be149d/src/parsers/manifest/smooth/get_codecs.ts
|
||||||
//https://github.dev/Dash-Industry-Forum/dash.js/blob/2aad3e79079b4de0bcd961ce6b4957103d98a621/src/mss/MssFragmentMoovProcessor.js
|
// https://github.dev/Dash-Industry-Forum/dash.js/blob/2aad3e79079b4de0bcd961ce6b4957103d98a621/src/mss/MssFragmentMoovProcessor.js
|
||||||
//https://github.com/yt-dlp/yt-dlp/blob/3639df54c3298e35b5ae2a96a25bc4d3c38950d0/yt_dlp/downloader/ism.py
|
// https://github.com/yt-dlp/yt-dlp/blob/3639df54c3298e35b5ae2a96a25bc4d3c38950d0/yt_dlp/downloader/ism.py
|
||||||
//https://github.com/google/ExoPlayer/blob/a9444c880230d2c2c79097e89259ce0b9f80b87d/library/extractor/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java#L38
|
// https://github.com/google/ExoPlayer/blob/a9444c880230d2c2c79097e89259ce0b9f80b87d/library/extractor/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java#L38
|
||||||
//https://github.com/sannies/mp4parser/blob/master/isoparser/src/main/java/org/mp4parser/boxes/iso14496/part15/HevcDecoderConfigurationRecord.java
|
// https://github.com/sannies/mp4parser/blob/master/isoparser/src/main/java/org/mp4parser/boxes/iso14496/part15/HevcDecoderConfigurationRecord.java
|
||||||
namespace N_m3u8DL_RE.Parser.Mp4
|
namespace N_m3u8DL_RE.Parser.Mp4;
|
||||||
|
|
||||||
|
public partial class MSSMoovProcessor
|
||||||
{
|
{
|
||||||
public partial class MSSMoovProcessor
|
|
||||||
{
|
|
||||||
[GeneratedRegex("\\<KID\\>(.*?)\\<")]
|
[GeneratedRegex("\\<KID\\>(.*?)\\<")]
|
||||||
private static partial Regex KIDRegex();
|
private static partial Regex KIDRegex();
|
||||||
|
|
||||||
|
@ -24,8 +24,8 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
private int Timesacle;
|
private int Timesacle;
|
||||||
private long Duration;
|
private long Duration;
|
||||||
private string Language { get => StreamSpec.Language ?? "und"; }
|
private string Language { get => StreamSpec.Language ?? "und"; }
|
||||||
private int Width { get => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').First()); }
|
private int Width => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').First());
|
||||||
private int Height { get => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').Last()); }
|
private int Height => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').Last());
|
||||||
private string StreamType;
|
private string StreamType;
|
||||||
private int Channels;
|
private int Channels;
|
||||||
private int BitsPerSample;
|
private int BitsPerSample;
|
||||||
|
@ -60,7 +60,7 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
private static byte TRACK_IN_MOVIE = 0x2;
|
private static byte TRACK_IN_MOVIE = 0x2;
|
||||||
private static byte TRACK_IN_PREVIEW = 0x4;
|
private static byte TRACK_IN_PREVIEW = 0x4;
|
||||||
private static byte SELF_CONTAINED = 0x1;
|
private static byte SELF_CONTAINED = 0x1;
|
||||||
private static List<string> SupportedFourCC = new List<string>()
|
private static List<string> SupportedFourCC = new()
|
||||||
{
|
{
|
||||||
"HVC1","HEV1","AACL","AACH","EC-3","H264","AVC1","DAVC","AVC1","TTML","DVHE","DVH1"
|
"HVC1","HEV1","AACL","AACH","EC-3","H264","AVC1","DAVC","AVC1","TTML","DVHE","DVH1"
|
||||||
};
|
};
|
||||||
|
@ -82,13 +82,13 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
this.ProtectionData = data.ProtectionData;
|
this.ProtectionData = data.ProtectionData;
|
||||||
this.ProtectionSystemId = data.ProtectionSystemID;
|
this.ProtectionSystemId = data.ProtectionSystemID;
|
||||||
|
|
||||||
//需要手动生成CodecPrivateData
|
// 需要手动生成CodecPrivateData
|
||||||
if (string.IsNullOrEmpty(CodecPrivateData))
|
if (string.IsNullOrEmpty(CodecPrivateData))
|
||||||
{
|
{
|
||||||
GenCodecPrivateDataForAAC();
|
GenCodecPrivateDataForAAC();
|
||||||
}
|
}
|
||||||
|
|
||||||
//解析KID
|
// 解析KID
|
||||||
if (IsProtection)
|
if (IsProtection)
|
||||||
{
|
{
|
||||||
ExtractKID();
|
ExtractKID();
|
||||||
|
@ -116,7 +116,7 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
|
|
||||||
private void GenCodecPrivateDataForAAC()
|
private void GenCodecPrivateDataForAAC()
|
||||||
{
|
{
|
||||||
var objectType = 0x02; //AAC Main Low Complexity => object Type = 2
|
var objectType = 0x02; // AAC Main Low Complexity => object Type = 2
|
||||||
var indexFreq = SamplingFrequencyIndex(SamplingRate);
|
var indexFreq = SamplingFrequencyIndex(SamplingRate);
|
||||||
|
|
||||||
if (FourCC == "AACH")
|
if (FourCC == "AACH")
|
||||||
|
@ -127,17 +127,17 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
var codecPrivateData = new byte[4];
|
var codecPrivateData = new byte[4];
|
||||||
var extensionSamplingFrequencyIndex = SamplingFrequencyIndex(SamplingRate * 2); // in HE AAC Extension Sampling frequence
|
var extensionSamplingFrequencyIndex = SamplingFrequencyIndex(SamplingRate * 2); // in HE AAC Extension Sampling frequence
|
||||||
// equals to SamplingRate*2
|
// equals to SamplingRate*2
|
||||||
//Freq Index is present for 3 bits in the first byte, last bit is in the second
|
// Freq Index is present for 3 bits in the first byte, last bit is in the second
|
||||||
codecPrivateData[0] = (byte)((objectType << 3) | (indexFreq >> 1));
|
codecPrivateData[0] = (byte)((objectType << 3) | (indexFreq >> 1));
|
||||||
codecPrivateData[1] = (byte)((indexFreq << 7) | (Channels << 3) | (extensionSamplingFrequencyIndex >> 1));
|
codecPrivateData[1] = (byte)((indexFreq << 7) | (Channels << 3) | (extensionSamplingFrequencyIndex >> 1));
|
||||||
codecPrivateData[2] = (byte)((extensionSamplingFrequencyIndex << 7) | (0x02 << 2)); // origin object type equals to 2 => AAC Main Low Complexity
|
codecPrivateData[2] = (byte)((extensionSamplingFrequencyIndex << 7) | (0x02 << 2)); // origin object type equals to 2 => AAC Main Low Complexity
|
||||||
codecPrivateData[3] = 0x0; //alignment bits
|
codecPrivateData[3] = 0x0; // alignment bits
|
||||||
|
|
||||||
var arr16 = new ushort[2];
|
var arr16 = new ushort[2];
|
||||||
arr16[0] = (ushort)((codecPrivateData[0] << 8) + codecPrivateData[1]);
|
arr16[0] = (ushort)((codecPrivateData[0] << 8) + codecPrivateData[1]);
|
||||||
arr16[1] = (ushort)((codecPrivateData[2] << 8) + codecPrivateData[3]);
|
arr16[1] = (ushort)((codecPrivateData[2] << 8) + codecPrivateData[3]);
|
||||||
|
|
||||||
//convert decimal to hex value
|
// convert decimal to hex value
|
||||||
this.CodecPrivateData = HexUtil.BytesToHex(BitConverter.GetBytes(arr16[0])).PadLeft(16, '0');
|
this.CodecPrivateData = HexUtil.BytesToHex(BitConverter.GetBytes(arr16[0])).PadLeft(16, '0');
|
||||||
this.CodecPrivateData += HexUtil.BytesToHex(BitConverter.GetBytes(arr16[1])).PadLeft(16, '0');
|
this.CodecPrivateData += HexUtil.BytesToHex(BitConverter.GetBytes(arr16[1])).PadLeft(16, '0');
|
||||||
}
|
}
|
||||||
|
@ -146,36 +146,36 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
// 2 bytes : XXXXX XXXX XXXX XXX
|
// 2 bytes : XXXXX XXXX XXXX XXX
|
||||||
// ' ObjectType' 'Freq Index' 'Channels value' 'GAS = 000'
|
// ' ObjectType' 'Freq Index' 'Channels value' 'GAS = 000'
|
||||||
var codecPrivateData = new byte[2];
|
var codecPrivateData = new byte[2];
|
||||||
//Freq Index is present for 3 bits in the first byte, last bit is in the second
|
// Freq Index is present for 3 bits in the first byte, last bit is in the second
|
||||||
codecPrivateData[0] = (byte)((objectType << 3) | (indexFreq >> 1));
|
codecPrivateData[0] = (byte)((objectType << 3) | (indexFreq >> 1));
|
||||||
codecPrivateData[1] = (byte)((indexFreq << 7) | Channels << 3);
|
codecPrivateData[1] = (byte)((indexFreq << 7) | Channels << 3);
|
||||||
// put the 2 bytes in an 16 bits array
|
// put the 2 bytes in an 16 bits array
|
||||||
var arr16 = new ushort[1];
|
var arr16 = new ushort[1];
|
||||||
arr16[0] = (ushort)((codecPrivateData[0] << 8) + codecPrivateData[1]);
|
arr16[0] = (ushort)((codecPrivateData[0] << 8) + codecPrivateData[1]);
|
||||||
|
|
||||||
//convert decimal to hex value
|
// convert decimal to hex value
|
||||||
this.CodecPrivateData = HexUtil.BytesToHex(BitConverter.GetBytes(arr16[0])).PadLeft(16, '0');
|
this.CodecPrivateData = HexUtil.BytesToHex(BitConverter.GetBytes(arr16[0])).PadLeft(16, '0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExtractKID()
|
private void ExtractKID()
|
||||||
{
|
{
|
||||||
//playready
|
// playready
|
||||||
if (ProtectionSystemId.ToUpper() == "9A04F079-9840-4286-AB92-E65BE0885F95")
|
if (ProtectionSystemId.ToUpper() == "9A04F079-9840-4286-AB92-E65BE0885F95")
|
||||||
{
|
{
|
||||||
var bytes = HexUtil.HexToBytes(ProtectionData.Replace("00", ""));
|
var bytes = HexUtil.HexToBytes(ProtectionData.Replace("00", ""));
|
||||||
var text = Encoding.ASCII.GetString(bytes);
|
var text = Encoding.ASCII.GetString(bytes);
|
||||||
var kidBytes = Convert.FromBase64String(KIDRegex().Match(text).Groups[1].Value);
|
var kidBytes = Convert.FromBase64String(KIDRegex().Match(text).Groups[1].Value);
|
||||||
//save kid for playready
|
// save kid for playready
|
||||||
this.ProtecitonKID_PR = HexUtil.BytesToHex(kidBytes);
|
this.ProtecitonKID_PR = HexUtil.BytesToHex(kidBytes);
|
||||||
//fix byte order
|
// fix byte order
|
||||||
var reverse1 = new byte[4] { kidBytes[3], kidBytes[2], kidBytes[1], kidBytes[0] };
|
var reverse1 = new byte[4] { kidBytes[3], kidBytes[2], kidBytes[1], kidBytes[0] };
|
||||||
var reverse2 = new byte[4] { kidBytes[5], kidBytes[4], kidBytes[7], kidBytes[6] };
|
var reverse2 = new byte[4] { kidBytes[5], kidBytes[4], kidBytes[7], kidBytes[6] };
|
||||||
Array.Copy(reverse1, 0, kidBytes, 0, reverse1.Length);
|
Array.Copy(reverse1, 0, kidBytes, 0, reverse1.Length);
|
||||||
Array.Copy(reverse2, 0, kidBytes, 4, reverse1.Length);
|
Array.Copy(reverse2, 0, kidBytes, 4, reverse1.Length);
|
||||||
this.ProtecitonKID = HexUtil.BytesToHex(kidBytes);
|
this.ProtecitonKID = HexUtil.BytesToHex(kidBytes);
|
||||||
}
|
}
|
||||||
//widevine
|
// widevine
|
||||||
else if (ProtectionSystemId.ToUpper() == "EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED")
|
else if (ProtectionSystemId.ToUpper() == "EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED")
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
|
@ -216,19 +216,19 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
sinfPayload.AddRange(frmaBox);
|
sinfPayload.AddRange(frmaBox);
|
||||||
|
|
||||||
var schmPayload = new List<byte>();
|
var schmPayload = new List<byte>();
|
||||||
schmPayload.AddRange(Encoding.ASCII.GetBytes("cenc")); //scheme_type 'cenc' => common encryption
|
schmPayload.AddRange(Encoding.ASCII.GetBytes("cenc")); // scheme_type 'cenc' => common encryption
|
||||||
schmPayload.AddRange(new byte[] { 0, 1, 0, 0 }); //scheme_version Major version 1, Minor version 0
|
schmPayload.AddRange(new byte[] { 0, 1, 0, 0 }); // scheme_version Major version 1, Minor version 0
|
||||||
var schmBox = FullBox("schm", 0, 0, schmPayload.ToArray());
|
var schmBox = FullBox("schm", 0, 0, schmPayload.ToArray());
|
||||||
|
|
||||||
sinfPayload.AddRange(schmBox);
|
sinfPayload.AddRange(schmBox);
|
||||||
|
|
||||||
var tencPayload = new List<byte>();
|
var tencPayload = new List<byte>();
|
||||||
tencPayload.AddRange(new byte[] { 0, 0 });
|
tencPayload.AddRange(new byte[] { 0, 0 });
|
||||||
tencPayload.Add(0x1); //default_IsProtected
|
tencPayload.Add(0x1); // default_IsProtected
|
||||||
tencPayload.Add(0x8); //default_Per_Sample_IV_size
|
tencPayload.Add(0x8); // default_Per_Sample_IV_size
|
||||||
tencPayload.AddRange(HexUtil.HexToBytes(ProtecitonKID)); //default_KID
|
tencPayload.AddRange(HexUtil.HexToBytes(ProtecitonKID)); // default_KID
|
||||||
//tencPayload.Add(0x8);//default_constant_IV_size
|
// tencPayload.Add(0x8);// default_constant_IV_size
|
||||||
//tencPayload.AddRange(new byte[8]);//default_constant_IV
|
// tencPayload.AddRange(new byte[8]);// default_constant_IV
|
||||||
var tencBox = FullBox("tenc", 0, 0, tencPayload.ToArray());
|
var tencBox = FullBox("tenc", 0, 0, tencPayload.ToArray());
|
||||||
|
|
||||||
var schiBox = Box("schi", tencBox);
|
var schiBox = Box("schi", tencBox);
|
||||||
|
@ -244,12 +244,12 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
using var writer = new BinaryWriter2(stream);
|
using var writer = new BinaryWriter2(stream);
|
||||||
|
|
||||||
writer.Write("isml"); //major brand
|
writer.Write("isml"); // major brand
|
||||||
writer.WriteUInt(1); //minor version
|
writer.WriteUInt(1); // minor version
|
||||||
writer.Write("iso5"); //compatible brand
|
writer.Write("iso5"); // compatible brand
|
||||||
writer.Write("iso6"); //compatible brand
|
writer.Write("iso6"); // compatible brand
|
||||||
writer.Write("piff"); //compatible brand
|
writer.Write("piff"); // compatible brand
|
||||||
writer.Write("msdh"); //compatible brand
|
writer.Write("msdh"); // compatible brand
|
||||||
|
|
||||||
return Box("ftyp", stream.ToArray());
|
return Box("ftyp", stream.ToArray());
|
||||||
}
|
}
|
||||||
|
@ -259,26 +259,26 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
using var writer = new BinaryWriter2(stream);
|
using var writer = new BinaryWriter2(stream);
|
||||||
|
|
||||||
writer.WriteULong(CreationTime); //creation_time
|
writer.WriteULong(CreationTime); // creation_time
|
||||||
writer.WriteULong(CreationTime); //modification_time
|
writer.WriteULong(CreationTime); // modification_time
|
||||||
writer.WriteUInt(Timesacle); //timescale
|
writer.WriteUInt(Timesacle); // timescale
|
||||||
writer.WriteULong(Duration); //duration
|
writer.WriteULong(Duration); // duration
|
||||||
writer.WriteUShort(1, padding: 2); //rate
|
writer.WriteUShort(1, padding: 2); // rate
|
||||||
writer.WriteByte(1, padding: 1); //volume
|
writer.WriteByte(1, padding: 1); // volume
|
||||||
writer.WriteUShort(0); //reserved
|
writer.WriteUShort(0); // reserved
|
||||||
writer.WriteUInt(0);
|
writer.WriteUInt(0);
|
||||||
writer.WriteUInt(0);
|
writer.WriteUInt(0);
|
||||||
|
|
||||||
writer.Write(UnityMatrix);
|
writer.Write(UnityMatrix);
|
||||||
|
|
||||||
writer.WriteUInt(0); //pre defined
|
writer.WriteUInt(0); // pre defined
|
||||||
writer.WriteUInt(0);
|
writer.WriteUInt(0);
|
||||||
writer.WriteUInt(0);
|
writer.WriteUInt(0);
|
||||||
writer.WriteUInt(0);
|
writer.WriteUInt(0);
|
||||||
writer.WriteUInt(0);
|
writer.WriteUInt(0);
|
||||||
writer.WriteUInt(0);
|
writer.WriteUInt(0);
|
||||||
|
|
||||||
writer.WriteUInt(0xffffffff); //next track id
|
writer.WriteUInt(0xffffffff); // next track id
|
||||||
|
|
||||||
|
|
||||||
return FullBox("mvhd", 1, 0, stream.ToArray());
|
return FullBox("mvhd", 1, 0, stream.ToArray());
|
||||||
|
@ -289,22 +289,22 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
using var writer = new BinaryWriter2(stream);
|
using var writer = new BinaryWriter2(stream);
|
||||||
|
|
||||||
writer.WriteULong(CreationTime); //creation_time
|
writer.WriteULong(CreationTime); // creation_time
|
||||||
writer.WriteULong(CreationTime); //modification_time
|
writer.WriteULong(CreationTime); // modification_time
|
||||||
writer.WriteUInt(TrackId); //track id
|
writer.WriteUInt(TrackId); // track id
|
||||||
writer.WriteUInt(0); //reserved
|
writer.WriteUInt(0); // reserved
|
||||||
writer.WriteULong(Duration); //duration
|
writer.WriteULong(Duration); // duration
|
||||||
writer.WriteUInt(0); //reserved
|
writer.WriteUInt(0); // reserved
|
||||||
writer.WriteUInt(0);
|
writer.WriteUInt(0);
|
||||||
writer.WriteShort(0); //layer
|
writer.WriteShort(0); // layer
|
||||||
writer.WriteShort(0); //alternate group
|
writer.WriteShort(0); // alternate group
|
||||||
writer.WriteByte(StreamType == "audio" ? (byte)1 : (byte)0, padding: 1); //volume
|
writer.WriteByte(StreamType == "audio" ? (byte)1 : (byte)0, padding: 1); // volume
|
||||||
writer.WriteUShort(0); //reserved
|
writer.WriteUShort(0); // reserved
|
||||||
|
|
||||||
writer.Write(UnityMatrix);
|
writer.Write(UnityMatrix);
|
||||||
|
|
||||||
writer.WriteUShort(Width, padding: 2); //width
|
writer.WriteUShort(Width, padding: 2); // width
|
||||||
writer.WriteUShort(Height, padding: 2); //height
|
writer.WriteUShort(Height, padding: 2); // height
|
||||||
|
|
||||||
return FullBox("tkhd", 1, (uint)TRACK_ENABLED | TRACK_IN_MOVIE | TRACK_IN_PREVIEW, stream.ToArray());
|
return FullBox("tkhd", 1, (uint)TRACK_ENABLED | TRACK_IN_MOVIE | TRACK_IN_PREVIEW, stream.ToArray());
|
||||||
}
|
}
|
||||||
|
@ -315,12 +315,12 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
using var writer = new BinaryWriter2(stream);
|
using var writer = new BinaryWriter2(stream);
|
||||||
|
|
||||||
writer.WriteULong(CreationTime); //creation_time
|
writer.WriteULong(CreationTime); // creation_time
|
||||||
writer.WriteULong(CreationTime); //modification_time
|
writer.WriteULong(CreationTime); // modification_time
|
||||||
writer.WriteUInt(Timesacle); //timescale
|
writer.WriteUInt(Timesacle); // timescale
|
||||||
writer.WriteULong(Duration); //duration
|
writer.WriteULong(Duration); // duration
|
||||||
writer.WriteUShort((Language[0] - 0x60) << 10 | (Language[1] - 0x60) << 5 | (Language[2] - 0x60)); //language
|
writer.WriteUShort((Language[0] - 0x60) << 10 | (Language[1] - 0x60) << 5 | (Language[2] - 0x60)); // language
|
||||||
writer.WriteUShort(0); //pre defined
|
writer.WriteUShort(0); // pre defined
|
||||||
|
|
||||||
return FullBox("mdhd", 1, 0, stream.ToArray());
|
return FullBox("mdhd", 1, 0, stream.ToArray());
|
||||||
}
|
}
|
||||||
|
@ -330,16 +330,16 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
using var writer = new BinaryWriter2(stream);
|
using var writer = new BinaryWriter2(stream);
|
||||||
|
|
||||||
writer.WriteUInt(0); //pre defined
|
writer.WriteUInt(0); // pre defined
|
||||||
if (StreamType == "audio") writer.Write("soun");
|
if (StreamType == "audio") writer.Write("soun");
|
||||||
else if (StreamType == "video") writer.Write("vide");
|
else if (StreamType == "video") writer.Write("vide");
|
||||||
else if (StreamType == "text") writer.Write("subt");
|
else if (StreamType == "text") writer.Write("subt");
|
||||||
else throw new NotSupportedException();
|
else throw new NotSupportedException();
|
||||||
|
|
||||||
writer.WriteUInt(0); //reserved
|
writer.WriteUInt(0); // reserved
|
||||||
writer.WriteUInt(0);
|
writer.WriteUInt(0);
|
||||||
writer.WriteUInt(0);
|
writer.WriteUInt(0);
|
||||||
writer.Write($"{StreamSpec.GroupId ?? "RE Handler"}\0"); //name
|
writer.Write($"{StreamSpec.GroupId ?? "RE Handler"}\0"); // name
|
||||||
|
|
||||||
return FullBox("hdlr", 0, 0, stream.ToArray());
|
return FullBox("hdlr", 0, 0, stream.ToArray());
|
||||||
}
|
}
|
||||||
|
@ -353,22 +353,22 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
if (StreamType == "audio")
|
if (StreamType == "audio")
|
||||||
{
|
{
|
||||||
var smhd = new List<byte>();
|
var smhd = new List<byte>();
|
||||||
smhd.Add(0); smhd.Add(0); //balance
|
smhd.Add(0); smhd.Add(0); // balance
|
||||||
smhd.Add(0); smhd.Add(0); //reserved
|
smhd.Add(0); smhd.Add(0); // reserved
|
||||||
|
|
||||||
minfPayload.AddRange(FullBox("smhd", 0, 0, smhd.ToArray())); //Sound Media Header
|
minfPayload.AddRange(FullBox("smhd", 0, 0, smhd.ToArray())); // Sound Media Header
|
||||||
}
|
}
|
||||||
else if (StreamType == "video")
|
else if (StreamType == "video")
|
||||||
{
|
{
|
||||||
var vmhd = new List<byte>();
|
var vmhd = new List<byte>();
|
||||||
vmhd.Add(0); vmhd.Add(0); //graphics mode
|
vmhd.Add(0); vmhd.Add(0); // graphics mode
|
||||||
vmhd.Add(0); vmhd.Add(0); vmhd.Add(0); vmhd.Add(0); vmhd.Add(0); vmhd.Add(0);//opcolor
|
vmhd.Add(0); vmhd.Add(0); vmhd.Add(0); vmhd.Add(0); vmhd.Add(0); vmhd.Add(0);// opcolor
|
||||||
|
|
||||||
minfPayload.AddRange(FullBox("vmhd", 0, 1, vmhd.ToArray())); //Video Media Header
|
minfPayload.AddRange(FullBox("vmhd", 0, 1, vmhd.ToArray())); // Video Media Header
|
||||||
}
|
}
|
||||||
else if (StreamType == "text")
|
else if (StreamType == "text")
|
||||||
{
|
{
|
||||||
minfPayload.AddRange(FullBox("sthd", 0, 0, new byte[0])); //Subtitle Media Header
|
minfPayload.AddRange(FullBox("sthd", 0, 0, new byte[0])); // Subtitle Media Header
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -376,11 +376,11 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
}
|
}
|
||||||
|
|
||||||
var drefPayload = new List<byte>();
|
var drefPayload = new List<byte>();
|
||||||
drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(1); //entry count
|
drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(1); // entry count
|
||||||
drefPayload.AddRange(FullBox("url ", 0, SELF_CONTAINED, new byte[0])); //Data Entry URL Box
|
drefPayload.AddRange(FullBox("url ", 0, SELF_CONTAINED, new byte[0])); // Data Entry URL Box
|
||||||
|
|
||||||
var dinfPayload = FullBox("dref", 0, 0, drefPayload.ToArray()); //Data Reference Box
|
var dinfPayload = FullBox("dref", 0, 0, drefPayload.ToArray()); // Data Reference Box
|
||||||
minfPayload.AddRange(Box("dinf", dinfPayload.ToArray())); //Data Information Box
|
minfPayload.AddRange(Box("dinf", dinfPayload.ToArray())); // Data Information Box
|
||||||
|
|
||||||
return minfPayload.ToArray();
|
return minfPayload.ToArray();
|
||||||
}
|
}
|
||||||
|
@ -398,35 +398,35 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
// esdsLength = 34 + len(audioSpecificConfig)
|
// esdsLength = 34 + len(audioSpecificConfig)
|
||||||
|
|
||||||
// ES_Descriptor (see ISO/IEC 14496-1 (Systems))
|
// ES_Descriptor (see ISO/IEC 14496-1 (Systems))
|
||||||
writer.WriteByte(0x03); //tag = 0x03 (ES_DescrTag)
|
writer.WriteByte(0x03); // tag = 0x03 (ES_DescrTag)
|
||||||
writer.WriteByte((byte)(20 + audioSpecificConfig.Length)); //size
|
writer.WriteByte((byte)(20 + audioSpecificConfig.Length)); // size
|
||||||
writer.WriteByte((byte)((TrackId & 0xFF00) >> 8)); //ES_ID = track_id
|
writer.WriteByte((byte)((TrackId & 0xFF00) >> 8)); // ES_ID = track_id
|
||||||
writer.WriteByte((byte)(TrackId & 0x00FF));
|
writer.WriteByte((byte)(TrackId & 0x00FF));
|
||||||
writer.WriteByte(0); //flags and streamPriority
|
writer.WriteByte(0); // flags and streamPriority
|
||||||
|
|
||||||
// DecoderConfigDescriptor (see ISO/IEC 14496-1 (Systems))
|
// DecoderConfigDescriptor (see ISO/IEC 14496-1 (Systems))
|
||||||
writer.WriteByte(0x04); //tag = 0x04 (DecoderConfigDescrTag)
|
writer.WriteByte(0x04); // tag = 0x04 (DecoderConfigDescrTag)
|
||||||
writer.WriteByte((byte)(15 + audioSpecificConfig.Length)); //size
|
writer.WriteByte((byte)(15 + audioSpecificConfig.Length)); // size
|
||||||
writer.WriteByte(0x40); //objectTypeIndication = 0x40 (MPEG-4 AAC)
|
writer.WriteByte(0x40); // objectTypeIndication = 0x40 (MPEG-4 AAC)
|
||||||
writer.WriteByte((0x05 << 2) | (0 << 1) | 1); //reserved = 1
|
writer.WriteByte((0x05 << 2) | (0 << 1) | 1); // reserved = 1
|
||||||
writer.WriteByte(0xFF); //buffersizeDB = undefined
|
writer.WriteByte(0xFF); // buffersizeDB = undefined
|
||||||
writer.WriteByte(0xFF);
|
writer.WriteByte(0xFF);
|
||||||
writer.WriteByte(0xFF);
|
writer.WriteByte(0xFF);
|
||||||
|
|
||||||
var bandwidth = StreamSpec.Bandwidth!;
|
var bandwidth = StreamSpec.Bandwidth!;
|
||||||
writer.WriteByte((byte)((bandwidth & 0xFF000000) >> 24)); //maxBitrate
|
writer.WriteByte((byte)((bandwidth & 0xFF000000) >> 24)); // maxBitrate
|
||||||
writer.WriteByte((byte)((bandwidth & 0x00FF0000) >> 16));
|
writer.WriteByte((byte)((bandwidth & 0x00FF0000) >> 16));
|
||||||
writer.WriteByte((byte)((bandwidth & 0x0000FF00) >> 8));
|
writer.WriteByte((byte)((bandwidth & 0x0000FF00) >> 8));
|
||||||
writer.WriteByte((byte)(bandwidth & 0x000000FF));
|
writer.WriteByte((byte)(bandwidth & 0x000000FF));
|
||||||
writer.WriteByte((byte)((bandwidth & 0xFF000000) >> 24)); //avgbitrate
|
writer.WriteByte((byte)((bandwidth & 0xFF000000) >> 24)); // avgbitrate
|
||||||
writer.WriteByte((byte)((bandwidth & 0x00FF0000) >> 16));
|
writer.WriteByte((byte)((bandwidth & 0x00FF0000) >> 16));
|
||||||
writer.WriteByte((byte)((bandwidth & 0x0000FF00) >> 8));
|
writer.WriteByte((byte)((bandwidth & 0x0000FF00) >> 8));
|
||||||
writer.WriteByte((byte)(bandwidth & 0x000000FF));
|
writer.WriteByte((byte)(bandwidth & 0x000000FF));
|
||||||
|
|
||||||
// DecoderSpecificInfo (see ISO/IEC 14496-1 (Systems))
|
// DecoderSpecificInfo (see ISO/IEC 14496-1 (Systems))
|
||||||
writer.WriteByte(0x05); //tag = 0x05 (DecSpecificInfoTag)
|
writer.WriteByte(0x05); // tag = 0x05 (DecSpecificInfoTag)
|
||||||
writer.WriteByte((byte)audioSpecificConfig.Length); //size
|
writer.WriteByte((byte)audioSpecificConfig.Length); // size
|
||||||
writer.Write(audioSpecificConfig); //AudioSpecificConfig bytes
|
writer.Write(audioSpecificConfig); // AudioSpecificConfig bytes
|
||||||
|
|
||||||
return FullBox("esds", 0, 0, stream.ToArray());
|
return FullBox("esds", 0, 0, stream.ToArray());
|
||||||
}
|
}
|
||||||
|
@ -436,23 +436,23 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
using var writer = new BinaryWriter2(stream);
|
using var writer = new BinaryWriter2(stream);
|
||||||
|
|
||||||
writer.WriteByte(0); //reserved
|
writer.WriteByte(0); // reserved
|
||||||
writer.WriteByte(0);
|
writer.WriteByte(0);
|
||||||
writer.WriteByte(0);
|
writer.WriteByte(0);
|
||||||
writer.WriteByte(0);
|
writer.WriteByte(0);
|
||||||
writer.WriteByte(0);
|
writer.WriteByte(0);
|
||||||
writer.WriteByte(0);
|
writer.WriteByte(0);
|
||||||
writer.WriteUShort(1); //data reference index
|
writer.WriteUShort(1); // data reference index
|
||||||
|
|
||||||
if (StreamType == "audio")
|
if (StreamType == "audio")
|
||||||
{
|
{
|
||||||
writer.WriteUInt(0); //reserved2
|
writer.WriteUInt(0); // reserved2
|
||||||
writer.WriteUInt(0);
|
writer.WriteUInt(0);
|
||||||
writer.WriteUShort(Channels); //channels
|
writer.WriteUShort(Channels); // channels
|
||||||
writer.WriteUShort(BitsPerSample); //bits_per_sample
|
writer.WriteUShort(BitsPerSample); // bits_per_sample
|
||||||
writer.WriteUShort(0); //pre defined
|
writer.WriteUShort(0); // pre defined
|
||||||
writer.WriteUShort(0); //reserved3
|
writer.WriteUShort(0); // reserved3
|
||||||
writer.WriteUShort(SamplingRate, padding: 2); //sampling_rate
|
writer.WriteUShort(SamplingRate, padding: 2); // sampling_rate
|
||||||
|
|
||||||
var audioSpecificConfig = HexUtil.HexToBytes(CodecPrivateData);
|
var audioSpecificConfig = HexUtil.HexToBytes(CodecPrivateData);
|
||||||
var esdsBox = GenEsds(audioSpecificConfig);
|
var esdsBox = GenEsds(audioSpecificConfig);
|
||||||
|
@ -464,7 +464,7 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
{
|
{
|
||||||
var sinfBox = GenSinf("mp4a");
|
var sinfBox = GenSinf("mp4a");
|
||||||
writer.Write(sinfBox);
|
writer.Write(sinfBox);
|
||||||
return Box("enca", stream.ToArray()); //Encrypted Audio
|
return Box("enca", stream.ToArray()); // Encrypted Audio
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -477,7 +477,7 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
{
|
{
|
||||||
var sinfBox = GenSinf("ec-3");
|
var sinfBox = GenSinf("ec-3");
|
||||||
writer.Write(sinfBox);
|
writer.Write(sinfBox);
|
||||||
return Box("enca", stream.ToArray()); //Encrypted Audio
|
return Box("enca", stream.ToArray()); // Encrypted Audio
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -487,23 +487,23 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
}
|
}
|
||||||
else if (StreamType == "video")
|
else if (StreamType == "video")
|
||||||
{
|
{
|
||||||
writer.WriteUShort(0); //pre defined
|
writer.WriteUShort(0); // pre defined
|
||||||
writer.WriteUShort(0); //reserved
|
writer.WriteUShort(0); // reserved
|
||||||
writer.WriteUInt(0); //pre defined
|
writer.WriteUInt(0); // pre defined
|
||||||
writer.WriteUInt(0);
|
writer.WriteUInt(0);
|
||||||
writer.WriteUInt(0);
|
writer.WriteUInt(0);
|
||||||
writer.WriteUShort(Width); //width
|
writer.WriteUShort(Width); // width
|
||||||
writer.WriteUShort(Height); //height
|
writer.WriteUShort(Height); // height
|
||||||
writer.WriteUShort(0x48, padding: 2); //horiz resolution 72 dpi
|
writer.WriteUShort(0x48, padding: 2); // horiz resolution 72 dpi
|
||||||
writer.WriteUShort(0x48, padding: 2); //vert resolution 72 dpi
|
writer.WriteUShort(0x48, padding: 2); // vert resolution 72 dpi
|
||||||
writer.WriteUInt(0); //reserved
|
writer.WriteUInt(0); // reserved
|
||||||
writer.WriteUShort(1); //frame count
|
writer.WriteUShort(1); // frame count
|
||||||
for (int i = 0; i < 32; i++) //compressor name
|
for (int i = 0; i < 32; i++) // compressor name
|
||||||
{
|
{
|
||||||
writer.WriteByte(0);
|
writer.WriteByte(0);
|
||||||
}
|
}
|
||||||
writer.WriteUShort(0x18); //depth
|
writer.WriteUShort(0x18); // depth
|
||||||
writer.WriteUShort(65535); //pre defined
|
writer.WriteUShort(65535); // pre defined
|
||||||
|
|
||||||
var codecPrivateData = HexUtil.HexToBytes(CodecPrivateData);
|
var codecPrivateData = HexUtil.HexToBytes(CodecPrivateData);
|
||||||
|
|
||||||
|
@ -512,18 +512,18 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
var arr = CodecPrivateData.Split(new[] { StartCode }, StringSplitOptions.RemoveEmptyEntries);
|
var arr = CodecPrivateData.Split(new[] { StartCode }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 7).First());
|
var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 7).First());
|
||||||
var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 8).First());
|
var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 8).First());
|
||||||
//make avcC
|
// make avcC
|
||||||
var avcC = GetAvcC(sps, pps);
|
var avcC = GetAvcC(sps, pps);
|
||||||
writer.Write(avcC);
|
writer.Write(avcC);
|
||||||
if (IsProtection)
|
if (IsProtection)
|
||||||
{
|
{
|
||||||
var sinfBox = GenSinf("avc1");
|
var sinfBox = GenSinf("avc1");
|
||||||
writer.Write(sinfBox);
|
writer.Write(sinfBox);
|
||||||
return Box("encv", stream.ToArray()); //Encrypted Video
|
return Box("encv", stream.ToArray()); // Encrypted Video
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return Box("avc1", stream.ToArray()); //AVC Simple Entry
|
return Box("avc1", stream.ToArray()); // AVC Simple Entry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (FourCC == "HVC1" || FourCC == "HEV1")
|
else if (FourCC == "HVC1" || FourCC == "HEV1")
|
||||||
|
@ -532,18 +532,18 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
var vps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20).First());
|
var vps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20).First());
|
||||||
var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21).First());
|
var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21).First());
|
||||||
var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22).First());
|
var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22).First());
|
||||||
//make hvcC
|
// make hvcC
|
||||||
var hvcC = GetHvcC(sps, pps, vps);
|
var hvcC = GetHvcC(sps, pps, vps);
|
||||||
writer.Write(hvcC);
|
writer.Write(hvcC);
|
||||||
if (IsProtection)
|
if (IsProtection)
|
||||||
{
|
{
|
||||||
var sinfBox = GenSinf("hvc1");
|
var sinfBox = GenSinf("hvc1");
|
||||||
writer.Write(sinfBox);
|
writer.Write(sinfBox);
|
||||||
return Box("encv", stream.ToArray()); //Encrypted Video
|
return Box("encv", stream.ToArray()); // Encrypted Video
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return Box("hvc1", stream.ToArray()); //HEVC Simple Entry
|
return Box("hvc1", stream.ToArray()); // HEVC Simple Entry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 杜比视界也按照hevc处理
|
// 杜比视界也按照hevc处理
|
||||||
|
@ -553,18 +553,18 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
var vps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20).First());
|
var vps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20).First());
|
||||||
var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21).First());
|
var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21).First());
|
||||||
var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22).First());
|
var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22).First());
|
||||||
//make hvcC
|
// make hvcC
|
||||||
var hvcC = GetHvcC(sps, pps, vps, "dvh1");
|
var hvcC = GetHvcC(sps, pps, vps, "dvh1");
|
||||||
writer.Write(hvcC);
|
writer.Write(hvcC);
|
||||||
if (IsProtection)
|
if (IsProtection)
|
||||||
{
|
{
|
||||||
var sinfBox = GenSinf("dvh1");
|
var sinfBox = GenSinf("dvh1");
|
||||||
writer.Write(sinfBox);
|
writer.Write(sinfBox);
|
||||||
return Box("encv", stream.ToArray()); //Encrypted Video
|
return Box("encv", stream.ToArray()); // Encrypted Video
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return Box("dvh1", stream.ToArray()); //HEVC Simple Entry
|
return Box("dvh1", stream.ToArray()); // HEVC Simple Entry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -576,10 +576,10 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
{
|
{
|
||||||
if (FourCC == "TTML")
|
if (FourCC == "TTML")
|
||||||
{
|
{
|
||||||
writer.Write("http://www.w3.org/ns/ttml\0"); //namespace
|
writer.Write("http://www.w3.org/ns/ttml\0"); // namespace
|
||||||
writer.Write("\0"); //schema location
|
writer.Write("\0"); // schema location
|
||||||
writer.Write("\0"); //auxilary mime types(??)
|
writer.Write("\0"); // auxilary mime types(??)
|
||||||
return Box("stpp", stream.ToArray()); //TTML Simple Entry
|
return Box("stpp", stream.ToArray()); // TTML Simple Entry
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -599,26 +599,26 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
using var writer = new BinaryWriter2(stream);
|
using var writer = new BinaryWriter2(stream);
|
||||||
|
|
||||||
writer.WriteByte(1); //configuration version
|
writer.WriteByte(1); // configuration version
|
||||||
writer.Write(sps[1..4]); //avc profile indication + profile compatibility + avc level indication
|
writer.Write(sps[1..4]); // avc profile indication + profile compatibility + avc level indication
|
||||||
writer.WriteByte((byte)(0xfc | (NalUnitLengthField - 1))); //complete representation (1) + reserved (11111) + length size minus one
|
writer.WriteByte((byte)(0xfc | (NalUnitLengthField - 1))); // complete representation (1) + reserved (11111) + length size minus one
|
||||||
writer.WriteByte(1); //reserved (0) + number of sps (0000001)
|
writer.WriteByte(1); // reserved (0) + number of sps (0000001)
|
||||||
writer.WriteUShort(sps.Length);
|
writer.WriteUShort(sps.Length);
|
||||||
writer.Write(sps);
|
writer.Write(sps);
|
||||||
writer.WriteByte(1); //number of pps
|
writer.WriteByte(1); // number of pps
|
||||||
writer.WriteUShort(pps.Length);
|
writer.WriteUShort(pps.Length);
|
||||||
writer.Write(pps);
|
writer.Write(pps);
|
||||||
|
|
||||||
return Box("avcC", stream.ToArray()); //AVC Decoder Configuration Record
|
return Box("avcC", stream.ToArray()); // AVC Decoder Configuration Record
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] GetHvcC(byte[] sps, byte[] pps, byte[] vps, string code = "hvc1")
|
private byte[] GetHvcC(byte[] sps, byte[] pps, byte[] vps, string code = "hvc1")
|
||||||
{
|
{
|
||||||
var oriSps = new List<byte>(sps);
|
var oriSps = new List<byte>(sps);
|
||||||
//https://www.itu.int/rec/dologin.asp?lang=f&id=T-REC-H.265-201504-S!!PDF-E&type=items
|
// https://www.itu.int/rec/dologin.asp?lang=f&id=T-REC-H.265-201504-S!!PDF-E&type=items
|
||||||
//Read generalProfileSpace, generalTierFlag, generalProfileIdc,
|
// Read generalProfileSpace, generalTierFlag, generalProfileIdc,
|
||||||
//generalProfileCompatibilityFlags, constraintBytes, generalLevelIdc
|
// generalProfileCompatibilityFlags, constraintBytes, generalLevelIdc
|
||||||
//from sps
|
// from sps
|
||||||
var encList = new List<byte>();
|
var encList = new List<byte>();
|
||||||
/**
|
/**
|
||||||
* 处理payload, 有00 00 03 0,1,2,3的情况 统一换成00 00 XX 即丢弃03
|
* 处理payload, 有00 00 03 0,1,2,3的情况 统一换成00 00 XX 即丢弃03
|
||||||
|
@ -644,7 +644,7 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
sps = encList.ToArray();
|
sps = encList.ToArray();
|
||||||
|
|
||||||
using var reader = new BinaryReader2(new MemoryStream(sps));
|
using var reader = new BinaryReader2(new MemoryStream(sps));
|
||||||
reader.ReadBytes(2); //Skip 2 bytes unit header
|
reader.ReadBytes(2); // Skip 2 bytes unit header
|
||||||
var firstByte = reader.ReadByte();
|
var firstByte = reader.ReadByte();
|
||||||
var maxSubLayersMinus1 = (firstByte & 0xe) >> 1;
|
var maxSubLayersMinus1 = (firstByte & 0xe) >> 1;
|
||||||
var nextByte = reader.ReadByte();
|
var nextByte = reader.ReadByte();
|
||||||
|
@ -658,21 +658,21 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
/*var skipBit = 0;
|
/*var skipBit = 0;
|
||||||
for (int i = 0; i < maxSubLayersMinus1; i++)
|
for (int i = 0; i < maxSubLayersMinus1; i++)
|
||||||
{
|
{
|
||||||
skipBit += 2; //sub_layer_profile_present_flag sub_layer_level_present_flag
|
skipBit += 2; // sub_layer_profile_present_flag sub_layer_level_present_flag
|
||||||
}
|
}
|
||||||
if (maxSubLayersMinus1 > 0)
|
if (maxSubLayersMinus1 > 0)
|
||||||
{
|
{
|
||||||
for (int i = maxSubLayersMinus1; i < 8; i++)
|
for (int i = maxSubLayersMinus1; i < 8; i++)
|
||||||
{
|
{
|
||||||
skipBit += 2; //reserved_zero_2bits
|
skipBit += 2; // reserved_zero_2bits
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < maxSubLayersMinus1; i++)
|
for (int i = 0; i < maxSubLayersMinus1; i++)
|
||||||
{
|
{
|
||||||
skipBit += 2; //sub_layer_profile_present_flag sub_layer_level_present_flag
|
skipBit += 2; // sub_layer_profile_present_flag sub_layer_level_present_flag
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
//生成编码信息
|
// 生成编码信息
|
||||||
var codecs = code +
|
var codecs = code +
|
||||||
$".{HEVC_GENERAL_PROFILE_SPACE_STRINGS[generalProfileSpace]}{generalProfileIdc}" +
|
$".{HEVC_GENERAL_PROFILE_SPACE_STRINGS[generalProfileSpace]}{generalProfileIdc}" +
|
||||||
$".{Convert.ToString(generalProfileCompatibilityFlags, 16)}" +
|
$".{Convert.ToString(generalProfileCompatibilityFlags, 16)}" +
|
||||||
|
@ -687,37 +687,37 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
using var writer = new BinaryWriter2(stream);
|
using var writer = new BinaryWriter2(stream);
|
||||||
|
|
||||||
//var reserved1 = 0xF;
|
// var reserved1 = 0xF;
|
||||||
|
|
||||||
writer.WriteByte(1); //configuration version
|
writer.WriteByte(1); // configuration version
|
||||||
writer.WriteByte((byte)((generalProfileSpace << 6) + (generalTierFlag == 1 ? 0x20 : 0) | generalProfileIdc)); //general_profile_space + general_tier_flag + general_profile_idc
|
writer.WriteByte((byte)((generalProfileSpace << 6) + (generalTierFlag == 1 ? 0x20 : 0) | generalProfileIdc)); // general_profile_space + general_tier_flag + general_profile_idc
|
||||||
writer.WriteUInt(generalProfileCompatibilityFlags); //general_profile_compatibility_flags
|
writer.WriteUInt(generalProfileCompatibilityFlags); // general_profile_compatibility_flags
|
||||||
writer.Write(constraintBytes); //general_constraint_indicator_flags
|
writer.Write(constraintBytes); // general_constraint_indicator_flags
|
||||||
writer.WriteByte((byte)generalProfileIdc); //general_level_idc
|
writer.WriteByte((byte)generalProfileIdc); // general_level_idc
|
||||||
writer.WriteUShort(0xf000); //reserved + min_spatial_segmentation_idc
|
writer.WriteUShort(0xf000); // reserved + min_spatial_segmentation_idc
|
||||||
writer.WriteByte(0xfc); //reserved + parallelismType
|
writer.WriteByte(0xfc); // reserved + parallelismType
|
||||||
writer.WriteByte(0 | 0xfc); //reserved + chromaFormat
|
writer.WriteByte(0 | 0xfc); // reserved + chromaFormat
|
||||||
writer.WriteByte(0 | 0xf8); //reserved + bitDepthLumaMinus8
|
writer.WriteByte(0 | 0xf8); // reserved + bitDepthLumaMinus8
|
||||||
writer.WriteByte(0 | 0xf8); //reserved + bitDepthChromaMinus8
|
writer.WriteByte(0 | 0xf8); // reserved + bitDepthChromaMinus8
|
||||||
writer.WriteUShort(0); //avgFrameRate
|
writer.WriteUShort(0); // avgFrameRate
|
||||||
writer.WriteByte((byte)(0 << 6 | 0 << 3 | 0 << 2 | (NalUnitLengthField - 1))); //constantFrameRate + numTemporalLayers + temporalIdNested + lengthSizeMinusOne
|
writer.WriteByte((byte)(0 << 6 | 0 << 3 | 0 << 2 | (NalUnitLengthField - 1))); // constantFrameRate + numTemporalLayers + temporalIdNested + lengthSizeMinusOne
|
||||||
writer.WriteByte(0x03); //numOfArrays (vps sps pps)
|
writer.WriteByte(0x03); // numOfArrays (vps sps pps)
|
||||||
|
|
||||||
sps = oriSps.ToArray();
|
sps = oriSps.ToArray();
|
||||||
writer.WriteByte(0x20); //array_completeness + reserved + NAL_unit_type
|
writer.WriteByte(0x20); // array_completeness + reserved + NAL_unit_type
|
||||||
writer.WriteUShort(1); //numNalus
|
writer.WriteUShort(1); // numNalus
|
||||||
writer.WriteUShort(vps.Length);
|
writer.WriteUShort(vps.Length);
|
||||||
writer.Write(vps);
|
writer.Write(vps);
|
||||||
writer.WriteByte(0x21);
|
writer.WriteByte(0x21);
|
||||||
writer.WriteUShort(1); //numNalus
|
writer.WriteUShort(1); // numNalus
|
||||||
writer.WriteUShort(sps.Length);
|
writer.WriteUShort(sps.Length);
|
||||||
writer.Write(sps);
|
writer.Write(sps);
|
||||||
writer.WriteByte(0x22);
|
writer.WriteByte(0x22);
|
||||||
writer.WriteUShort(1); //numNalus
|
writer.WriteUShort(1); // numNalus
|
||||||
writer.WriteUShort(pps.Length);
|
writer.WriteUShort(pps.Length);
|
||||||
writer.Write(pps);
|
writer.Write(pps);
|
||||||
|
|
||||||
return Box("hvcC", stream.ToArray()); //HEVC Decoder Configuration Record
|
return Box("hvcC", stream.ToArray()); // HEVC Decoder Configuration Record
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] GetStsd()
|
private byte[] GetStsd()
|
||||||
|
@ -725,7 +725,7 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
using var writer = new BinaryWriter2(stream);
|
using var writer = new BinaryWriter2(stream);
|
||||||
|
|
||||||
writer.WriteUInt(1); //entry count
|
writer.WriteUInt(1); // entry count
|
||||||
var sampleEntryData = GetSampleEntryBox();
|
var sampleEntryData = GetSampleEntryBox();
|
||||||
writer.Write(sampleEntryData);
|
writer.Write(sampleEntryData);
|
||||||
|
|
||||||
|
@ -739,20 +739,20 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
|
|
||||||
writer.WriteULong(Duration);
|
writer.WriteULong(Duration);
|
||||||
|
|
||||||
return FullBox("mehd", 1, 0, stream.ToArray()); //Movie Extends Header Box
|
return FullBox("mehd", 1, 0, stream.ToArray()); // Movie Extends Header Box
|
||||||
}
|
}
|
||||||
private byte[] GetTrex()
|
private byte[] GetTrex()
|
||||||
{
|
{
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
using var writer = new BinaryWriter2(stream);
|
using var writer = new BinaryWriter2(stream);
|
||||||
|
|
||||||
writer.WriteUInt(TrackId); //track id
|
writer.WriteUInt(TrackId); // track id
|
||||||
writer.WriteUInt(1); //default sample description index
|
writer.WriteUInt(1); // default sample description index
|
||||||
writer.WriteUInt(0); //default sample duration
|
writer.WriteUInt(0); // default sample duration
|
||||||
writer.WriteUInt(0); //default sample size
|
writer.WriteUInt(0); // default sample size
|
||||||
writer.WriteUInt(0); //default sample flags
|
writer.WriteUInt(0); // default sample flags
|
||||||
|
|
||||||
return FullBox("trex", 0, 0, stream.ToArray()); //Track Extends Box
|
return FullBox("trex", 0, 0, stream.ToArray()); // Track Extends Box
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] GenPsshBoxForPlayReady()
|
private byte[] GenPsshBoxForPlayReady()
|
||||||
|
@ -763,8 +763,8 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
var psshData = HexUtil.HexToBytes(ProtectionData);
|
var psshData = HexUtil.HexToBytes(ProtectionData);
|
||||||
|
|
||||||
_writer.Write(sysIdData); // SystemID 16 bytes
|
_writer.Write(sysIdData); // SystemID 16 bytes
|
||||||
_writer.WriteUInt(psshData.Length); //Size of Data 4 bytes
|
_writer.WriteUInt(psshData.Length); // Size of Data 4 bytes
|
||||||
_writer.Write(psshData); //Data
|
_writer.Write(psshData); // Data
|
||||||
var psshBox = FullBox("pssh", 0, 0, _stream.ToArray());
|
var psshBox = FullBox("pssh", 0, 0, _stream.ToArray());
|
||||||
return psshBox;
|
return psshBox;
|
||||||
}
|
}
|
||||||
|
@ -774,12 +774,12 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
using var _stream = new MemoryStream();
|
using var _stream = new MemoryStream();
|
||||||
using var _writer = new BinaryWriter2(_stream);
|
using var _writer = new BinaryWriter2(_stream);
|
||||||
var sysIdData = HexUtil.HexToBytes("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed".Replace("-", ""));
|
var sysIdData = HexUtil.HexToBytes("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed".Replace("-", ""));
|
||||||
//var kid = HexUtil.HexToBytes(ProtecitonKID);
|
// var kid = HexUtil.HexToBytes(ProtecitonKID);
|
||||||
|
|
||||||
_writer.Write(sysIdData); // SystemID 16 bytes
|
_writer.Write(sysIdData); // SystemID 16 bytes
|
||||||
var psshData = HexUtil.HexToBytes($"08011210{ProtecitonKID}1A046E647265220400000000");
|
var psshData = HexUtil.HexToBytes($"08011210{ProtecitonKID}1A046E647265220400000000");
|
||||||
_writer.WriteUInt(psshData.Length); //Size of Data 4 bytes
|
_writer.WriteUInt(psshData.Length); // Size of Data 4 bytes
|
||||||
_writer.Write(psshData); //Data
|
_writer.Write(psshData); // Data
|
||||||
var psshBox = FullBox("pssh", 0, 0, _stream.ToArray());
|
var psshBox = FullBox("pssh", 0, 0, _stream.ToArray());
|
||||||
return psshBox;
|
return psshBox;
|
||||||
}
|
}
|
||||||
|
@ -789,13 +789,13 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
using var writer = new BinaryWriter2(stream);
|
using var writer = new BinaryWriter2(stream);
|
||||||
|
|
||||||
//make senc
|
// make senc
|
||||||
writer.WriteUInt(1); //sample_count
|
writer.WriteUInt(1); // sample_count
|
||||||
writer.Write(new byte[8]); //8 bytes IV
|
writer.Write(new byte[8]); // 8 bytes IV
|
||||||
|
|
||||||
var sencBox = FullBox("senc", 1, 0, stream.ToArray());
|
var sencBox = FullBox("senc", 1, 0, stream.ToArray());
|
||||||
|
|
||||||
var moofBox = Box("moof", sencBox); //Movie Extends Box
|
var moofBox = Box("moof", sencBox); // Movie Extends Box
|
||||||
|
|
||||||
return moofBox;
|
return moofBox;
|
||||||
}
|
}
|
||||||
|
@ -834,41 +834,41 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
var minfPayload = GenMinf();
|
var minfPayload = GenMinf();
|
||||||
|
|
||||||
|
|
||||||
var sttsPayload = new byte[] { 0, 0, 0, 0 }; //entry count
|
var sttsPayload = new byte[] { 0, 0, 0, 0 }; // entry count
|
||||||
var stblPayload = FullBox("stts", 0, 0, sttsPayload); //Decoding Time to Sample Box
|
var stblPayload = FullBox("stts", 0, 0, sttsPayload); // Decoding Time to Sample Box
|
||||||
|
|
||||||
var stscPayload = new byte[] { 0, 0, 0, 0 }; //entry count
|
var stscPayload = new byte[] { 0, 0, 0, 0 }; // entry count
|
||||||
var stscBox = FullBox("stsc", 0, 0, stscPayload); //Sample To Chunk Box
|
var stscBox = FullBox("stsc", 0, 0, stscPayload); // Sample To Chunk Box
|
||||||
|
|
||||||
var stcoPayload = new byte[] { 0, 0, 0, 0 }; //entry count
|
var stcoPayload = new byte[] { 0, 0, 0, 0 }; // entry count
|
||||||
var stcoBox = FullBox("stco", 0, 0, stcoPayload); //Chunk Offset Box
|
var stcoBox = FullBox("stco", 0, 0, stcoPayload); // Chunk Offset Box
|
||||||
|
|
||||||
var stszPayload = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; //sample size, sample count
|
var stszPayload = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; // sample size, sample count
|
||||||
var stszBox = FullBox("stsz", 0, 0, stszPayload); //Sample Size Box
|
var stszBox = FullBox("stsz", 0, 0, stszPayload); // Sample Size Box
|
||||||
|
|
||||||
var stsdPayload = GetStsd();
|
var stsdPayload = GetStsd();
|
||||||
var stsdBox = FullBox("stsd", 0, 0, stsdPayload); //Sample Description Box
|
var stsdBox = FullBox("stsd", 0, 0, stsdPayload); // Sample Description Box
|
||||||
|
|
||||||
stblPayload = stblPayload.Concat(stscBox).Concat(stcoBox).Concat(stszBox).Concat(stsdBox).ToArray();
|
stblPayload = stblPayload.Concat(stscBox).Concat(stcoBox).Concat(stszBox).Concat(stsdBox).ToArray();
|
||||||
|
|
||||||
|
|
||||||
var stblBox = Box("stbl", stblPayload); //Sample Table Box
|
var stblBox = Box("stbl", stblPayload); // Sample Table Box
|
||||||
minfPayload = minfPayload.Concat(stblBox).ToArray();
|
minfPayload = minfPayload.Concat(stblBox).ToArray();
|
||||||
|
|
||||||
var minfBox = Box("minf", minfPayload); //Media Information Box
|
var minfBox = Box("minf", minfPayload); // Media Information Box
|
||||||
mdiaPayload = mdiaPayload.Concat(minfBox).ToArray();
|
mdiaPayload = mdiaPayload.Concat(minfBox).ToArray();
|
||||||
|
|
||||||
var mdiaBox = Box("mdia", mdiaPayload); //Media Box
|
var mdiaBox = Box("mdia", mdiaPayload); // Media Box
|
||||||
trakPayload = trakPayload.Concat(mdiaBox).ToArray();
|
trakPayload = trakPayload.Concat(mdiaBox).ToArray();
|
||||||
|
|
||||||
var trakBox = Box("trak", trakPayload); //Track Box
|
var trakBox = Box("trak", trakPayload); // Track Box
|
||||||
moovPayload = moovPayload.Concat(trakBox).ToArray();
|
moovPayload = moovPayload.Concat(trakBox).ToArray();
|
||||||
|
|
||||||
var mvexPayload = GetMehd();
|
var mvexPayload = GetMehd();
|
||||||
var trexBox = GetTrex();
|
var trexBox = GetTrex();
|
||||||
mvexPayload = mvexPayload.Concat(trexBox).ToArray();
|
mvexPayload = mvexPayload.Concat(trexBox).ToArray();
|
||||||
|
|
||||||
var mvexBox = Box("mvex", mvexPayload); //Movie Extends Box
|
var mvexBox = Box("mvex", mvexPayload); // Movie Extends Box
|
||||||
moovPayload = moovPayload.Concat(mvexBox).ToArray();
|
moovPayload = moovPayload.Concat(mvexBox).ToArray();
|
||||||
|
|
||||||
if (IsProtection)
|
if (IsProtection)
|
||||||
|
@ -878,14 +878,13 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
||||||
moovPayload = moovPayload.Concat(psshBox1).Concat(psshBox2).ToArray();
|
moovPayload = moovPayload.Concat(psshBox1).Concat(psshBox2).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var moovBox = Box("moov", moovPayload); //Movie Box
|
var moovBox = Box("moov", moovPayload); // Movie Box
|
||||||
|
|
||||||
stream.Write(moovBox);
|
stream.Write(moovBox);
|
||||||
|
|
||||||
//var moofBox = GenMoof(); //Movie Extends Box
|
// var moofBox = GenMoof(); // Movie Extends Box
|
||||||
//stream.Write(moofBox);
|
// stream.Write(moofBox);
|
||||||
|
|
||||||
return stream.ToArray();
|
return stream.ToArray();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -6,11 +6,10 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor
|
namespace N_m3u8DL_RE.Parser.Processor;
|
||||||
|
|
||||||
|
public abstract class ContentProcessor
|
||||||
{
|
{
|
||||||
public abstract class ContentProcessor
|
|
||||||
{
|
|
||||||
public abstract bool CanProcess(ExtractorType extractorType, string rawText, ParserConfig parserConfig);
|
public abstract bool CanProcess(ExtractorType extractorType, string rawText, ParserConfig parserConfig);
|
||||||
public abstract string Process(string rawText, ParserConfig parserConfig);
|
public abstract string Process(string rawText, ParserConfig parserConfig);
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -7,13 +7,13 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor.DASH
|
namespace N_m3u8DL_RE.Parser.Processor.DASH;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// XG视频处理
|
||||||
|
/// </summary>
|
||||||
|
public class DefaultDASHContentProcessor : ContentProcessor
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 西瓜视频处理
|
|
||||||
/// </summary>
|
|
||||||
public class DefaultDASHContentProcessor : ContentProcessor
|
|
||||||
{
|
|
||||||
public override bool CanProcess(ExtractorType extractorType, string mpdContent, ParserConfig parserConfig)
|
public override bool CanProcess(ExtractorType extractorType, string mpdContent, ParserConfig parserConfig)
|
||||||
{
|
{
|
||||||
if (extractorType != ExtractorType.MPEG_DASH) return false;
|
if (extractorType != ExtractorType.MPEG_DASH) return false;
|
||||||
|
@ -32,5 +32,4 @@ namespace N_m3u8DL_RE.Parser.Processor.DASH
|
||||||
|
|
||||||
return mpdContent;
|
return mpdContent;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -9,10 +9,10 @@ using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor
|
namespace N_m3u8DL_RE.Parser.Processor;
|
||||||
|
|
||||||
|
public class DefaultUrlProcessor : UrlProcessor
|
||||||
{
|
{
|
||||||
public class DefaultUrlProcessor : UrlProcessor
|
|
||||||
{
|
|
||||||
public override bool CanProcess(ExtractorType extractorType, string oriUrl, ParserConfig paserConfig) => paserConfig.AppendUrlParams;
|
public override bool CanProcess(ExtractorType extractorType, string oriUrl, ParserConfig paserConfig) => paserConfig.AppendUrlParams;
|
||||||
|
|
||||||
public override string Process(string oriUrl, ParserConfig paserConfig)
|
public override string Process(string oriUrl, ParserConfig paserConfig)
|
||||||
|
@ -42,5 +42,4 @@ namespace N_m3u8DL_RE.Parser.Processor
|
||||||
|
|
||||||
return oriUrl;
|
return oriUrl;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -8,10 +8,10 @@ using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor.HLS
|
namespace N_m3u8DL_RE.Parser.Processor.HLS;
|
||||||
|
|
||||||
|
public partial class DefaultHLSContentProcessor : ContentProcessor
|
||||||
{
|
{
|
||||||
public partial class DefaultHLSContentProcessor : ContentProcessor
|
|
||||||
{
|
|
||||||
[GeneratedRegex("#EXT-X-DISCONTINUITY\\s+#EXT-X-MAP:URI=\\\"(.*?)\\\",BYTERANGE=\\\"(.*?)\\\"")]
|
[GeneratedRegex("#EXT-X-DISCONTINUITY\\s+#EXT-X-MAP:URI=\\\"(.*?)\\\",BYTERANGE=\\\"(.*?)\\\"")]
|
||||||
private static partial Regex YkDVRegex();
|
private static partial Regex YkDVRegex();
|
||||||
[GeneratedRegex("#EXT-X-MAP:URI=\\\".*?BUMPER/[\\s\\S]+?#EXT-X-DISCONTINUITY")]
|
[GeneratedRegex("#EXT-X-MAP:URI=\\\".*?BUMPER/[\\s\\S]+?#EXT-X-DISCONTINUITY")]
|
||||||
|
@ -29,32 +29,26 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
||||||
|
|
||||||
public override string Process(string m3u8Content, ParserConfig parserConfig)
|
public override string Process(string m3u8Content, ParserConfig parserConfig)
|
||||||
{
|
{
|
||||||
//处理content以\r作为换行符的情况
|
// 处理content以\r作为换行符的情况
|
||||||
if (m3u8Content.Contains("\r") && !m3u8Content.Contains("\n"))
|
if (m3u8Content.Contains("\r") && !m3u8Content.Contains("\n"))
|
||||||
{
|
{
|
||||||
m3u8Content = m3u8Content.Replace("\r", Environment.NewLine);
|
m3u8Content = m3u8Content.Replace("\r", Environment.NewLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
var m3u8Url = parserConfig.Url;
|
var m3u8Url = parserConfig.Url;
|
||||||
//央视频回放
|
// YSP回放
|
||||||
if (m3u8Url.Contains("tlivecloud-playback-cdn.ysp.cctv.cn") && m3u8Url.Contains("endtime="))
|
if (m3u8Url.Contains("tlivecloud-playback-cdn.ysp.cctv.cn") && m3u8Url.Contains("endtime="))
|
||||||
{
|
{
|
||||||
m3u8Content += Environment.NewLine + HLSTags.ext_x_endlist;
|
m3u8Content += Environment.NewLine + HLSTags.ext_x_endlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
//IMOOC
|
// IMOOC
|
||||||
if (m3u8Url.Contains("imooc.com/"))
|
if (m3u8Url.Contains("imooc.com/"))
|
||||||
{
|
{
|
||||||
//M3u8Content = DecodeImooc.DecodeM3u8(M3u8Content);
|
// M3u8Content = DecodeImooc.DecodeM3u8(M3u8Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
//iqy
|
// 针对YK #EXT-X-VERSION:7杜比视界片源修正
|
||||||
if (m3u8Content.StartsWith("{\"payload\""))
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
//针对优酷#EXT-X-VERSION:7杜比视界片源修正
|
|
||||||
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && m3u8Content.Contains("ott.cibntv.net") && m3u8Content.Contains("ccode="))
|
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && m3u8Content.Contains("ott.cibntv.net") && m3u8Content.Contains("ccode="))
|
||||||
{
|
{
|
||||||
Regex ykmap = YkDVRegex();
|
Regex ykmap = YkDVRegex();
|
||||||
|
@ -64,7 +58,7 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//针对Disney+修正
|
// 针对Disney+修正
|
||||||
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && m3u8Url.Contains("media.dssott.com/"))
|
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && m3u8Url.Contains("media.dssott.com/"))
|
||||||
{
|
{
|
||||||
Regex ykmap = DNSPRegex();
|
Regex ykmap = DNSPRegex();
|
||||||
|
@ -74,7 +68,7 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//针对Disney+字幕修正
|
// 针对Disney+字幕修正
|
||||||
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("seg_00000.vtt") && m3u8Url.Contains("media.dssott.com/"))
|
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("seg_00000.vtt") && m3u8Url.Contains("media.dssott.com/"))
|
||||||
{
|
{
|
||||||
Regex ykmap = DNSPSubRegex();
|
Regex ykmap = DNSPSubRegex();
|
||||||
|
@ -84,10 +78,10 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//针对AppleTv修正
|
// 针对AppleTv修正
|
||||||
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && (m3u8Url.Contains(".apple.com/") || ATVRegex().IsMatch(m3u8Content)))
|
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && (m3u8Url.Contains(".apple.com/") || ATVRegex().IsMatch(m3u8Content)))
|
||||||
{
|
{
|
||||||
//只取加密部分即可
|
// 只取加密部分即可
|
||||||
Regex ykmap = ATVRegex2();
|
Regex ykmap = ATVRegex2();
|
||||||
if (ykmap.IsMatch(m3u8Content))
|
if (ykmap.IsMatch(m3u8Content))
|
||||||
{
|
{
|
||||||
|
@ -95,7 +89,7 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//修复#EXT-X-KEY与#EXTINF出现次序异常问题
|
// 修复#EXT-X-KEY与#EXTINF出现次序异常问题
|
||||||
var regex = OrderFixRegex();
|
var regex = OrderFixRegex();
|
||||||
if (regex.IsMatch(m3u8Content))
|
if (regex.IsMatch(m3u8Content))
|
||||||
{
|
{
|
||||||
|
@ -104,5 +98,4 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
||||||
|
|
||||||
return m3u8Content;
|
return m3u8Content;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -6,16 +6,11 @@ using N_m3u8DL_RE.Common.Util;
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Parser.Util;
|
using N_m3u8DL_RE.Parser.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor.HLS
|
namespace N_m3u8DL_RE.Parser.Processor.HLS;
|
||||||
|
|
||||||
|
public class DefaultHLSKeyProcessor : KeyProcessor
|
||||||
{
|
{
|
||||||
public class DefaultHLSKeyProcessor : KeyProcessor
|
|
||||||
{
|
|
||||||
public override bool CanProcess(ExtractorType extractorType, string m3u8Url, string keyLine, string m3u8Content, ParserConfig paserConfig) => extractorType == ExtractorType.HLS;
|
public override bool CanProcess(ExtractorType extractorType, string m3u8Url, string keyLine, string m3u8Content, ParserConfig paserConfig) => extractorType == ExtractorType.HLS;
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,21 +24,21 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
||||||
|
|
||||||
var encryptInfo = new EncryptInfo(method);
|
var encryptInfo = new EncryptInfo(method);
|
||||||
|
|
||||||
//IV
|
// IV
|
||||||
if (!string.IsNullOrEmpty(iv))
|
if (!string.IsNullOrEmpty(iv))
|
||||||
{
|
{
|
||||||
encryptInfo.IV = HexUtil.HexToBytes(iv);
|
encryptInfo.IV = HexUtil.HexToBytes(iv);
|
||||||
}
|
}
|
||||||
//自定义IV
|
// 自定义IV
|
||||||
if (parserConfig.CustomeIV != null && parserConfig.CustomeIV.Length > 0)
|
if (parserConfig.CustomeIV is { Length: > 0 })
|
||||||
{
|
{
|
||||||
encryptInfo.IV = parserConfig.CustomeIV;
|
encryptInfo.IV = parserConfig.CustomeIV;
|
||||||
}
|
}
|
||||||
|
|
||||||
//KEY
|
// KEY
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (parserConfig.CustomeKey != null && parserConfig.CustomeKey.Length > 0)
|
if (parserConfig.CustomeKey is { Length: > 0 })
|
||||||
{
|
{
|
||||||
encryptInfo.Key = parserConfig.CustomeKey;
|
encryptInfo.Key = parserConfig.CustomeKey;
|
||||||
}
|
}
|
||||||
|
@ -78,7 +73,7 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
||||||
Logger.WarnMarkUp($"[grey]{_ex.Message.EscapeMarkup()} retryCount: {retryCount}[/]");
|
Logger.WarnMarkUp($"[grey]{_ex.Message.EscapeMarkup()} retryCount: {retryCount}[/]");
|
||||||
Thread.Sleep(1000);
|
Thread.Sleep(1000);
|
||||||
if (retryCount-- > 0) goto getHttpKey;
|
if (retryCount-- > 0) goto getHttpKey;
|
||||||
else throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +83,7 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
||||||
encryptInfo.Method = EncryptMethod.UNKNOWN;
|
encryptInfo.Method = EncryptMethod.UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
//处理自定义加密方式
|
// 处理自定义加密方式
|
||||||
if (parserConfig.CustomMethod != null)
|
if (parserConfig.CustomMethod != null)
|
||||||
{
|
{
|
||||||
encryptInfo.Method = parserConfig.CustomMethod.Value;
|
encryptInfo.Method = parserConfig.CustomMethod.Value;
|
||||||
|
@ -113,5 +108,4 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -7,11 +7,10 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor
|
namespace N_m3u8DL_RE.Parser.Processor;
|
||||||
|
|
||||||
|
public abstract class KeyProcessor
|
||||||
{
|
{
|
||||||
public abstract class KeyProcessor
|
|
||||||
{
|
|
||||||
public abstract bool CanProcess(ExtractorType extractorType, string keyLine, string m3u8Url, string m3u8Content, ParserConfig parserConfig);
|
public abstract bool CanProcess(ExtractorType extractorType, string keyLine, string m3u8Url, string m3u8Content, ParserConfig parserConfig);
|
||||||
public abstract EncryptInfo Process(string keyLine, string m3u8Url, string m3u8Content, ParserConfig parserConfig);
|
public abstract EncryptInfo Process(string keyLine, string m3u8Url, string m3u8Content, ParserConfig parserConfig);
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,16 +1,10 @@
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor
|
namespace N_m3u8DL_RE.Parser.Processor;
|
||||||
|
|
||||||
|
public abstract class UrlProcessor
|
||||||
{
|
{
|
||||||
public abstract class UrlProcessor
|
|
||||||
{
|
|
||||||
public abstract bool CanProcess(ExtractorType extractorType, string oriUrl, ParserConfig parserConfig);
|
public abstract bool CanProcess(ExtractorType extractorType, string oriUrl, ParserConfig parserConfig);
|
||||||
public abstract string Process(string oriUrl, ParserConfig parserConfig);
|
public abstract string Process(string oriUrl, ParserConfig parserConfig);
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -6,19 +6,18 @@ using N_m3u8DL_RE.Parser.Constants;
|
||||||
using N_m3u8DL_RE.Parser.Extractor;
|
using N_m3u8DL_RE.Parser.Extractor;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using Spectre.Console;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser
|
namespace N_m3u8DL_RE.Parser;
|
||||||
|
|
||||||
|
public class StreamExtractor
|
||||||
{
|
{
|
||||||
public class StreamExtractor
|
public ExtractorType ExtractorType => extractor.ExtractorType;
|
||||||
{
|
|
||||||
public ExtractorType ExtractorType { get => extractor.ExtractorType; }
|
|
||||||
private IExtractor extractor;
|
private IExtractor extractor;
|
||||||
private ParserConfig parserConfig = new ParserConfig();
|
private ParserConfig parserConfig = new();
|
||||||
private string rawText;
|
private string rawText;
|
||||||
private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
|
private static SemaphoreSlim semaphore = new(1, 1);
|
||||||
|
|
||||||
public Dictionary<string, string> RawFiles { get; set; } = new(); //存储(文件名,文件内容)
|
public Dictionary<string, string> RawFiles { get; set; } = new(); // 存储(文件名,文件内容)
|
||||||
|
|
||||||
public StreamExtractor()
|
public StreamExtractor()
|
||||||
{
|
{
|
||||||
|
@ -69,14 +68,14 @@ namespace N_m3u8DL_RE.Parser
|
||||||
else if (rawText.Contains("</MPD>") && rawText.Contains("<MPD"))
|
else if (rawText.Contains("</MPD>") && rawText.Contains("<MPD"))
|
||||||
{
|
{
|
||||||
Logger.InfoMarkUp(ResString.matchDASH);
|
Logger.InfoMarkUp(ResString.matchDASH);
|
||||||
//extractor = new DASHExtractor(parserConfig);
|
// extractor = new DASHExtractor(parserConfig);
|
||||||
extractor = new DASHExtractor2(parserConfig);
|
extractor = new DASHExtractor2(parserConfig);
|
||||||
rawType = "mpd";
|
rawType = "mpd";
|
||||||
}
|
}
|
||||||
else if (rawText.Contains("</SmoothStreamingMedia>") && rawText.Contains("<SmoothStreamingMedia"))
|
else if (rawText.Contains("</SmoothStreamingMedia>") && rawText.Contains("<SmoothStreamingMedia"))
|
||||||
{
|
{
|
||||||
Logger.InfoMarkUp(ResString.matchMSS);
|
Logger.InfoMarkUp(ResString.matchMSS);
|
||||||
//extractor = new DASHExtractor(parserConfig);
|
// extractor = new DASHExtractor(parserConfig);
|
||||||
extractor = new MSSExtractor(parserConfig);
|
extractor = new MSSExtractor(parserConfig);
|
||||||
rawType = "ism";
|
rawType = "ism";
|
||||||
}
|
}
|
||||||
|
@ -145,5 +144,4 @@ namespace N_m3u8DL_RE.Parser
|
||||||
semaphore.Release();
|
semaphore.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,15 +1,10 @@
|
||||||
using N_m3u8DL_RE.Parser.Constants;
|
using N_m3u8DL_RE.Parser.Constants;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Util
|
namespace N_m3u8DL_RE.Parser.Util;
|
||||||
|
|
||||||
|
public partial class ParserUtil
|
||||||
{
|
{
|
||||||
public partial class ParserUtil
|
|
||||||
{
|
|
||||||
[GeneratedRegex("\\$Number%([^$]+)d\\$")]
|
[GeneratedRegex("\\$Number%([^$]+)d\\$")]
|
||||||
private static partial Regex VarsNumberRegex();
|
private static partial Regex VarsNumberRegex();
|
||||||
|
|
||||||
|
@ -28,13 +23,13 @@ namespace N_m3u8DL_RE.Parser.Util
|
||||||
|
|
||||||
var index = -1;
|
var index = -1;
|
||||||
var result = string.Empty;
|
var result = string.Empty;
|
||||||
if ((index = line.IndexOf(key + "=\"")) > -1)
|
if ((index = line.IndexOf(key + "=\"", StringComparison.Ordinal)) > -1)
|
||||||
{
|
{
|
||||||
var startIndex = index + (key + "=\"").Length;
|
var startIndex = index + (key + "=\"").Length;
|
||||||
var endIndex = startIndex + line[startIndex..].IndexOf('\"');
|
var endIndex = startIndex + line[startIndex..].IndexOf('\"');
|
||||||
result = line[startIndex..endIndex];
|
result = line[startIndex..endIndex];
|
||||||
}
|
}
|
||||||
else if ((index = line.IndexOf(key + "=")) > -1)
|
else if ((index = line.IndexOf(key + "=", StringComparison.Ordinal)) > -1)
|
||||||
{
|
{
|
||||||
var startIndex = index + (key + "=").Length;
|
var startIndex = index + (key + "=").Length;
|
||||||
var endIndex = startIndex + line[startIndex..].IndexOf(',');
|
var endIndex = startIndex + line[startIndex..].IndexOf(',');
|
||||||
|
@ -92,13 +87,13 @@ namespace N_m3u8DL_RE.Parser.Util
|
||||||
if (text.Contains(item.Key))
|
if (text.Contains(item.Key))
|
||||||
text = text.Replace(item.Key, item.Value!.ToString());
|
text = text.Replace(item.Key, item.Value!.ToString());
|
||||||
|
|
||||||
//处理特殊形式数字 如 $Number%05d$
|
// 处理特殊形式数字 如 $Number%05d$
|
||||||
var regex = VarsNumberRegex();
|
var regex = VarsNumberRegex();
|
||||||
if (regex.IsMatch(text) && keyValuePairs.ContainsKey(DASHTags.TemplateNumber))
|
if (regex.IsMatch(text) && keyValuePairs.TryGetValue(DASHTags.TemplateNumber, out var keyValuePair))
|
||||||
{
|
{
|
||||||
foreach (Match m in regex.Matches(text))
|
foreach (Match m in regex.Matches(text))
|
||||||
{
|
{
|
||||||
text = text.Replace(m.Value, keyValuePairs[DASHTags.TemplateNumber]?.ToString()?.PadLeft(Convert.ToInt32(m.Groups[1].Value), '0'));
|
text = text.Replace(m.Value, keyValuePair?.ToString()?.PadLeft(Convert.ToInt32(m.Groups[1].Value), '0'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,11 +111,10 @@ namespace N_m3u8DL_RE.Parser.Util
|
||||||
if (string.IsNullOrEmpty(baseurl))
|
if (string.IsNullOrEmpty(baseurl))
|
||||||
return url;
|
return url;
|
||||||
|
|
||||||
Uri uri1 = new Uri(baseurl); //这里直接传完整的URL即可
|
Uri uri1 = new Uri(baseurl); // 这里直接传完整的URL即可
|
||||||
Uri uri2 = new Uri(uri1, url);
|
Uri uri2 = new Uri(uri1, url);
|
||||||
url = uri2.ToString();
|
url = uri2.ToString();
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,18 +1,13 @@
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Entity;
|
||||||
using N_m3u8DL_RE.Entity;
|
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Column
|
namespace N_m3u8DL_RE.Column;
|
||||||
|
|
||||||
|
internal sealed class DownloadSpeedColumn : ProgressColumn
|
||||||
{
|
{
|
||||||
internal sealed class DownloadSpeedColumn : ProgressColumn
|
|
||||||
{
|
|
||||||
private long _stopSpeed = 0;
|
private long _stopSpeed = 0;
|
||||||
private ConcurrentDictionary<int, string> DateTimeStringDic = new();
|
private ConcurrentDictionary<int, string> DateTimeStringDic = new();
|
||||||
protected override bool NoWrap => true;
|
protected override bool NoWrap => true;
|
||||||
|
@ -31,36 +26,23 @@ namespace N_m3u8DL_RE.Column
|
||||||
var speedContainer = SpeedContainerDic[taskId];
|
var speedContainer = SpeedContainerDic[taskId];
|
||||||
var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
var flag = task.IsFinished || !task.IsStarted;
|
var flag = task.IsFinished || !task.IsStarted;
|
||||||
//单文件下载汇报进度
|
// 单文件下载汇报进度
|
||||||
if (!flag && speedContainer.SingleSegment && speedContainer.ResponseLength != null)
|
if (!flag && speedContainer.SingleSegment && speedContainer.ResponseLength != null)
|
||||||
{
|
{
|
||||||
task.MaxValue = (double)speedContainer.ResponseLength;
|
task.MaxValue = (double)speedContainer.ResponseLength;
|
||||||
task.Value = speedContainer.RDownloaded;
|
task.Value = speedContainer.RDownloaded;
|
||||||
}
|
}
|
||||||
//一秒汇报一次即可
|
// 一秒汇报一次即可
|
||||||
if (DateTimeStringDic.TryGetValue(taskId, out var oldTime) && oldTime != now && !flag)
|
if (DateTimeStringDic.TryGetValue(taskId, out var oldTime) && oldTime != now && !flag)
|
||||||
{
|
{
|
||||||
speedContainer.NowSpeed = speedContainer.Downloaded;
|
speedContainer.NowSpeed = speedContainer.Downloaded;
|
||||||
//速度为0,计数增加
|
// 速度为0,计数增加
|
||||||
if (speedContainer.Downloaded <= _stopSpeed) { speedContainer.AddLowSpeedCount(); }
|
if (speedContainer.Downloaded <= _stopSpeed) { speedContainer.AddLowSpeedCount(); }
|
||||||
else speedContainer.ResetLowSpeedCount();
|
else speedContainer.ResetLowSpeedCount();
|
||||||
speedContainer.Reset();
|
speedContainer.Reset();
|
||||||
}
|
}
|
||||||
DateTimeStringDic[taskId] = now;
|
DateTimeStringDic[taskId] = now;
|
||||||
var style = flag ? Style.Plain : MyStyle;
|
var style = flag ? Style.Plain : MyStyle;
|
||||||
return flag ? new Text("-", style).Centered() : new Text(FormatFileSize(speedContainer.NowSpeed) + (speedContainer.LowSpeedCount > 0 ? $"({speedContainer.LowSpeedCount})" : ""), style).Centered();
|
return flag ? new Text("-", style).Centered() : new Text(GlobalUtil.FormatFileSize(speedContainer.NowSpeed) + (speedContainer.LowSpeedCount > 0 ? $"({speedContainer.LowSpeedCount})" : ""), style).Centered();
|
||||||
}
|
|
||||||
|
|
||||||
private static string FormatFileSize(double fileSize)
|
|
||||||
{
|
|
||||||
return fileSize switch
|
|
||||||
{
|
|
||||||
< 0 => throw new ArgumentOutOfRangeException(nameof(fileSize)),
|
|
||||||
>= 1024 * 1024 * 1024 => string.Format("{0:########0.00}GBps", (double)fileSize / (1024 * 1024 * 1024)),
|
|
||||||
>= 1024 * 1024 => string.Format("{0:####0.00}MBps", (double)fileSize / (1024 * 1024)),
|
|
||||||
>= 1024 => string.Format("{0:####0.00}KBps", (double)fileSize / 1024),
|
|
||||||
_ => string.Format("{0:####0.00}Bps", fileSize)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,17 +2,12 @@
|
||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Column
|
namespace N_m3u8DL_RE.Column;
|
||||||
|
|
||||||
|
internal class DownloadStatusColumn : ProgressColumn
|
||||||
{
|
{
|
||||||
internal class DownloadStatusColumn : ProgressColumn
|
|
||||||
{
|
|
||||||
private ConcurrentDictionary<int, SpeedContainer> SpeedContainerDic { get; set; }
|
private ConcurrentDictionary<int, SpeedContainer> SpeedContainerDic { get; set; }
|
||||||
private ConcurrentDictionary<int, string> DateTimeStringDic = new();
|
private ConcurrentDictionary<int, string> DateTimeStringDic = new();
|
||||||
private ConcurrentDictionary<int, string> SizeDic = new();
|
private ConcurrentDictionary<int, string> SizeDic = new();
|
||||||
|
@ -32,7 +27,7 @@ namespace N_m3u8DL_RE.Column
|
||||||
var speedContainer = SpeedContainerDic[task.Id];
|
var speedContainer = SpeedContainerDic[task.Id];
|
||||||
var size = speedContainer.RDownloaded;
|
var size = speedContainer.RDownloaded;
|
||||||
|
|
||||||
//一秒汇报一次即可
|
// 一秒汇报一次即可
|
||||||
if (DateTimeStringDic.TryGetValue(task.Id, out var oldTime) && oldTime != now)
|
if (DateTimeStringDic.TryGetValue(task.Id, out var oldTime) && oldTime != now)
|
||||||
{
|
{
|
||||||
var totalSize = speedContainer.SingleSegment ? (speedContainer.ResponseLength ?? 0) : (long)(size / (task.Value / task.MaxValue));
|
var totalSize = speedContainer.SingleSegment ? (speedContainer.ResponseLength ?? 0) : (long)(size / (task.Value / task.MaxValue));
|
||||||
|
@ -45,5 +40,4 @@ namespace N_m3u8DL_RE.Column
|
||||||
|
|
||||||
return new Text(sizeStr ?? "-", MyStyle).RightJustified();
|
return new Text(sizeStr ?? "-", MyStyle).RightJustified();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,15 +1,10 @@
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Column
|
namespace N_m3u8DL_RE.Column;
|
||||||
|
|
||||||
|
internal class MyPercentageColumn : ProgressColumn
|
||||||
{
|
{
|
||||||
internal class MyPercentageColumn : ProgressColumn
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the style for a non-complete task.
|
/// Gets or sets the style for a non-complete task.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -27,5 +22,4 @@ namespace N_m3u8DL_RE.Column
|
||||||
var style = percentage == 100 ? CompletedStyle : Style ?? Style.Plain;
|
var style = percentage == 100 ? CompletedStyle : Style ?? Style.Plain;
|
||||||
return new Text($"{task.Value}/{task.MaxValue} {percentage:F2}%", style).RightJustified();
|
return new Text($"{task.Value}/{task.MaxValue} {percentage:F2}%", style).RightJustified();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,17 +1,12 @@
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Column
|
namespace N_m3u8DL_RE.Column;
|
||||||
|
|
||||||
|
internal class RecordingDurationColumn : ProgressColumn
|
||||||
{
|
{
|
||||||
internal class RecordingDurationColumn : ProgressColumn
|
|
||||||
{
|
|
||||||
protected override bool NoWrap => true;
|
protected override bool NoWrap => true;
|
||||||
private ConcurrentDictionary<int, int> _recodingDurDic;
|
private ConcurrentDictionary<int, int> _recodingDurDic;
|
||||||
private ConcurrentDictionary<int, int>? _refreshedDurDic;
|
private ConcurrentDictionary<int, int>? _refreshedDurDic;
|
||||||
|
@ -35,5 +30,4 @@ namespace N_m3u8DL_RE.Column
|
||||||
return new Text($"{GlobalUtil.FormatTime(_recodingDurDic[task.Id])}/{GlobalUtil.FormatTime(_refreshedDurDic[task.Id])}", GreyStyle);
|
return new Text($"{GlobalUtil.FormatTime(_recodingDurDic[task.Id])}/{GlobalUtil.FormatTime(_refreshedDurDic[task.Id])}", GreyStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,20 +1,14 @@
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using N_m3u8DL_RE.Entity;
|
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Column
|
namespace N_m3u8DL_RE.Column;
|
||||||
|
|
||||||
|
internal class RecordingSizeColumn : ProgressColumn
|
||||||
{
|
{
|
||||||
internal class RecordingSizeColumn : ProgressColumn
|
|
||||||
{
|
|
||||||
protected override bool NoWrap => true;
|
protected override bool NoWrap => true;
|
||||||
private ConcurrentDictionary<int, double> RecodingSizeDic = new(); //临时的大小 每秒刷新用
|
private ConcurrentDictionary<int, double> RecodingSizeDic = new(); // 临时的大小 每秒刷新用
|
||||||
private ConcurrentDictionary<int, double> _recodingSizeDic;
|
private ConcurrentDictionary<int, double> _recodingSizeDic;
|
||||||
private ConcurrentDictionary<int, string> DateTimeStringDic = new();
|
private ConcurrentDictionary<int, string> DateTimeStringDic = new();
|
||||||
public Style MyStyle { get; set; } = new Style(foreground: Color.DarkCyan);
|
public Style MyStyle { get; set; } = new Style(foreground: Color.DarkCyan);
|
||||||
|
@ -26,7 +20,7 @@ namespace N_m3u8DL_RE.Column
|
||||||
{
|
{
|
||||||
var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
var taskId = task.Id;
|
var taskId = task.Id;
|
||||||
//一秒汇报一次即可
|
// 一秒汇报一次即可
|
||||||
if (DateTimeStringDic.TryGetValue(taskId, out var oldTime) && oldTime != now)
|
if (DateTimeStringDic.TryGetValue(taskId, out var oldTime) && oldTime != now)
|
||||||
{
|
{
|
||||||
RecodingSizeDic[task.Id] = _recodingSizeDic[task.Id];
|
RecodingSizeDic[task.Id] = _recodingSizeDic[task.Id];
|
||||||
|
@ -35,5 +29,4 @@ namespace N_m3u8DL_RE.Column
|
||||||
var flag = RecodingSizeDic.TryGetValue(taskId, out var size);
|
var flag = RecodingSizeDic.TryGetValue(taskId, out var size);
|
||||||
return new Text(GlobalUtil.FormatFileSize(flag ? size : 0), MyStyle).LeftJustified();
|
return new Text(GlobalUtil.FormatFileSize(flag ? size : 0), MyStyle).LeftJustified();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,15 +1,10 @@
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Column
|
namespace N_m3u8DL_RE.Column;
|
||||||
|
|
||||||
|
internal class RecordingStatusColumn : ProgressColumn
|
||||||
{
|
{
|
||||||
internal class RecordingStatusColumn : ProgressColumn
|
|
||||||
{
|
|
||||||
protected override bool NoWrap => true;
|
protected override bool NoWrap => true;
|
||||||
public Style MyStyle { get; set; } = new Style(foreground: Color.Default);
|
public Style MyStyle { get; set; } = new Style(foreground: Color.Default);
|
||||||
public Style FinishedStyle { get; set; } = new Style(foreground: Color.Yellow);
|
public Style FinishedStyle { get; set; } = new Style(foreground: Color.Yellow);
|
||||||
|
@ -19,5 +14,4 @@ namespace N_m3u8DL_RE.Column
|
||||||
return new Text($"{task.Value}/{task.MaxValue} Waiting ", FinishedStyle).LeftJustified();
|
return new Text($"{task.Value}/{task.MaxValue} Waiting ", FinishedStyle).LeftJustified();
|
||||||
return new Text($"{task.Value}/{task.MaxValue} Recording", MyStyle).LeftJustified();
|
return new Text($"{task.Value}/{task.MaxValue} Recording", MyStyle).LeftJustified();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -5,7 +5,6 @@ using N_m3u8DL_RE.Common.Util;
|
||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using N_m3u8DL_RE.Enum;
|
using N_m3u8DL_RE.Enum;
|
||||||
using N_m3u8DL_RE.Util;
|
using N_m3u8DL_RE.Util;
|
||||||
using NiL.JS.Expressions;
|
|
||||||
using System.CommandLine;
|
using System.CommandLine;
|
||||||
using System.CommandLine.Binding;
|
using System.CommandLine.Binding;
|
||||||
using System.CommandLine.Builder;
|
using System.CommandLine.Builder;
|
||||||
|
@ -14,97 +13,97 @@ using System.Globalization;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.CommandLine
|
namespace N_m3u8DL_RE.CommandLine;
|
||||||
|
|
||||||
|
internal partial class CommandInvoker
|
||||||
{
|
{
|
||||||
internal partial class CommandInvoker
|
public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20241110";
|
||||||
{
|
|
||||||
public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20241020";
|
|
||||||
|
|
||||||
[GeneratedRegex("((best|worst)\\d*|all)")]
|
[GeneratedRegex("((best|worst)\\d*|all)")]
|
||||||
private static partial Regex ForStrRegex();
|
private static partial Regex ForStrRegex();
|
||||||
[GeneratedRegex("(\\d*)-(\\d*)")]
|
[GeneratedRegex("(\\d*)-(\\d*)")]
|
||||||
private static partial Regex RangeRegex();
|
private static partial Regex RangeRegex();
|
||||||
|
|
||||||
private readonly static Argument<string> Input = new(name: "input", description: ResString.cmd_Input);
|
private static readonly Argument<string> Input = new(name: "input", description: ResString.cmd_Input);
|
||||||
private readonly static Option<string?> TmpDir = new(new string[] { "--tmp-dir" }, description: ResString.cmd_tmpDir);
|
private static readonly Option<string?> TmpDir = new(["--tmp-dir"], description: ResString.cmd_tmpDir);
|
||||||
private readonly static Option<string?> SaveDir = new(new string[] { "--save-dir" }, description: ResString.cmd_saveDir);
|
private static readonly Option<string?> SaveDir = new(["--save-dir"], description: ResString.cmd_saveDir);
|
||||||
private readonly static Option<string?> SaveName = new(new string[] { "--save-name" }, description: ResString.cmd_saveName, parseArgument: ParseSaveName);
|
private static readonly Option<string?> SaveName = new(["--save-name"], description: ResString.cmd_saveName, parseArgument: ParseSaveName);
|
||||||
private readonly static Option<string?> SavePattern = new(new string[] { "--save-pattern" }, description: ResString.cmd_savePattern, getDefaultValue: () => "<SaveName>_<Id>_<Codecs>_<Language>_<Ext>");
|
private static readonly Option<string?> SavePattern = new(["--save-pattern"], description: ResString.cmd_savePattern, getDefaultValue: () => "<SaveName>_<Id>_<Codecs>_<Language>_<Ext>");
|
||||||
private readonly static Option<string?> UILanguage = new Option<string?>(new string[] { "--ui-language" }, description: ResString.cmd_uiLanguage).FromAmong("en-US", "zh-CN", "zh-TW");
|
private static readonly Option<string?> UILanguage = new Option<string?>(["--ui-language"], description: ResString.cmd_uiLanguage).FromAmong("en-US", "zh-CN", "zh-TW");
|
||||||
private readonly static Option<string?> UrlProcessorArgs = new(new string[] { "--urlprocessor-args" }, description: ResString.cmd_urlProcessorArgs);
|
private static readonly Option<string?> UrlProcessorArgs = new(["--urlprocessor-args"], description: ResString.cmd_urlProcessorArgs);
|
||||||
private readonly static Option<string[]?> Keys = new(new string[] { "--key" }, description: ResString.cmd_keys) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false };
|
private static readonly Option<string[]?> Keys = new(["--key"], description: ResString.cmd_keys) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false };
|
||||||
private readonly static Option<string> KeyTextFile = new(new string[] { "--key-text-file" }, description: ResString.cmd_keyText);
|
private static readonly Option<string> KeyTextFile = new(["--key-text-file"], description: ResString.cmd_keyText);
|
||||||
private readonly static Option<Dictionary<string, string>> Headers = new(new string[] { "-H", "--header" }, description: ResString.cmd_header, parseArgument: ParseHeaders) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false };
|
private static readonly Option<Dictionary<string, string>> Headers = new(["-H", "--header"], description: ResString.cmd_header, parseArgument: ParseHeaders) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false };
|
||||||
private readonly static Option<LogLevel> LogLevel = new(name: "--log-level", description: ResString.cmd_logLevel, getDefaultValue: () => Common.Log.LogLevel.INFO);
|
private static readonly Option<LogLevel> LogLevel = new(name: "--log-level", description: ResString.cmd_logLevel, getDefaultValue: () => Common.Log.LogLevel.INFO);
|
||||||
private readonly static Option<SubtitleFormat> SubtitleFormat = new(name: "--sub-format", description: ResString.cmd_subFormat, getDefaultValue: () => Enum.SubtitleFormat.SRT);
|
private static readonly Option<SubtitleFormat> SubtitleFormat = new(name: "--sub-format", description: ResString.cmd_subFormat, getDefaultValue: () => Enum.SubtitleFormat.SRT);
|
||||||
private readonly static Option<bool> AutoSelect = new(new string[] { "--auto-select" }, description: ResString.cmd_autoSelect, getDefaultValue: () => false);
|
private static readonly Option<bool> AutoSelect = new(["--auto-select"], description: ResString.cmd_autoSelect, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> SubOnly = new(new string[] { "--sub-only" }, description: ResString.cmd_subOnly, getDefaultValue: () => false);
|
private static readonly Option<bool> SubOnly = new(["--sub-only"], description: ResString.cmd_subOnly, getDefaultValue: () => false);
|
||||||
private readonly static Option<int> ThreadCount = new(new string[] { "--thread-count" }, description: ResString.cmd_threadCount, getDefaultValue: () => Environment.ProcessorCount) { ArgumentHelpName = "number" };
|
private static readonly Option<int> ThreadCount = new(["--thread-count"], description: ResString.cmd_threadCount, getDefaultValue: () => Environment.ProcessorCount) { ArgumentHelpName = "number" };
|
||||||
private readonly static Option<int> DownloadRetryCount = new(new string[] { "--download-retry-count" }, description: ResString.cmd_downloadRetryCount, getDefaultValue: () => 3) { ArgumentHelpName = "number" };
|
private static readonly Option<int> DownloadRetryCount = new(["--download-retry-count"], description: ResString.cmd_downloadRetryCount, getDefaultValue: () => 3) { ArgumentHelpName = "number" };
|
||||||
private readonly static Option<bool> SkipMerge = new(new string[] { "--skip-merge" }, description: ResString.cmd_skipMerge, getDefaultValue: () => false);
|
private static readonly Option<bool> SkipMerge = new(["--skip-merge"], description: ResString.cmd_skipMerge, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> SkipDownload = new(new string[] { "--skip-download" }, description: ResString.cmd_skipDownload, getDefaultValue: () => false);
|
private static readonly Option<bool> SkipDownload = new(["--skip-download"], description: ResString.cmd_skipDownload, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> NoDateInfo = new(new string[] { "--no-date-info" }, description: ResString.cmd_noDateInfo, getDefaultValue: () => false);
|
private static readonly Option<bool> NoDateInfo = new(["--no-date-info"], description: ResString.cmd_noDateInfo, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> BinaryMerge = new(new string[] { "--binary-merge" }, description: ResString.cmd_binaryMerge, getDefaultValue: () => false);
|
private static readonly Option<bool> BinaryMerge = new(["--binary-merge"], description: ResString.cmd_binaryMerge, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> UseFFmpegConcatDemuxer = new(new string[] { "--use-ffmpeg-concat-demuxer" }, description: ResString.cmd_useFFmpegConcatDemuxer, getDefaultValue: () => false);
|
private static readonly Option<bool> UseFFmpegConcatDemuxer = new(["--use-ffmpeg-concat-demuxer"], description: ResString.cmd_useFFmpegConcatDemuxer, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> DelAfterDone = new(new string[] { "--del-after-done" }, description: ResString.cmd_delAfterDone, getDefaultValue: () => true);
|
private static readonly Option<bool> DelAfterDone = new(["--del-after-done"], description: ResString.cmd_delAfterDone, getDefaultValue: () => true);
|
||||||
private readonly static Option<bool> AutoSubtitleFix = new(new string[] { "--auto-subtitle-fix" }, description: ResString.cmd_subtitleFix, getDefaultValue: () => true);
|
private static readonly Option<bool> AutoSubtitleFix = new(["--auto-subtitle-fix"], description: ResString.cmd_subtitleFix, getDefaultValue: () => true);
|
||||||
private readonly static Option<bool> CheckSegmentsCount = new(new string[] { "--check-segments-count" }, description: ResString.cmd_checkSegmentsCount, getDefaultValue: () => true);
|
private static readonly Option<bool> CheckSegmentsCount = new(["--check-segments-count"], description: ResString.cmd_checkSegmentsCount, getDefaultValue: () => true);
|
||||||
private readonly static Option<bool> WriteMetaJson = new(new string[] { "--write-meta-json" }, description: ResString.cmd_writeMetaJson, getDefaultValue: () => true);
|
private static readonly Option<bool> WriteMetaJson = new(["--write-meta-json"], description: ResString.cmd_writeMetaJson, getDefaultValue: () => true);
|
||||||
private readonly static Option<bool> AppendUrlParams = new(new string[] { "--append-url-params" }, description: ResString.cmd_appendUrlParams, getDefaultValue: () => false);
|
private static readonly Option<bool> AppendUrlParams = new(["--append-url-params"], description: ResString.cmd_appendUrlParams, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> MP4RealTimeDecryption = new (new string[] { "--mp4-real-time-decryption" }, description: ResString.cmd_MP4RealTimeDecryption, getDefaultValue: () => false);
|
private static readonly Option<bool> MP4RealTimeDecryption = new (["--mp4-real-time-decryption"], description: ResString.cmd_MP4RealTimeDecryption, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> UseShakaPackager = new (new string[] { "--use-shaka-packager" }, description: ResString.cmd_useShakaPackager, getDefaultValue: () => false);
|
private static readonly Option<bool> UseShakaPackager = new (["--use-shaka-packager"], description: ResString.cmd_useShakaPackager, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> ForceAnsiConsole = new(new string[] { "--force-ansi-console" }, description: ResString.cmd_forceAnsiConsole);
|
private static readonly Option<bool> ForceAnsiConsole = new(["--force-ansi-console"], description: ResString.cmd_forceAnsiConsole);
|
||||||
private readonly static Option<bool> NoAnsiColor = new(new string[] { "--no-ansi-color" }, description: ResString.cmd_noAnsiColor);
|
private static readonly Option<bool> NoAnsiColor = new(["--no-ansi-color"], description: ResString.cmd_noAnsiColor);
|
||||||
private readonly static Option<string?> DecryptionBinaryPath = new(new string[] { "--decryption-binary-path" }, description: ResString.cmd_decryptionBinaryPath) { ArgumentHelpName = "PATH" };
|
private static readonly Option<string?> DecryptionBinaryPath = new(["--decryption-binary-path"], description: ResString.cmd_decryptionBinaryPath) { ArgumentHelpName = "PATH" };
|
||||||
private readonly static Option<string?> FFmpegBinaryPath = new(new string[] { "--ffmpeg-binary-path" }, description: ResString.cmd_ffmpegBinaryPath) { ArgumentHelpName = "PATH" };
|
private static readonly Option<string?> FFmpegBinaryPath = new(["--ffmpeg-binary-path"], description: ResString.cmd_ffmpegBinaryPath) { ArgumentHelpName = "PATH" };
|
||||||
private readonly static Option<string?> BaseUrl = new(new string[] { "--base-url" }, description: ResString.cmd_baseUrl);
|
private static readonly Option<string?> BaseUrl = new(["--base-url"], description: ResString.cmd_baseUrl);
|
||||||
private readonly static Option<bool> ConcurrentDownload = new(new string[] { "-mt", "--concurrent-download" }, description: ResString.cmd_concurrentDownload, getDefaultValue: () => false);
|
private static readonly Option<bool> ConcurrentDownload = new(["-mt", "--concurrent-download"], description: ResString.cmd_concurrentDownload, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> NoLog = new(new string[] { "--no-log" }, description: ResString.cmd_noLog, getDefaultValue: () => false);
|
private static readonly Option<bool> NoLog = new(["--no-log"], description: ResString.cmd_noLog, getDefaultValue: () => false);
|
||||||
private readonly static Option<string[]?> AdKeywords = new(new string[] { "--ad-keyword" }, description: ResString.cmd_adKeyword) { ArgumentHelpName = "REG" };
|
private static readonly Option<string[]?> AdKeywords = new(["--ad-keyword"], description: ResString.cmd_adKeyword) { ArgumentHelpName = "REG" };
|
||||||
private readonly static Option<long?> MaxSpeed = new(new string[] { "-R", "--max-speed" }, description: ResString.cmd_maxSpeed, parseArgument: ParseSpeedLimit) { ArgumentHelpName = "SPEED" };
|
private static readonly Option<long?> MaxSpeed = new(["-R", "--max-speed"], description: ResString.cmd_maxSpeed, parseArgument: ParseSpeedLimit) { ArgumentHelpName = "SPEED" };
|
||||||
|
|
||||||
|
|
||||||
//代理选项
|
// 代理选项
|
||||||
private readonly static Option<bool> UseSystemProxy = new(new string[] { "--use-system-proxy" }, description: ResString.cmd_useSystemProxy, getDefaultValue: () => true);
|
private static readonly Option<bool> UseSystemProxy = new(["--use-system-proxy"], description: ResString.cmd_useSystemProxy, getDefaultValue: () => true);
|
||||||
private readonly static Option<WebProxy?> CustomProxy = new(new string[] { "--custom-proxy" }, description: ResString.cmd_customProxy, parseArgument: ParseProxy) { ArgumentHelpName = "URL" };
|
private static readonly Option<WebProxy?> CustomProxy = new(["--custom-proxy"], description: ResString.cmd_customProxy, parseArgument: ParseProxy) { ArgumentHelpName = "URL" };
|
||||||
|
|
||||||
//只下载部分分片
|
// 只下载部分分片
|
||||||
private readonly static Option<CustomRange?> CustomRange = new(new string[] { "--custom-range" }, description: ResString.cmd_customRange, parseArgument: ParseCustomRange) { ArgumentHelpName = "RANGE" };
|
private static readonly Option<CustomRange?> CustomRange = new(["--custom-range"], description: ResString.cmd_customRange, parseArgument: ParseCustomRange) { ArgumentHelpName = "RANGE" };
|
||||||
|
|
||||||
|
|
||||||
//morehelp
|
// morehelp
|
||||||
private readonly static Option<string?> MoreHelp = new(new string[] { "--morehelp" }, description: ResString.cmd_moreHelp) { ArgumentHelpName = "OPTION" };
|
private static readonly Option<string?> MoreHelp = new(["--morehelp"], description: ResString.cmd_moreHelp) { ArgumentHelpName = "OPTION" };
|
||||||
|
|
||||||
//自定义KEY等
|
// 自定义KEY等
|
||||||
private readonly static Option<EncryptMethod?> CustomHLSMethod = new(name: "--custom-hls-method", description: ResString.cmd_customHLSMethod) { ArgumentHelpName = "METHOD" };
|
private static readonly Option<EncryptMethod?> CustomHLSMethod = new(name: "--custom-hls-method", description: ResString.cmd_customHLSMethod) { ArgumentHelpName = "METHOD" };
|
||||||
private readonly static Option<byte[]?> CustomHLSKey = new(name: "--custom-hls-key", description: ResString.cmd_customHLSKey, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" };
|
private static readonly Option<byte[]?> CustomHLSKey = new(name: "--custom-hls-key", description: ResString.cmd_customHLSKey, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" };
|
||||||
private readonly static Option<byte[]?> CustomHLSIv = new(name: "--custom-hls-iv", description: ResString.cmd_customHLSIv, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" };
|
private static readonly Option<byte[]?> CustomHLSIv = new(name: "--custom-hls-iv", description: ResString.cmd_customHLSIv, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" };
|
||||||
|
|
||||||
//任务开始时间
|
// 任务开始时间
|
||||||
private readonly static Option<DateTime?> TaskStartAt = new(new string[] { "--task-start-at" }, description: ResString.cmd_taskStartAt, parseArgument: ParseStartTime) { ArgumentHelpName = "yyyyMMddHHmmss" };
|
private static readonly Option<DateTime?> TaskStartAt = new(["--task-start-at"], description: ResString.cmd_taskStartAt, parseArgument: ParseStartTime) { ArgumentHelpName = "yyyyMMddHHmmss" };
|
||||||
|
|
||||||
|
|
||||||
//直播相关
|
// 直播相关
|
||||||
private readonly static Option<bool> LivePerformAsVod = new(new string[] { "--live-perform-as-vod" }, description: ResString.cmd_livePerformAsVod, getDefaultValue: () => false);
|
private static readonly Option<bool> LivePerformAsVod = new(["--live-perform-as-vod"], description: ResString.cmd_livePerformAsVod, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> LiveRealTimeMerge = new(new string[] { "--live-real-time-merge" }, description: ResString.cmd_liveRealTimeMerge, getDefaultValue: () => false);
|
private static readonly Option<bool> LiveRealTimeMerge = new(["--live-real-time-merge"], description: ResString.cmd_liveRealTimeMerge, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> LiveKeepSegments = new(new string[] { "--live-keep-segments" }, description: ResString.cmd_liveKeepSegments, getDefaultValue: () => true);
|
private static readonly Option<bool> LiveKeepSegments = new(["--live-keep-segments"], description: ResString.cmd_liveKeepSegments, getDefaultValue: () => true);
|
||||||
private readonly static Option<bool> LivePipeMux = new(new string[] { "--live-pipe-mux" }, description: ResString.cmd_livePipeMux, getDefaultValue: () => false);
|
private static readonly Option<bool> LivePipeMux = new(["--live-pipe-mux"], description: ResString.cmd_livePipeMux, getDefaultValue: () => false);
|
||||||
private readonly static Option<TimeSpan?> LiveRecordLimit = new(new string[] { "--live-record-limit" }, description: ResString.cmd_liveRecordLimit, parseArgument: ParseLiveLimit) { ArgumentHelpName = "HH:mm:ss" };
|
private static readonly Option<TimeSpan?> LiveRecordLimit = new(["--live-record-limit"], description: ResString.cmd_liveRecordLimit, parseArgument: ParseLiveLimit) { ArgumentHelpName = "HH:mm:ss" };
|
||||||
private readonly static Option<int?> LiveWaitTime = new(new string[] { "--live-wait-time" }, description: ResString.cmd_liveWaitTime) { ArgumentHelpName = "SEC" };
|
private static readonly Option<int?> LiveWaitTime = new(["--live-wait-time"], description: ResString.cmd_liveWaitTime) { ArgumentHelpName = "SEC" };
|
||||||
private readonly static Option<int> LiveTakeCount = new(new string[] { "--live-take-count" }, description: ResString.cmd_liveTakeCount, getDefaultValue: () => 16) { ArgumentHelpName = "NUM" };
|
private static readonly Option<int> LiveTakeCount = new(["--live-take-count"], description: ResString.cmd_liveTakeCount, getDefaultValue: () => 16) { ArgumentHelpName = "NUM" };
|
||||||
private readonly static Option<bool> LiveFixVttByAudio = new(new string[] { "--live-fix-vtt-by-audio" }, description: ResString.cmd_liveFixVttByAudio, getDefaultValue: () => false);
|
private static readonly Option<bool> LiveFixVttByAudio = new(["--live-fix-vtt-by-audio"], description: ResString.cmd_liveFixVttByAudio, getDefaultValue: () => false);
|
||||||
|
|
||||||
|
|
||||||
//复杂命令行如下
|
// 复杂命令行如下
|
||||||
private readonly static Option<MuxOptions?> MuxAfterDone = new(new string[] { "-M", "--mux-after-done" }, description: ResString.cmd_muxAfterDone, parseArgument: ParseMuxAfterDone) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<MuxOptions?> MuxAfterDone = new(["-M", "--mux-after-done"], description: ResString.cmd_muxAfterDone, parseArgument: ParseMuxAfterDone) { ArgumentHelpName = "OPTIONS" };
|
||||||
private readonly static Option<List<OutputFile>> MuxImports = new("--mux-import", description: ResString.cmd_muxImport, parseArgument: ParseImports) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false, ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<List<OutputFile>> MuxImports = new("--mux-import", description: ResString.cmd_muxImport, parseArgument: ParseImports) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false, ArgumentHelpName = "OPTIONS" };
|
||||||
private readonly static Option<StreamFilter?> VideoFilter = new(new string[] { "-sv", "--select-video" }, description: ResString.cmd_selectVideo, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<StreamFilter?> VideoFilter = new(["-sv", "--select-video"], description: ResString.cmd_selectVideo, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||||
private readonly static Option<StreamFilter?> AudioFilter = new(new string[] { "-sa", "--select-audio" }, description: ResString.cmd_selectAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<StreamFilter?> AudioFilter = new(["-sa", "--select-audio"], description: ResString.cmd_selectAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||||
private readonly static Option<StreamFilter?> SubtitleFilter = new(new string[] { "-ss", "--select-subtitle" }, description: ResString.cmd_selectSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<StreamFilter?> SubtitleFilter = new(["-ss", "--select-subtitle"], description: ResString.cmd_selectSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||||
|
|
||||||
private readonly static Option<StreamFilter?> DropVideoFilter = new(new string[] { "-dv", "--drop-video" }, description: ResString.cmd_dropVideo, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<StreamFilter?> DropVideoFilter = new(["-dv", "--drop-video"], description: ResString.cmd_dropVideo, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||||
private readonly static Option<StreamFilter?> DropAudioFilter = new(new string[] { "-da", "--drop-audio" }, description: ResString.cmd_dropAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<StreamFilter?> DropAudioFilter = new(["-da", "--drop-audio"], description: ResString.cmd_dropAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||||
private readonly static Option<StreamFilter?> DropSubtitleFilter = new(new string[] { "-ds", "--drop-subtitle" }, description: ResString.cmd_dropSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<StreamFilter?> DropSubtitleFilter = new(["-ds", "--drop-subtitle"], description: ResString.cmd_dropSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 解析录制直播时长限制
|
/// 解析录制直播时长限制
|
||||||
|
@ -141,7 +140,7 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
private static CustomRange? ParseCustomRange(ArgumentResult result)
|
private static CustomRange? ParseCustomRange(ArgumentResult result)
|
||||||
{
|
{
|
||||||
var input = result.Tokens.First().Value;
|
var input = result.Tokens.First().Value;
|
||||||
//支持的种类 0-100; 01:00:00-02:30:00; -300; 300-; 05:00-; -03:00;
|
// 支持的种类 0-100; 01:00:00-02:30:00; -300; 300-; 05:00-; -03:00;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(input))
|
if (string.IsNullOrEmpty(input))
|
||||||
|
@ -300,7 +299,7 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
var p = new ComplexParamParser(input);
|
var p = new ComplexParamParser(input);
|
||||||
|
|
||||||
|
|
||||||
//目标范围
|
// 目标范围
|
||||||
var forStr = "";
|
var forStr = "";
|
||||||
if (input == ForStrRegex().Match(input).Value)
|
if (input == ForStrRegex().Match(input).Value)
|
||||||
{
|
{
|
||||||
|
@ -407,7 +406,7 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
foreach (var item in result.Tokens)
|
foreach (var item in result.Tokens)
|
||||||
{
|
{
|
||||||
var p = new ComplexParamParser(item.Value);
|
var p = new ComplexParamParser(item.Value);
|
||||||
var path = p.GetValue("path") ?? item.Value; //若未获取到,直接整个字符串作为path
|
var path = p.GetValue("path") ?? item.Value; // 若未获取到,直接整个字符串作为path
|
||||||
var lang = p.GetValue("lang");
|
var lang = p.GetValue("lang");
|
||||||
var name = p.GetValue("name");
|
var name = p.GetValue("name");
|
||||||
if (string.IsNullOrEmpty(path) || !File.Exists(path))
|
if (string.IsNullOrEmpty(path) || !File.Exists(path))
|
||||||
|
@ -436,43 +435,43 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
{
|
{
|
||||||
var v = result.Tokens.First().Value;
|
var v = result.Tokens.First().Value;
|
||||||
var p = new ComplexParamParser(v);
|
var p = new ComplexParamParser(v);
|
||||||
//混流格式
|
// 混流格式
|
||||||
var format = p.GetValue("format") ?? v.Split(':')[0]; //若未获取到,直接:前的字符串作为format解析
|
var format = p.GetValue("format") ?? v.Split(':')[0]; // 若未获取到,直接:前的字符串作为format解析
|
||||||
var parseResult = System.Enum.TryParse(format.ToUpperInvariant(), out MuxFormat muxFormat);
|
var parseResult = System.Enum.TryParse(format.ToUpperInvariant(), out MuxFormat muxFormat);
|
||||||
if (!parseResult)
|
if (!parseResult)
|
||||||
{
|
{
|
||||||
result.ErrorMessage = $"format={format} not valid";
|
result.ErrorMessage = $"format={format} not valid";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
//混流器
|
// 混流器
|
||||||
var muxer = p.GetValue("muxer") ?? "ffmpeg";
|
var muxer = p.GetValue("muxer") ?? "ffmpeg";
|
||||||
if (muxer != "ffmpeg" && muxer != "mkvmerge")
|
if (muxer != "ffmpeg" && muxer != "mkvmerge")
|
||||||
{
|
{
|
||||||
result.ErrorMessage = $"muxer={muxer} not valid";
|
result.ErrorMessage = $"muxer={muxer} not valid";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
//混流器路径
|
// 混流器路径
|
||||||
var bin_path = p.GetValue("bin_path") ?? "auto";
|
var bin_path = p.GetValue("bin_path") ?? "auto";
|
||||||
if (string.IsNullOrEmpty(bin_path))
|
if (string.IsNullOrEmpty(bin_path))
|
||||||
{
|
{
|
||||||
result.ErrorMessage = $"bin_path={bin_path} not valid";
|
result.ErrorMessage = $"bin_path={bin_path} not valid";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
//是否删除
|
// 是否删除
|
||||||
var keep = p.GetValue("keep") ?? "false";
|
var keep = p.GetValue("keep") ?? "false";
|
||||||
if (keep != "true" && keep != "false")
|
if (keep != "true" && keep != "false")
|
||||||
{
|
{
|
||||||
result.ErrorMessage = $"keep={keep} not valid";
|
result.ErrorMessage = $"keep={keep} not valid";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
//是否忽略字幕
|
// 是否忽略字幕
|
||||||
var skipSub = p.GetValue("skip_sub") ?? "false";
|
var skipSub = p.GetValue("skip_sub") ?? "false";
|
||||||
if (skipSub != "true" && skipSub != "false")
|
if (skipSub != "true" && skipSub != "false")
|
||||||
{
|
{
|
||||||
result.ErrorMessage = $"skip_sub={keep} not valid";
|
result.ErrorMessage = $"skip_sub={keep} not valid";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
//冲突检测
|
// 冲突检测
|
||||||
if (muxer == "mkvmerge" && format == "mp4")
|
if (muxer == "mkvmerge" && format == "mp4")
|
||||||
{
|
{
|
||||||
result.ErrorMessage = $"mkvmerge can not do mp4";
|
result.ErrorMessage = $"mkvmerge can not do mp4";
|
||||||
|
@ -560,7 +559,7 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
option.Headers = parsedHeaders;
|
option.Headers = parsedHeaders;
|
||||||
|
|
||||||
|
|
||||||
//以用户选择语言为准优先
|
// 以用户选择语言为准优先
|
||||||
if (option.UILanguage != null)
|
if (option.UILanguage != null)
|
||||||
{
|
{
|
||||||
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(option.UILanguage);
|
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(option.UILanguage);
|
||||||
|
@ -568,7 +567,7 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(option.UILanguage);
|
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(option.UILanguage);
|
||||||
}
|
}
|
||||||
|
|
||||||
//混流设置
|
// 混流设置
|
||||||
var muxAfterDoneValue = bindingContext.ParseResult.GetValueForOption(MuxAfterDone);
|
var muxAfterDoneValue = bindingContext.ParseResult.GetValueForOption(MuxAfterDone);
|
||||||
if (muxAfterDoneValue != null)
|
if (muxAfterDoneValue != null)
|
||||||
{
|
{
|
||||||
|
@ -639,5 +638,4 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
|
|
||||||
return await parser.InvokeAsync(args);
|
return await parser.InvokeAsync(args);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,13 +1,9 @@
|
||||||
using System;
|
using System.Text;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.CommandLine
|
namespace N_m3u8DL_RE.CommandLine;
|
||||||
|
|
||||||
|
internal class ComplexParamParser
|
||||||
{
|
{
|
||||||
internal class ComplexParamParser
|
|
||||||
{
|
|
||||||
private string _arg;
|
private string _arg;
|
||||||
public ComplexParamParser(string arg)
|
public ComplexParamParser(string arg)
|
||||||
{
|
{
|
||||||
|
@ -47,7 +43,7 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
|
|
||||||
var resultStr = result.ToString().Trim().Trim('\"').Trim('\'');
|
var resultStr = result.ToString().Trim().Trim('\"').Trim('\'');
|
||||||
|
|
||||||
//不应该有引号出现
|
// 不应该有引号出现
|
||||||
if (resultStr.Contains('\"') || resultStr.Contains('\'')) throw new Exception();
|
if (resultStr.Contains('\"') || resultStr.Contains('\'')) throw new Exception();
|
||||||
|
|
||||||
return resultStr;
|
return resultStr;
|
||||||
|
@ -57,5 +53,4 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
throw new ArgumentException($"Parse Argument [{key}] failed!");
|
throw new ArgumentException($"Parse Argument [{key}] failed!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -4,10 +4,10 @@ using N_m3u8DL_RE.Entity;
|
||||||
using N_m3u8DL_RE.Enum;
|
using N_m3u8DL_RE.Enum;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.CommandLine
|
namespace N_m3u8DL_RE.CommandLine;
|
||||||
|
|
||||||
|
internal class MyOption
|
||||||
{
|
{
|
||||||
internal class MyOption
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// See: <see cref="CommandInvoker.Input"/>.
|
/// See: <see cref="CommandInvoker.Input"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -244,8 +244,8 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
/// See: <see cref="CommandInvoker.LiveTakeCount"/>.
|
/// See: <see cref="CommandInvoker.LiveTakeCount"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int LiveTakeCount { get; set; }
|
public int LiveTakeCount { get; set; }
|
||||||
public MuxOptions MuxOptions { get; set; }
|
public MuxOptions? MuxOptions { get; set; }
|
||||||
//public bool LiveWriteHLS { get; set; } = true;
|
// public bool LiveWriteHLS { get; set; } = true;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// See: <see cref="CommandInvoker.LivePipeMux"/>.
|
/// See: <see cref="CommandInvoker.LivePipeMux"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -254,5 +254,4 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
/// See: <see cref="CommandInvoker.LiveFixVttByAudio"/>.
|
/// See: <see cref="CommandInvoker.LiveFixVttByAudio"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool LiveFixVttByAudio { get; set; }
|
public bool LiveFixVttByAudio { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,16 +1,9 @@
|
||||||
using N_m3u8DL_RE.CommandLine;
|
using N_m3u8DL_RE.CommandLine;
|
||||||
using N_m3u8DL_RE.Enum;
|
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Config
|
namespace N_m3u8DL_RE.Config;
|
||||||
|
|
||||||
|
internal class DownloaderConfig
|
||||||
{
|
{
|
||||||
internal class DownloaderConfig
|
|
||||||
{
|
|
||||||
public required MyOption MyOptions { get; set; }
|
public required MyOption MyOptions { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -29,5 +22,4 @@ namespace N_m3u8DL_RE.Config
|
||||||
/// 请求头
|
/// 请求头
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
|
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,14 +1,9 @@
|
||||||
using System;
|
using System.Security.Cryptography;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Crypto
|
namespace N_m3u8DL_RE.Crypto;
|
||||||
|
|
||||||
|
internal class AESUtil
|
||||||
{
|
{
|
||||||
internal class AESUtil
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AES-128解密,解密后原地替换文件
|
/// AES-128解密,解密后原地替换文件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -40,5 +35,4 @@ namespace N_m3u8DL_RE.Crypto
|
||||||
byte[] resultArray = cTransform.TransformFinalBlock(inBuff, 0, inBuff.Length);
|
byte[] resultArray = cTransform.TransformFinalBlock(inBuff, 0, inBuff.Length);
|
||||||
return resultArray;
|
return resultArray;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,14 +1,9 @@
|
||||||
using CSChaCha20;
|
using CSChaCha20;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Crypto
|
namespace N_m3u8DL_RE.Crypto;
|
||||||
|
|
||||||
|
internal class ChaCha20Util
|
||||||
{
|
{
|
||||||
internal class ChaCha20Util
|
|
||||||
{
|
|
||||||
public static byte[] DecryptPer1024Bytes(byte[] encryptedBuff, byte[] keyBytes, byte[] nonceBytes)
|
public static byte[] DecryptPer1024Bytes(byte[] encryptedBuff, byte[] keyBytes, byte[] nonceBytes)
|
||||||
{
|
{
|
||||||
if (keyBytes.Length != 32)
|
if (keyBytes.Length != 32)
|
||||||
|
@ -39,5 +34,4 @@ namespace N_m3u8DL_RE.Crypto
|
||||||
|
|
||||||
return decStream.ToArray();
|
return decStream.ToArray();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
using Mp4SubtitleParser;
|
using N_m3u8DL_RE.Column;
|
||||||
using N_m3u8DL_RE.Column;
|
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
|
@ -11,22 +9,13 @@ using N_m3u8DL_RE.Entity;
|
||||||
using N_m3u8DL_RE.Parser;
|
using N_m3u8DL_RE.Parser;
|
||||||
using N_m3u8DL_RE.Util;
|
using N_m3u8DL_RE.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.IO;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Reflection.PortableExecutable;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Threading.Tasks.Dataflow;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.DownloadManager
|
namespace N_m3u8DL_RE.DownloadManager;
|
||||||
|
|
||||||
|
internal class HTTPLiveRecordManager
|
||||||
{
|
{
|
||||||
internal class HTTPLiveRecordManager
|
|
||||||
{
|
|
||||||
IDownloader Downloader;
|
IDownloader Downloader;
|
||||||
DownloaderConfig DownloaderConfig;
|
DownloaderConfig DownloaderConfig;
|
||||||
StreamExtractor StreamExtractor;
|
StreamExtractor StreamExtractor;
|
||||||
|
@ -36,10 +25,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
DateTime? PublishDateTime;
|
DateTime? PublishDateTime;
|
||||||
bool STOP_FLAG = false;
|
bool STOP_FLAG = false;
|
||||||
bool READ_IFO = false;
|
bool READ_IFO = false;
|
||||||
ConcurrentDictionary<int, int> RecordingDurDic = new(); //已录制时长
|
ConcurrentDictionary<int, int> RecordingDurDic = new(); // 已录制时长
|
||||||
ConcurrentDictionary<int, double> RecordingSizeDic = new(); //已录制大小
|
ConcurrentDictionary<int, double> RecordingSizeDic = new(); // 已录制大小
|
||||||
CancellationTokenSource CancellationTokenSource = new(); //取消Wait
|
CancellationTokenSource CancellationTokenSource = new(); // 取消Wait
|
||||||
List<byte> InfoBuffer = new List<byte>(188 * 5000); //5000个分包中解析信息,没有就算了
|
List<byte> InfoBuffer = new List<byte>(188 * 5000); // 5000个分包中解析信息,没有就算了
|
||||||
|
|
||||||
public HTTPLiveRecordManager(DownloaderConfig downloaderConfig, List<StreamSpec> selectedSteams, StreamExtractor streamExtractor)
|
public HTTPLiveRecordManager(DownloaderConfig downloaderConfig, List<StreamSpec> selectedSteams, StreamExtractor streamExtractor)
|
||||||
{
|
{
|
||||||
|
@ -63,7 +52,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
Logger.Debug($"dirName: {dirName}; saveDir: {saveDir}; saveName: {saveName}");
|
Logger.Debug($"dirName: {dirName}; saveDir: {saveDir}; saveName: {saveName}");
|
||||||
|
|
||||||
//创建文件夹
|
// 创建文件夹
|
||||||
if (!Directory.Exists(saveDir)) Directory.CreateDirectory(saveDir);
|
if (!Directory.Exists(saveDir)) Directory.CreateDirectory(saveDir);
|
||||||
|
|
||||||
using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(streamSpec.Url));
|
using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(streamSpec.Url));
|
||||||
|
@ -83,10 +72,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var buffer = new byte[16 * 1024];
|
var buffer = new byte[16 * 1024];
|
||||||
var size = 0;
|
var size = 0;
|
||||||
|
|
||||||
//计时器
|
// 计时器
|
||||||
TimeCounterAsync();
|
_ = TimeCounterAsync();
|
||||||
//读取INFO
|
// 读取INFO
|
||||||
ReadInfoAsync();
|
_ = ReadInfoAsync();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -137,16 +126,16 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var tsHeaderInt = BitConverter.ToUInt32(BitConverter.IsLittleEndian ? tsData.Take(4).Reverse().ToArray() : tsData.Take(4).ToArray(), 0);
|
var tsHeaderInt = BitConverter.ToUInt32(BitConverter.IsLittleEndian ? tsData.Take(4).Reverse().ToArray() : tsData.Take(4).ToArray(), 0);
|
||||||
var pid = (tsHeaderInt & 0x1fff00) >> 8;
|
var pid = (tsHeaderInt & 0x1fff00) >> 8;
|
||||||
var tsPayload = tsData.Skip(4);
|
var tsPayload = tsData.Skip(4);
|
||||||
//PAT
|
// PAT
|
||||||
if (pid == 0x0000)
|
if (pid == 0x0000)
|
||||||
{
|
{
|
||||||
programId = ConvertToUint16(tsPayload.Skip(9).Take(2)).ToString();
|
programId = ConvertToUint16(tsPayload.Skip(9).Take(2)).ToString();
|
||||||
}
|
}
|
||||||
//SDT, BAT, ST
|
// SDT, BAT, ST
|
||||||
else if (pid == 0x0011)
|
else if (pid == 0x0011)
|
||||||
{
|
{
|
||||||
var tableId = (int)tsPayload.Skip(1).First();
|
var tableId = (int)tsPayload.Skip(1).First();
|
||||||
//Current TS Info
|
// Current TS Info
|
||||||
if (tableId == 0x42)
|
if (tableId == 0x42)
|
||||||
{
|
{
|
||||||
var sectionLength = ConvertToUint16(tsPayload.Skip(2).Take(2)) & 0xfff;
|
var sectionLength = ConvertToUint16(tsPayload.Skip(2).Take(2)) & 0xfff;
|
||||||
|
@ -182,7 +171,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
RecordingDurDic[0]++;
|
RecordingDurDic[0]++;
|
||||||
|
|
||||||
//检测时长限制
|
// 检测时长限制
|
||||||
if (RecordingDurDic.All(d => d.Value >= DownloaderConfig.MyOptions.LiveRecordLimit?.TotalSeconds))
|
if (RecordingDurDic.All(d => d.Value >= DownloaderConfig.MyOptions.LiveRecordLimit?.TotalSeconds))
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimitReached}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimitReached}[/]");
|
||||||
|
@ -194,20 +183,20 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
public async Task<bool> StartRecordAsync()
|
public async Task<bool> StartRecordAsync()
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<int, SpeedContainer> SpeedContainerDic = new(); //速度计算
|
ConcurrentDictionary<int, SpeedContainer> SpeedContainerDic = new(); // 速度计算
|
||||||
ConcurrentDictionary<StreamSpec, bool?> Results = new();
|
ConcurrentDictionary<StreamSpec, bool?> Results = new();
|
||||||
|
|
||||||
var progress = CustomAnsiConsole.Console.Progress().AutoClear(true);
|
var progress = CustomAnsiConsole.Console.Progress().AutoClear(true);
|
||||||
progress.AutoRefresh = DownloaderConfig.MyOptions.LogLevel != LogLevel.OFF;
|
progress.AutoRefresh = DownloaderConfig.MyOptions.LogLevel != LogLevel.OFF;
|
||||||
|
|
||||||
//进度条的列定义
|
// 进度条的列定义
|
||||||
var progressColumns = new ProgressColumn[]
|
var progressColumns = new ProgressColumn[]
|
||||||
{
|
{
|
||||||
new TaskDescriptionColumn() { Alignment = Justify.Left },
|
new TaskDescriptionColumn() { Alignment = Justify.Left },
|
||||||
new RecordingDurationColumn(RecordingDurDic), //时长显示
|
new RecordingDurationColumn(RecordingDurDic), // 时长显示
|
||||||
new RecordingSizeColumn(RecordingSizeDic), //大小显示
|
new RecordingSizeColumn(RecordingSizeDic), // 大小显示
|
||||||
new RecordingStatusColumn(),
|
new RecordingStatusColumn(),
|
||||||
new DownloadSpeedColumn(SpeedContainerDic), //速度计算
|
new DownloadSpeedColumn(SpeedContainerDic), // 速度计算
|
||||||
new SpinnerColumn(),
|
new SpinnerColumn(),
|
||||||
};
|
};
|
||||||
if (DownloaderConfig.MyOptions.NoAnsiColor)
|
if (DownloaderConfig.MyOptions.NoAnsiColor)
|
||||||
|
@ -218,11 +207,11 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
await progress.StartAsync(async ctx =>
|
await progress.StartAsync(async ctx =>
|
||||||
{
|
{
|
||||||
//创建任务
|
// 创建任务
|
||||||
var dic = SelectedSteams.Select(item =>
|
var dic = SelectedSteams.Select(item =>
|
||||||
{
|
{
|
||||||
var task = ctx.AddTask(item.ToShortString(), autoStart: false, maxValue: 0);
|
var task = ctx.AddTask(item.ToShortString(), autoStart: false, maxValue: 0);
|
||||||
SpeedContainerDic[task.Id] = new SpeedContainer(); //速度计算
|
SpeedContainerDic[task.Id] = new SpeedContainer(); // 速度计算
|
||||||
RecordingDurDic[task.Id] = 0;
|
RecordingDurDic[task.Id] = 0;
|
||||||
RecordingSizeDic[task.Id] = 0;
|
RecordingSizeDic[task.Id] = 0;
|
||||||
return (item, task);
|
return (item, task);
|
||||||
|
@ -232,12 +221,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
|
var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
|
||||||
if (limit != TimeSpan.MaxValue)
|
if (limit != TimeSpan.MaxValue)
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimit}{GlobalUtil.FormatTime((int)limit.Value.TotalSeconds)}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimit}{GlobalUtil.FormatTime((int)limit.Value.TotalSeconds)}[/]");
|
||||||
//录制直播时,用户选了几个流就并发录几个
|
// 录制直播时,用户选了几个流就并发录几个
|
||||||
var options = new ParallelOptions()
|
var options = new ParallelOptions()
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = SelectedSteams.Count
|
MaxDegreeOfParallelism = SelectedSteams.Count
|
||||||
};
|
};
|
||||||
//并发下载
|
// 并发下载
|
||||||
await Parallel.ForEachAsync(dic, options, async (kp, _) =>
|
await Parallel.ForEachAsync(dic, options, async (kp, _) =>
|
||||||
{
|
{
|
||||||
var task = kp.Value;
|
var task = kp.Value;
|
||||||
|
@ -250,5 +239,4 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -14,10 +14,10 @@ using Spectre.Console;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.DownloadManager
|
namespace N_m3u8DL_RE.DownloadManager;
|
||||||
|
|
||||||
|
internal class SimpleDownloadManager
|
||||||
{
|
{
|
||||||
internal class SimpleDownloadManager
|
|
||||||
{
|
|
||||||
IDownloader Downloader;
|
IDownloader Downloader;
|
||||||
DownloaderConfig DownloaderConfig;
|
DownloaderConfig DownloaderConfig;
|
||||||
StreamExtractor StreamExtractor;
|
StreamExtractor StreamExtractor;
|
||||||
|
@ -32,7 +32,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
Downloader = new SimpleDownloader(DownloaderConfig);
|
Downloader = new SimpleDownloader(DownloaderConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
//从文件读取KEY
|
// 从文件读取KEY
|
||||||
private async Task SearchKeyAsync(string? currentKID)
|
private async Task SearchKeyAsync(string? currentKID)
|
||||||
{
|
{
|
||||||
var _key = await MP4DecryptUtil.SearchKeyFromFileAsync(DownloaderConfig.MyOptions.KeyTextFile, currentKID);
|
var _key = await MP4DecryptUtil.SearchKeyFromFileAsync(DownloaderConfig.MyOptions.KeyTextFile, currentKID);
|
||||||
|
@ -79,13 +79,13 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
private async Task<bool> DownloadStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer)
|
private async Task<bool> DownloadStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer)
|
||||||
{
|
{
|
||||||
speedContainer.ResetVars();
|
speedContainer.ResetVars();
|
||||||
bool useAACFilter = false; //ffmpeg合并flag
|
bool useAACFilter = false; // ffmpeg合并flag
|
||||||
List<Mediainfo> mediaInfos = new();
|
List<Mediainfo> mediaInfos = new();
|
||||||
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
|
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
|
||||||
|
|
||||||
var segments = streamSpec.Playlist?.MediaParts.SelectMany(m => m.MediaSegments);
|
var segments = streamSpec.Playlist?.MediaParts.SelectMany(m => m.MediaSegments);
|
||||||
if (segments == null || !segments.Any()) return false;
|
if (segments == null || !segments.Any()) return false;
|
||||||
//单分段尝试切片并行下载
|
// 单分段尝试切片并行下载
|
||||||
if (segments.Count() == 1)
|
if (segments.Count() == 1)
|
||||||
{
|
{
|
||||||
var splitSegments = await LargeSingleFileSplitUtil.SplitUrlAsync(segments.First(), DownloaderConfig.Headers);
|
var splitSegments = await LargeSingleFileSplitUtil.SplitUrlAsync(segments.First(), DownloaderConfig.Headers);
|
||||||
|
@ -109,19 +109,19 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
|
var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
|
||||||
var headers = DownloaderConfig.Headers;
|
var headers = DownloaderConfig.Headers;
|
||||||
|
|
||||||
//mp4decrypt
|
// mp4decrypt
|
||||||
var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
|
var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
|
||||||
var mp4InitFile = "";
|
var mp4InitFile = "";
|
||||||
var currentKID = "";
|
var currentKID = "";
|
||||||
var readInfo = false; //是否读取过
|
var readInfo = false; // 是否读取过
|
||||||
var mp4Info = new ParsedMP4Info();
|
var mp4Info = new ParsedMP4Info();
|
||||||
|
|
||||||
//用户自定义范围导致被跳过的时长 计算字幕偏移使用
|
// 用户自定义范围导致被跳过的时长 计算字幕偏移使用
|
||||||
var skippedDur = streamSpec.SkippedDuration ?? 0d;
|
var skippedDur = streamSpec.SkippedDuration ?? 0d;
|
||||||
|
|
||||||
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}");
|
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}");
|
||||||
|
|
||||||
//创建文件夹
|
// 创建文件夹
|
||||||
if (!Directory.Exists(tmpDir)) Directory.CreateDirectory(tmpDir);
|
if (!Directory.Exists(tmpDir)) Directory.CreateDirectory(tmpDir);
|
||||||
if (!Directory.Exists(saveDir)) Directory.CreateDirectory(saveDir);
|
if (!Directory.Exists(saveDir)) Directory.CreateDirectory(saveDir);
|
||||||
|
|
||||||
|
@ -134,20 +134,20 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
task.MaxValue = totalCount;
|
task.MaxValue = totalCount;
|
||||||
task.StartTask();
|
task.StartTask();
|
||||||
|
|
||||||
//开始下载
|
// 开始下载
|
||||||
Logger.InfoMarkUp(ResString.startDownloading + streamSpec.ToShortString());
|
Logger.InfoMarkUp(ResString.startDownloading + streamSpec.ToShortString());
|
||||||
|
|
||||||
//对于CENC,全部自动开启二进制合并
|
// 对于CENC,全部自动开启二进制合并
|
||||||
if (!DownloaderConfig.MyOptions.BinaryMerge && totalCount >= 1 && streamSpec.Playlist!.MediaParts.First().MediaSegments.First().EncryptInfo.Method == Common.Enum.EncryptMethod.CENC)
|
if (!DownloaderConfig.MyOptions.BinaryMerge && totalCount >= 1 && streamSpec.Playlist!.MediaParts.First().MediaSegments.First().EncryptInfo.Method == Common.Enum.EncryptMethod.CENC)
|
||||||
{
|
{
|
||||||
DownloaderConfig.MyOptions.BinaryMerge = true;
|
DownloaderConfig.MyOptions.BinaryMerge = true;
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge4}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge4}[/]");
|
||||||
}
|
}
|
||||||
|
|
||||||
//下载init
|
// 下载init
|
||||||
if (streamSpec.Playlist?.MediaInit != null)
|
if (streamSpec.Playlist?.MediaInit != null)
|
||||||
{
|
{
|
||||||
//对于fMP4,自动开启二进制合并
|
// 对于fMP4,自动开启二进制合并
|
||||||
if (!DownloaderConfig.MyOptions.BinaryMerge && streamSpec.MediaType != MediaType.SUBTITLES)
|
if (!DownloaderConfig.MyOptions.BinaryMerge && streamSpec.MediaType != MediaType.SUBTITLES)
|
||||||
{
|
{
|
||||||
DownloaderConfig.MyOptions.BinaryMerge = true;
|
DownloaderConfig.MyOptions.BinaryMerge = true;
|
||||||
|
@ -164,7 +164,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
mp4InitFile = result.ActualFilePath;
|
mp4InitFile = result.ActualFilePath;
|
||||||
task.Increment(1);
|
task.Increment(1);
|
||||||
|
|
||||||
//读取mp4信息
|
// 读取mp4信息
|
||||||
if (result != null && result.Success)
|
if (result != null && result.Success)
|
||||||
{
|
{
|
||||||
mp4Info = MP4DecryptUtil.GetMP4Info(result.ActualFilePath);
|
mp4Info = MP4DecryptUtil.GetMP4Info(result.ActualFilePath);
|
||||||
|
@ -173,9 +173,9 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
||||||
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt);
|
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt);
|
||||||
}
|
}
|
||||||
//从文件读取KEY
|
// 从文件读取KEY
|
||||||
await SearchKeyAsync(currentKID);
|
await SearchKeyAsync(currentKID);
|
||||||
//实时解密
|
// 实时解密
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID) && StreamExtractor.ExtractorType != ExtractorType.MSS)
|
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID) && StreamExtractor.ExtractorType != ExtractorType.MSS)
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
|
@ -186,7 +186,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//ffmpeg读取信息
|
// ffmpeg读取信息
|
||||||
if (!readInfo)
|
if (!readInfo)
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp(ResString.readingInfo);
|
Logger.WarnMarkUp(ResString.readingInfo);
|
||||||
|
@ -198,10 +198,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//计算填零个数
|
// 计算填零个数
|
||||||
var pad = "0".PadLeft(segments.Count().ToString().Length, '0');
|
var pad = "0".PadLeft(segments.Count().ToString().Length, '0');
|
||||||
|
|
||||||
//下载第一个分片
|
// 下载第一个分片
|
||||||
if (!readInfo || StreamExtractor.ExtractorType == ExtractorType.MSS)
|
if (!readInfo || StreamExtractor.ExtractorType == ExtractorType.MSS)
|
||||||
{
|
{
|
||||||
var seg = segments.First();
|
var seg = segments.First();
|
||||||
|
@ -218,7 +218,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
task.Increment(1);
|
task.Increment(1);
|
||||||
if (result != null && result.Success)
|
if (result != null && result.Success)
|
||||||
{
|
{
|
||||||
//修复MSS init
|
// 修复MSS init
|
||||||
if (StreamExtractor.ExtractorType == ExtractorType.MSS)
|
if (StreamExtractor.ExtractorType == ExtractorType.MSS)
|
||||||
{
|
{
|
||||||
var processor = new MSSMoovProcessor(streamSpec);
|
var processor = new MSSMoovProcessor(streamSpec);
|
||||||
|
@ -226,7 +226,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
await File.WriteAllBytesAsync(FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath, header);
|
await File.WriteAllBytesAsync(FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath, header);
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
//需要重新解密init
|
// 需要重新解密init
|
||||||
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
|
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
||||||
|
@ -236,7 +236,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//读取init信息
|
// 读取init信息
|
||||||
if (string.IsNullOrEmpty(currentKID))
|
if (string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID;
|
currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID;
|
||||||
|
@ -245,9 +245,9 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
||||||
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt);
|
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt);
|
||||||
}
|
}
|
||||||
//从文件读取KEY
|
// 从文件读取KEY
|
||||||
await SearchKeyAsync(currentKID);
|
await SearchKeyAsync(currentKID);
|
||||||
//实时解密
|
// 实时解密
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
|
@ -262,7 +262,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
if (!readInfo)
|
if (!readInfo)
|
||||||
{
|
{
|
||||||
//ffmpeg读取信息
|
// ffmpeg读取信息
|
||||||
Logger.WarnMarkUp(ResString.readingInfo);
|
Logger.WarnMarkUp(ResString.readingInfo);
|
||||||
mediaInfos = await MediainfoUtil.ReadInfoAsync(DownloaderConfig.MyOptions.FFmpegBinaryPath!, result!.ActualFilePath);
|
mediaInfos = await MediainfoUtil.ReadInfoAsync(DownloaderConfig.MyOptions.FFmpegBinaryPath!, result!.ActualFilePath);
|
||||||
mediaInfos.ForEach(info => Logger.InfoMarkUp(info.ToStringMarkUp()));
|
mediaInfos.ForEach(info => Logger.InfoMarkUp(info.ToStringMarkUp()));
|
||||||
|
@ -272,7 +272,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//开始下载
|
// 开始下载
|
||||||
var options = new ParallelOptions()
|
var options = new ParallelOptions()
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = DownloaderConfig.MyOptions.ThreadCount
|
MaxDegreeOfParallelism = DownloaderConfig.MyOptions.ThreadCount
|
||||||
|
@ -285,7 +285,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
FileDic[seg] = result;
|
FileDic[seg] = result;
|
||||||
if (result != null && result.Success)
|
if (result != null && result.Success)
|
||||||
task.Increment(1);
|
task.Increment(1);
|
||||||
//实时解密
|
// 实时解密
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && result != null && result.Success && !string.IsNullOrEmpty(currentKID))
|
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && result != null && result.Success && !string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
|
@ -300,7 +300,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//修改输出后缀
|
// 修改输出后缀
|
||||||
var outputExt = "." + streamSpec.Extension;
|
var outputExt = "." + streamSpec.Extension;
|
||||||
if (streamSpec.Extension == null) outputExt = ".ts";
|
if (streamSpec.Extension == null) outputExt = ".ts";
|
||||||
else if (streamSpec.MediaType == MediaType.AUDIO && (streamSpec.Extension == "m4s" || streamSpec.Extension == "mp4")) outputExt = ".m4a";
|
else if (streamSpec.MediaType == MediaType.AUDIO && (streamSpec.Extension == "m4s" || streamSpec.Extension == "mp4")) outputExt = ".m4a";
|
||||||
|
@ -313,7 +313,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
var output = Path.Combine(saveDir, saveName + outputExt);
|
var output = Path.Combine(saveDir, saveName + outputExt);
|
||||||
|
|
||||||
//检测目标文件是否存在
|
// 检测目标文件是否存在
|
||||||
while (File.Exists(output))
|
while (File.Exists(output))
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp($"{Path.GetFileName(output)} => {Path.GetFileName(output = Path.ChangeExtension(output, $"copy" + Path.GetExtension(output)))}");
|
Logger.WarnMarkUp($"{Path.GetFileName(output)} => {Path.GetFileName(output = Path.ChangeExtension(output, $"copy" + Path.GetExtension(output)))}");
|
||||||
|
@ -322,39 +322,39 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
if (!string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0 && mp4InitFile != "")
|
if (!string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0 && mp4InitFile != "")
|
||||||
{
|
{
|
||||||
File.Delete(mp4InitFile);
|
File.Delete(mp4InitFile);
|
||||||
//shaka实时解密不需要init文件用于合并
|
// shaka实时解密不需要init文件用于合并
|
||||||
if (DownloaderConfig.MyOptions.UseShakaPackager)
|
if (DownloaderConfig.MyOptions.UseShakaPackager)
|
||||||
{
|
{
|
||||||
FileDic!.Remove(streamSpec.Playlist!.MediaInit, out _);
|
FileDic!.Remove(streamSpec.Playlist!.MediaInit, out _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//校验分片数量
|
// 校验分片数量
|
||||||
if (DownloaderConfig.MyOptions.CheckSegmentsCount && FileDic.Values.Any(s => s == null))
|
if (DownloaderConfig.MyOptions.CheckSegmentsCount && FileDic.Values.Any(s => s == null))
|
||||||
{
|
{
|
||||||
Logger.ErrorMarkUp(ResString.segmentCountCheckNotPass, totalCount, FileDic.Values.Where(s => s != null).Count());
|
Logger.ErrorMarkUp(ResString.segmentCountCheckNotPass, totalCount, FileDic.Values.Where(s => s != null).Count());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//移除无效片段
|
// 移除无效片段
|
||||||
var badKeys = FileDic.Where(i => i.Value == null).Select(i => i.Key);
|
var badKeys = FileDic.Where(i => i.Value == null).Select(i => i.Key);
|
||||||
foreach (var badKey in badKeys)
|
foreach (var badKey in badKeys)
|
||||||
{
|
{
|
||||||
FileDic!.Remove(badKey, out _);
|
FileDic!.Remove(badKey, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
//校验完整性
|
// 校验完整性
|
||||||
if (DownloaderConfig.CheckContentLength && FileDic.Values.Any(a => a!.Success == false))
|
if (DownloaderConfig.CheckContentLength && FileDic.Values.Any(a => a!.Success == false))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//自动修复VTT raw字幕
|
// 自动修复VTT raw字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("vtt"))
|
&& streamSpec.Extension != null && streamSpec.Extension.Contains("vtt"))
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp(ResString.fixingVTT);
|
Logger.WarnMarkUp(ResString.fixingVTT);
|
||||||
//排序字幕并修正时间戳
|
// 排序字幕并修正时间戳
|
||||||
bool first = true;
|
bool first = true;
|
||||||
var finalVtt = new WebVttSub();
|
var finalVtt = new WebVttSub();
|
||||||
var keys = FileDic.Keys.OrderBy(k => k.Index);
|
var keys = FileDic.Keys.OrderBy(k => k.Index);
|
||||||
|
@ -362,7 +362,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
{
|
{
|
||||||
var vttContent = File.ReadAllText(FileDic[seg]!.ActualFilePath);
|
var vttContent = File.ReadAllText(FileDic[seg]!.ActualFilePath);
|
||||||
var vtt = WebVttSub.Parse(vttContent);
|
var vtt = WebVttSub.Parse(vttContent);
|
||||||
//手动计算MPEGTS
|
// 手动计算MPEGTS
|
||||||
if (finalVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
if (finalVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||||
{
|
{
|
||||||
vtt.MpegtsTimestamp = 90000 * (long)(skippedDur + keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
|
vtt.MpegtsTimestamp = 90000 * (long)(skippedDur + keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
|
||||||
|
@ -370,16 +370,16 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
if (first) { finalVtt = vtt; first = false; }
|
if (first) { finalVtt = vtt; first = false; }
|
||||||
else finalVtt.AddCuesFromOne(vtt);
|
else finalVtt.AddCuesFromOne(vtt);
|
||||||
}
|
}
|
||||||
//写出字幕
|
// 写出字幕
|
||||||
var files = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).ToArray();
|
var files = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).ToArray();
|
||||||
foreach (var item in files) File.Delete(item);
|
foreach (var item in files) File.Delete(item);
|
||||||
FileDic.Clear();
|
FileDic.Clear();
|
||||||
var index = 0;
|
var index = 0;
|
||||||
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
||||||
//设置字幕偏移
|
// 设置字幕偏移
|
||||||
finalVtt.LeftShiftTime(TimeSpan.FromSeconds(skippedDur));
|
finalVtt.LeftShiftTime(TimeSpan.FromSeconds(skippedDur));
|
||||||
var subContentFixed = finalVtt.ToVtt();
|
var subContentFixed = finalVtt.ToVtt();
|
||||||
//转换字幕格式
|
// 转换字幕格式
|
||||||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||||
{
|
{
|
||||||
path = Path.ChangeExtension(path, ".srt");
|
path = Path.ChangeExtension(path, ".srt");
|
||||||
|
@ -393,7 +393,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//自动修复VTT mp4字幕
|
// 自动修复VTT mp4字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||||
&& streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s"))
|
&& streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s"))
|
||||||
{
|
{
|
||||||
|
@ -405,17 +405,17 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
Logger.WarnMarkUp(ResString.fixingVTTmp4);
|
Logger.WarnMarkUp(ResString.fixingVTTmp4);
|
||||||
var mp4s = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).Where(p => p.EndsWith(".m4s")).ToArray();
|
var mp4s = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).Where(p => p.EndsWith(".m4s")).ToArray();
|
||||||
var finalVtt = MP4VttUtil.ExtractSub(mp4s, timescale);
|
var finalVtt = MP4VttUtil.ExtractSub(mp4s, timescale);
|
||||||
//写出字幕
|
// 写出字幕
|
||||||
var firstKey = FileDic.Keys.First();
|
var firstKey = FileDic.Keys.First();
|
||||||
var files = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).ToArray();
|
var files = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).ToArray();
|
||||||
foreach (var item in files) File.Delete(item);
|
foreach (var item in files) File.Delete(item);
|
||||||
FileDic.Clear();
|
FileDic.Clear();
|
||||||
var index = 0;
|
var index = 0;
|
||||||
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
||||||
//设置字幕偏移
|
// 设置字幕偏移
|
||||||
finalVtt.LeftShiftTime(TimeSpan.FromSeconds(skippedDur));
|
finalVtt.LeftShiftTime(TimeSpan.FromSeconds(skippedDur));
|
||||||
var subContentFixed = finalVtt.ToVtt();
|
var subContentFixed = finalVtt.ToVtt();
|
||||||
//转换字幕格式
|
// 转换字幕格式
|
||||||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||||
{
|
{
|
||||||
path = Path.ChangeExtension(path, ".srt");
|
path = Path.ChangeExtension(path, ".srt");
|
||||||
|
@ -430,7 +430,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//自动修复TTML raw字幕
|
// 自动修复TTML raw字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("ttml"))
|
&& streamSpec.Extension != null && streamSpec.Extension.Contains("ttml"))
|
||||||
{
|
{
|
||||||
|
@ -441,7 +441,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
foreach (var seg in keys)
|
foreach (var seg in keys)
|
||||||
{
|
{
|
||||||
var vtt = MP4TtmlUtil.ExtractFromTTML(FileDic[seg]!.ActualFilePath, 0);
|
var vtt = MP4TtmlUtil.ExtractFromTTML(FileDic[seg]!.ActualFilePath, 0);
|
||||||
//手动计算MPEGTS
|
// 手动计算MPEGTS
|
||||||
if (finalVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
if (finalVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||||
{
|
{
|
||||||
vtt.MpegtsTimestamp = 90000 * (long)(skippedDur + keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
|
vtt.MpegtsTimestamp = 90000 * (long)(skippedDur + keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
|
||||||
|
@ -449,21 +449,21 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
if (first) { finalVtt = vtt; first = false; }
|
if (first) { finalVtt = vtt; first = false; }
|
||||||
else finalVtt.AddCuesFromOne(vtt);
|
else finalVtt.AddCuesFromOne(vtt);
|
||||||
}
|
}
|
||||||
//写出字幕
|
// 写出字幕
|
||||||
var firstKey = FileDic.Keys.First();
|
var firstKey = FileDic.Keys.First();
|
||||||
var files = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).ToArray();
|
var files = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).ToArray();
|
||||||
|
|
||||||
//处理图形字幕
|
// 处理图形字幕
|
||||||
await SubtitleUtil.TryWriteImagePngsAsync(finalVtt, tmpDir);
|
await SubtitleUtil.TryWriteImagePngsAsync(finalVtt, tmpDir);
|
||||||
|
|
||||||
foreach (var item in files) File.Delete(item);
|
foreach (var item in files) File.Delete(item);
|
||||||
FileDic.Clear();
|
FileDic.Clear();
|
||||||
var index = 0;
|
var index = 0;
|
||||||
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
||||||
//设置字幕偏移
|
// 设置字幕偏移
|
||||||
finalVtt.LeftShiftTime(TimeSpan.FromSeconds(skippedDur));
|
finalVtt.LeftShiftTime(TimeSpan.FromSeconds(skippedDur));
|
||||||
var subContentFixed = finalVtt.ToVtt();
|
var subContentFixed = finalVtt.ToVtt();
|
||||||
//转换字幕格式
|
// 转换字幕格式
|
||||||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||||
{
|
{
|
||||||
path = Path.ChangeExtension(path, ".srt");
|
path = Path.ChangeExtension(path, ".srt");
|
||||||
|
@ -477,23 +477,23 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//自动修复TTML mp4字幕
|
// 自动修复TTML mp4字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")
|
&& streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")
|
||||||
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp"))
|
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp"))
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp(ResString.fixingTTMLmp4);
|
Logger.WarnMarkUp(ResString.fixingTTMLmp4);
|
||||||
//sawTtml暂时不判断
|
// sawTtml暂时不判断
|
||||||
//var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault();
|
// var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault();
|
||||||
//var iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath);
|
// var iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath);
|
||||||
//var sawTtml = MP4TtmlUtil.CheckInit(iniFileBytes);
|
// var sawTtml = MP4TtmlUtil.CheckInit(iniFileBytes);
|
||||||
var first = true;
|
var first = true;
|
||||||
var finalVtt = new WebVttSub();
|
var finalVtt = new WebVttSub();
|
||||||
var keys = FileDic.OrderBy(s => s.Key.Index).Where(v => v.Value!.ActualFilePath.EndsWith(".m4s")).Select(s => s.Key);
|
var keys = FileDic.OrderBy(s => s.Key.Index).Where(v => v.Value!.ActualFilePath.EndsWith(".m4s")).Select(s => s.Key);
|
||||||
foreach (var seg in keys)
|
foreach (var seg in keys)
|
||||||
{
|
{
|
||||||
var vtt = MP4TtmlUtil.ExtractFromMp4(FileDic[seg]!.ActualFilePath, 0);
|
var vtt = MP4TtmlUtil.ExtractFromMp4(FileDic[seg]!.ActualFilePath, 0);
|
||||||
//手动计算MPEGTS
|
// 手动计算MPEGTS
|
||||||
if (finalVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
if (finalVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||||
{
|
{
|
||||||
vtt.MpegtsTimestamp = 90000 * (long)(skippedDur + keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
|
vtt.MpegtsTimestamp = 90000 * (long)(skippedDur + keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
|
||||||
|
@ -502,21 +502,21 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
else finalVtt.AddCuesFromOne(vtt);
|
else finalVtt.AddCuesFromOne(vtt);
|
||||||
}
|
}
|
||||||
|
|
||||||
//写出字幕
|
// 写出字幕
|
||||||
var firstKey = FileDic.Keys.First();
|
var firstKey = FileDic.Keys.First();
|
||||||
var files = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).ToArray();
|
var files = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).ToArray();
|
||||||
|
|
||||||
//处理图形字幕
|
// 处理图形字幕
|
||||||
await SubtitleUtil.TryWriteImagePngsAsync(finalVtt, tmpDir);
|
await SubtitleUtil.TryWriteImagePngsAsync(finalVtt, tmpDir);
|
||||||
|
|
||||||
foreach (var item in files) File.Delete(item);
|
foreach (var item in files) File.Delete(item);
|
||||||
FileDic.Clear();
|
FileDic.Clear();
|
||||||
var index = 0;
|
var index = 0;
|
||||||
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
||||||
//设置字幕偏移
|
// 设置字幕偏移
|
||||||
finalVtt.LeftShiftTime(TimeSpan.FromSeconds(skippedDur));
|
finalVtt.LeftShiftTime(TimeSpan.FromSeconds(skippedDur));
|
||||||
var subContentFixed = finalVtt.ToVtt();
|
var subContentFixed = finalVtt.ToVtt();
|
||||||
//转换字幕格式
|
// 转换字幕格式
|
||||||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||||
{
|
{
|
||||||
path = Path.ChangeExtension(path, ".srt");
|
path = Path.ChangeExtension(path, ".srt");
|
||||||
|
@ -531,10 +531,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mergeSuccess = false;
|
bool mergeSuccess = false;
|
||||||
//合并
|
// 合并
|
||||||
if (!DownloaderConfig.MyOptions.SkipMerge)
|
if (!DownloaderConfig.MyOptions.SkipMerge)
|
||||||
{
|
{
|
||||||
//字幕也使用二进制合并
|
// 字幕也使用二进制合并
|
||||||
if (DownloaderConfig.MyOptions.BinaryMerge || streamSpec.MediaType == MediaType.SUBTITLES)
|
if (DownloaderConfig.MyOptions.BinaryMerge || streamSpec.MediaType == MediaType.SUBTITLES)
|
||||||
{
|
{
|
||||||
Logger.InfoMarkUp(ResString.binaryMerge);
|
Logger.InfoMarkUp(ResString.binaryMerge);
|
||||||
|
@ -544,17 +544,17 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//ffmpeg合并
|
// ffmpeg合并
|
||||||
var files = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).ToArray();
|
var files = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).ToArray();
|
||||||
Logger.InfoMarkUp(ResString.ffmpegMerge);
|
Logger.InfoMarkUp(ResString.ffmpegMerge);
|
||||||
var ext = streamSpec.MediaType == MediaType.AUDIO ? "m4a" : "mp4";
|
var ext = streamSpec.MediaType == MediaType.AUDIO ? "m4a" : "mp4";
|
||||||
var ffOut = Path.Combine(Path.GetDirectoryName(output)!, Path.GetFileNameWithoutExtension(output) + $".{ext}");
|
var ffOut = Path.Combine(Path.GetDirectoryName(output)!, Path.GetFileNameWithoutExtension(output) + $".{ext}");
|
||||||
//检测目标文件是否存在
|
// 检测目标文件是否存在
|
||||||
while (File.Exists(ffOut))
|
while (File.Exists(ffOut))
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp($"{Path.GetFileName(ffOut)} => {Path.GetFileName(ffOut = Path.ChangeExtension(ffOut, $"copy" + Path.GetExtension(ffOut)))}");
|
Logger.WarnMarkUp($"{Path.GetFileName(ffOut)} => {Path.GetFileName(ffOut = Path.ChangeExtension(ffOut, $"copy" + Path.GetExtension(ffOut)))}");
|
||||||
}
|
}
|
||||||
//大于1800分片,需要分步骤合并
|
// 大于1800分片,需要分步骤合并
|
||||||
if (files.Length >= 1800)
|
if (files.Length >= 1800)
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp(ResString.partMerge);
|
Logger.WarnMarkUp(ResString.partMerge);
|
||||||
|
@ -573,7 +573,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//删除临时文件夹
|
// 删除临时文件夹
|
||||||
if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && mergeSuccess)
|
if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && mergeSuccess)
|
||||||
{
|
{
|
||||||
var files = FileDic.Values.Select(v => v!.ActualFilePath);
|
var files = FileDic.Values.Select(v => v!.ActualFilePath);
|
||||||
|
@ -584,7 +584,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
OtherUtil.SafeDeleteDir(tmpDir);
|
OtherUtil.SafeDeleteDir(tmpDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
//重新读取init信息
|
// 重新读取init信息
|
||||||
if (mergeSuccess && totalCount >= 1 && string.IsNullOrEmpty(currentKID) && streamSpec.Playlist!.MediaParts.First().MediaSegments.First().EncryptInfo.Method != Common.Enum.EncryptMethod.NONE)
|
if (mergeSuccess && totalCount >= 1 && string.IsNullOrEmpty(currentKID) && streamSpec.Playlist!.MediaParts.First().MediaSegments.First().EncryptInfo.Method != Common.Enum.EncryptMethod.NONE)
|
||||||
{
|
{
|
||||||
currentKID = MP4DecryptUtil.GetMP4Info(output).KID;
|
currentKID = MP4DecryptUtil.GetMP4Info(output).KID;
|
||||||
|
@ -592,11 +592,11 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
||||||
currentKID = MP4DecryptUtil.ReadInitShaka(output, mp4decrypt);
|
currentKID = MP4DecryptUtil.ReadInitShaka(output, mp4decrypt);
|
||||||
}
|
}
|
||||||
//从文件读取KEY
|
// 从文件读取KEY
|
||||||
await SearchKeyAsync(currentKID);
|
await SearchKeyAsync(currentKID);
|
||||||
}
|
}
|
||||||
|
|
||||||
//调用mp4decrypt解密
|
// 调用mp4decrypt解密
|
||||||
if (mergeSuccess && File.Exists(output) && !string.IsNullOrEmpty(currentKID) && !DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0)
|
if (mergeSuccess && File.Exists(output) && !string.IsNullOrEmpty(currentKID) && !DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0)
|
||||||
{
|
{
|
||||||
var enc = output;
|
var enc = output;
|
||||||
|
@ -611,7 +611,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//记录所有文件信息
|
// 记录所有文件信息
|
||||||
if (File.Exists(output))
|
if (File.Exists(output))
|
||||||
{
|
{
|
||||||
OutputFiles.Add(new OutputFile()
|
OutputFiles.Add(new OutputFile()
|
||||||
|
@ -630,20 +630,20 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
public async Task<bool> StartDownloadAsync()
|
public async Task<bool> StartDownloadAsync()
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<int, SpeedContainer> SpeedContainerDic = new(); //速度计算
|
ConcurrentDictionary<int, SpeedContainer> SpeedContainerDic = new(); // 速度计算
|
||||||
ConcurrentDictionary<StreamSpec, bool?> Results = new();
|
ConcurrentDictionary<StreamSpec, bool?> Results = new();
|
||||||
|
|
||||||
var progress = CustomAnsiConsole.Console.Progress().AutoClear(true);
|
var progress = CustomAnsiConsole.Console.Progress().AutoClear(true);
|
||||||
progress.AutoRefresh = DownloaderConfig.MyOptions.LogLevel != LogLevel.OFF;
|
progress.AutoRefresh = DownloaderConfig.MyOptions.LogLevel != LogLevel.OFF;
|
||||||
|
|
||||||
//进度条的列定义
|
// 进度条的列定义
|
||||||
var progressColumns = new ProgressColumn[]
|
var progressColumns = new ProgressColumn[]
|
||||||
{
|
{
|
||||||
new TaskDescriptionColumn() { Alignment = Justify.Left },
|
new TaskDescriptionColumn() { Alignment = Justify.Left },
|
||||||
new ProgressBarColumn(){ Width = 30 },
|
new ProgressBarColumn(){ Width = 30 },
|
||||||
new MyPercentageColumn(),
|
new MyPercentageColumn(),
|
||||||
new DownloadStatusColumn(SpeedContainerDic),
|
new DownloadStatusColumn(SpeedContainerDic),
|
||||||
new DownloadSpeedColumn(SpeedContainerDic), //速度计算
|
new DownloadSpeedColumn(SpeedContainerDic), // 速度计算
|
||||||
new RemainingTimeColumn(),
|
new RemainingTimeColumn(),
|
||||||
new SpinnerColumn(),
|
new SpinnerColumn(),
|
||||||
};
|
};
|
||||||
|
@ -659,13 +659,13 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
await progress.StartAsync(async ctx =>
|
await progress.StartAsync(async ctx =>
|
||||||
{
|
{
|
||||||
//创建任务
|
// 创建任务
|
||||||
var dic = SelectedSteams.Select(item =>
|
var dic = SelectedSteams.Select(item =>
|
||||||
{
|
{
|
||||||
var description = item.ToShortShortString();
|
var description = item.ToShortShortString();
|
||||||
var task = ctx.AddTask(description, autoStart: false);
|
var task = ctx.AddTask(description, autoStart: false);
|
||||||
SpeedContainerDic[task.Id] = new SpeedContainer(); //速度计算
|
SpeedContainerDic[task.Id] = new SpeedContainer(); // 速度计算
|
||||||
//限速设置
|
// 限速设置
|
||||||
if (DownloaderConfig.MyOptions.MaxSpeed != null)
|
if (DownloaderConfig.MyOptions.MaxSpeed != null)
|
||||||
{
|
{
|
||||||
SpeedContainerDic[task.Id].SpeedLimit = DownloaderConfig.MyOptions.MaxSpeed.Value;
|
SpeedContainerDic[task.Id].SpeedLimit = DownloaderConfig.MyOptions.MaxSpeed.Value;
|
||||||
|
@ -675,19 +675,19 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
if (!DownloaderConfig.MyOptions.ConcurrentDownload)
|
if (!DownloaderConfig.MyOptions.ConcurrentDownload)
|
||||||
{
|
{
|
||||||
//遍历,顺序下载
|
// 遍历,顺序下载
|
||||||
foreach (var kp in dic)
|
foreach (var kp in dic)
|
||||||
{
|
{
|
||||||
var task = kp.Value;
|
var task = kp.Value;
|
||||||
var result = await DownloadStreamAsync(kp.Key, task, SpeedContainerDic[task.Id]);
|
var result = await DownloadStreamAsync(kp.Key, task, SpeedContainerDic[task.Id]);
|
||||||
Results[kp.Key] = result;
|
Results[kp.Key] = result;
|
||||||
//失败不再下载后续
|
// 失败不再下载后续
|
||||||
if (!result) break;
|
if (!result) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//并发下载
|
// 并发下载
|
||||||
await Parallel.ForEachAsync(dic, async (kp, _) =>
|
await Parallel.ForEachAsync(dic, async (kp, _) =>
|
||||||
{
|
{
|
||||||
var task = kp.Value;
|
var task = kp.Value;
|
||||||
|
@ -699,7 +699,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
var success = Results.Values.All(v => v == true);
|
var success = Results.Values.All(v => v == true);
|
||||||
|
|
||||||
//删除临时文件夹
|
// 删除临时文件夹
|
||||||
if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && success)
|
if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && success)
|
||||||
{
|
{
|
||||||
foreach (var item in StreamExtractor.RawFiles)
|
foreach (var item in StreamExtractor.RawFiles)
|
||||||
|
@ -710,12 +710,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
OtherUtil.SafeDeleteDir(DownloaderConfig.DirPrefix);
|
OtherUtil.SafeDeleteDir(DownloaderConfig.DirPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
//混流
|
// 混流
|
||||||
if (success && DownloaderConfig.MyOptions.MuxAfterDone && OutputFiles.Count > 0)
|
if (success && DownloaderConfig.MyOptions.MuxAfterDone && OutputFiles.Count > 0)
|
||||||
{
|
{
|
||||||
OutputFiles = OutputFiles.OrderBy(o => o.Index).ToList();
|
OutputFiles = OutputFiles.OrderBy(o => o.Index).ToList();
|
||||||
//是否跳过字幕
|
// 是否跳过字幕
|
||||||
if (DownloaderConfig.MyOptions.MuxOptions.SkipSubtitle)
|
if (DownloaderConfig.MyOptions.MuxOptions!.SkipSubtitle)
|
||||||
{
|
{
|
||||||
OutputFiles = OutputFiles.Where(o => o.MediaType != MediaType.SUBTITLES).ToList();
|
OutputFiles = OutputFiles.Where(o => o.MediaType != MediaType.SUBTITLES).ToList();
|
||||||
}
|
}
|
||||||
|
@ -733,7 +733,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var result = false;
|
var result = false;
|
||||||
if (DownloaderConfig.MyOptions.MuxOptions.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MyOptions.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
|
if (DownloaderConfig.MyOptions.MuxOptions.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MyOptions.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
|
||||||
else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MyOptions.MuxOptions.MuxFormat, !DownloaderConfig.MyOptions.NoDateInfo);
|
else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MyOptions.MuxOptions.MuxFormat, !DownloaderConfig.MyOptions.NoDateInfo);
|
||||||
//完成后删除各轨道文件
|
// 完成后删除各轨道文件
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
if (!DownloaderConfig.MyOptions.MuxOptions.KeepFiles)
|
if (!DownloaderConfig.MyOptions.MuxOptions.KeepFiles)
|
||||||
|
@ -749,7 +749,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
success = false;
|
success = false;
|
||||||
Logger.ErrorMarkUp($"Mux failed");
|
Logger.ErrorMarkUp($"Mux failed");
|
||||||
}
|
}
|
||||||
//判断是否要改名
|
// 判断是否要改名
|
||||||
var newPath = Path.ChangeExtension(outPath, ext);
|
var newPath = Path.ChangeExtension(outPath, ext);
|
||||||
if (result && !File.Exists(newPath))
|
if (result && !File.Exists(newPath))
|
||||||
{
|
{
|
||||||
|
@ -760,5 +760,4 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -12,21 +12,15 @@ using N_m3u8DL_RE.Parser;
|
||||||
using N_m3u8DL_RE.Parser.Mp4;
|
using N_m3u8DL_RE.Parser.Mp4;
|
||||||
using N_m3u8DL_RE.Util;
|
using N_m3u8DL_RE.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Threading.Tasks.Dataflow;
|
using System.Threading.Tasks.Dataflow;
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.DownloadManager
|
namespace N_m3u8DL_RE.DownloadManager;
|
||||||
|
|
||||||
|
internal class SimpleLiveRecordManager2
|
||||||
{
|
{
|
||||||
internal class SimpleLiveRecordManager2
|
|
||||||
{
|
|
||||||
IDownloader Downloader;
|
IDownloader Downloader;
|
||||||
DownloaderConfig DownloaderConfig;
|
DownloaderConfig DownloaderConfig;
|
||||||
StreamExtractor StreamExtractor;
|
StreamExtractor StreamExtractor;
|
||||||
|
@ -35,16 +29,16 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
List<OutputFile> OutputFiles = new();
|
List<OutputFile> OutputFiles = new();
|
||||||
DateTime? PublishDateTime;
|
DateTime? PublishDateTime;
|
||||||
bool STOP_FLAG = false;
|
bool STOP_FLAG = false;
|
||||||
int WAIT_SEC = 0; //刷新间隔
|
int WAIT_SEC = 0; // 刷新间隔
|
||||||
ConcurrentDictionary<int, int> RecordedDurDic = new(); //已录制时长
|
ConcurrentDictionary<int, int> RecordedDurDic = new(); // 已录制时长
|
||||||
ConcurrentDictionary<int, int> RefreshedDurDic = new(); //已刷新出的时长
|
ConcurrentDictionary<int, int> RefreshedDurDic = new(); // 已刷新出的时长
|
||||||
ConcurrentDictionary<int, BufferBlock<List<MediaSegment>>> BlockDic = new(); //各流的Block
|
ConcurrentDictionary<int, BufferBlock<List<MediaSegment>>> BlockDic = new(); // 各流的Block
|
||||||
ConcurrentDictionary<int, bool> SamePathDic = new(); //各流是否allSamePath
|
ConcurrentDictionary<int, bool> SamePathDic = new(); // 各流是否allSamePath
|
||||||
ConcurrentDictionary<int, bool> RecordLimitReachedDic = new(); //各流是否达到上限
|
ConcurrentDictionary<int, bool> RecordLimitReachedDic = new(); // 各流是否达到上限
|
||||||
ConcurrentDictionary<int, string> LastFileNameDic = new(); //上次下载的文件名
|
ConcurrentDictionary<int, string> LastFileNameDic = new(); // 上次下载的文件名
|
||||||
ConcurrentDictionary<int, long> MaxIndexDic = new(); //最大Index
|
ConcurrentDictionary<int, long> MaxIndexDic = new(); // 最大Index
|
||||||
ConcurrentDictionary<int, long> DateTimeDic = new(); //上次下载的dateTime
|
ConcurrentDictionary<int, long> DateTimeDic = new(); // 上次下载的dateTime
|
||||||
CancellationTokenSource CancellationTokenSource = new(); //取消Wait
|
CancellationTokenSource CancellationTokenSource = new(); // 取消Wait
|
||||||
|
|
||||||
private readonly object lockObj = new object();
|
private readonly object lockObj = new object();
|
||||||
TimeSpan? audioStart = null;
|
TimeSpan? audioStart = null;
|
||||||
|
@ -58,7 +52,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
SelectedSteams = selectedSteams;
|
SelectedSteams = selectedSteams;
|
||||||
}
|
}
|
||||||
|
|
||||||
//从文件读取KEY
|
// 从文件读取KEY
|
||||||
private async Task SearchKeyAsync(string? currentKID)
|
private async Task SearchKeyAsync(string? currentKID)
|
||||||
{
|
{
|
||||||
var _key = await MP4DecryptUtil.SearchKeyFromFileAsync(DownloaderConfig.MyOptions.KeyTextFile, currentKID);
|
var _key = await MP4DecryptUtil.SearchKeyFromFileAsync(DownloaderConfig.MyOptions.KeyTextFile, currentKID);
|
||||||
|
@ -149,17 +143,17 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
private async Task<bool> RecordStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer, BufferBlock<List<MediaSegment>> source)
|
private async Task<bool> RecordStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer, BufferBlock<List<MediaSegment>> source)
|
||||||
{
|
{
|
||||||
var baseTimestamp = PublishDateTime == null ? 0L : (long)(PublishDateTime.Value.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
|
var baseTimestamp = PublishDateTime == null ? 0L : (long)(PublishDateTime.Value.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
|
||||||
//mp4decrypt
|
// mp4decrypt
|
||||||
var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
|
var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
|
||||||
var mp4InitFile = "";
|
var mp4InitFile = "";
|
||||||
var currentKID = "";
|
var currentKID = "";
|
||||||
var readInfo = false; //是否读取过
|
var readInfo = false; // 是否读取过
|
||||||
bool useAACFilter = false; //ffmpeg合并flag
|
bool useAACFilter = false; // ffmpeg合并flag
|
||||||
bool initDownloaded = false; //是否下载过init文件
|
bool initDownloaded = false; // 是否下载过init文件
|
||||||
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
|
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
|
||||||
List<Mediainfo> mediaInfos = new();
|
List<Mediainfo> mediaInfos = new();
|
||||||
Stream? fileOutputStream = null;
|
Stream? fileOutputStream = null;
|
||||||
WebVttSub currentVtt = new(); //字幕流始终维护一个实例
|
WebVttSub currentVtt = new(); // 字幕流始终维护一个实例
|
||||||
bool firstSub = true;
|
bool firstSub = true;
|
||||||
task.StartTask();
|
task.StartTask();
|
||||||
|
|
||||||
|
@ -173,26 +167,26 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}");
|
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}");
|
||||||
|
|
||||||
//创建文件夹
|
// 创建文件夹
|
||||||
if (!Directory.Exists(tmpDir)) Directory.CreateDirectory(tmpDir);
|
if (!Directory.Exists(tmpDir)) Directory.CreateDirectory(tmpDir);
|
||||||
if (!Directory.Exists(saveDir)) Directory.CreateDirectory(saveDir);
|
if (!Directory.Exists(saveDir)) Directory.CreateDirectory(saveDir);
|
||||||
|
|
||||||
while (true && await source.OutputAvailableAsync())
|
while (true && await source.OutputAvailableAsync())
|
||||||
{
|
{
|
||||||
//接收新片段 且总是拿全部未处理的片段
|
// 接收新片段 且总是拿全部未处理的片段
|
||||||
//有时每次只有很少的片段,但是之前的片段下载慢,导致后面还没下载的片段都失效了
|
// 有时每次只有很少的片段,但是之前的片段下载慢,导致后面还没下载的片段都失效了
|
||||||
//TryReceiveAll可以稍微缓解一下
|
// TryReceiveAll可以稍微缓解一下
|
||||||
source.TryReceiveAll(out IList<List<MediaSegment>>? segmentsList);
|
source.TryReceiveAll(out IList<List<MediaSegment>>? segmentsList);
|
||||||
var segments = segmentsList!.SelectMany(s => s);
|
var segments = segmentsList!.SelectMany(s => s);
|
||||||
if (segments == null || !segments.Any()) continue;
|
if (segments == null || !segments.Any()) continue;
|
||||||
var segmentsDuration = segments.Sum(s => s.Duration);
|
var segmentsDuration = segments.Sum(s => s.Duration);
|
||||||
Logger.DebugMarkUp(string.Join(",", segments.Select(sss => GetSegmentName(sss, false, false))));
|
Logger.DebugMarkUp(string.Join(",", segments.Select(sss => GetSegmentName(sss, false, false))));
|
||||||
|
|
||||||
//下载init
|
// 下载init
|
||||||
if (!initDownloaded && streamSpec.Playlist?.MediaInit != null)
|
if (!initDownloaded && streamSpec.Playlist?.MediaInit != null)
|
||||||
{
|
{
|
||||||
task.MaxValue += 1;
|
task.MaxValue += 1;
|
||||||
//对于fMP4,自动开启二进制合并
|
// 对于fMP4,自动开启二进制合并
|
||||||
if (!DownloaderConfig.MyOptions.BinaryMerge && streamSpec.MediaType != MediaType.SUBTITLES)
|
if (!DownloaderConfig.MyOptions.BinaryMerge && streamSpec.MediaType != MediaType.SUBTITLES)
|
||||||
{
|
{
|
||||||
DownloaderConfig.MyOptions.BinaryMerge = true;
|
DownloaderConfig.MyOptions.BinaryMerge = true;
|
||||||
|
@ -209,13 +203,13 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
mp4InitFile = result.ActualFilePath;
|
mp4InitFile = result.ActualFilePath;
|
||||||
task.Increment(1);
|
task.Increment(1);
|
||||||
|
|
||||||
//读取mp4信息
|
// 读取mp4信息
|
||||||
if (result != null && result.Success)
|
if (result != null && result.Success)
|
||||||
{
|
{
|
||||||
currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID;
|
currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID;
|
||||||
//从文件读取KEY
|
// 从文件读取KEY
|
||||||
await SearchKeyAsync(currentKID);
|
await SearchKeyAsync(currentKID);
|
||||||
//实时解密
|
// 实时解密
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID) && StreamExtractor.ExtractorType != ExtractorType.MSS)
|
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID) && StreamExtractor.ExtractorType != ExtractorType.MSS)
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
|
@ -226,7 +220,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//ffmpeg读取信息
|
// ffmpeg读取信息
|
||||||
if (!readInfo)
|
if (!readInfo)
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp(ResString.readingInfo);
|
Logger.WarnMarkUp(ResString.readingInfo);
|
||||||
|
@ -251,12 +245,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
SamePathDic[task.Id] = allSamePath;
|
SamePathDic[task.Id] = allSamePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
//下载第一个分片
|
// 下载第一个分片
|
||||||
if (!readInfo || StreamExtractor.ExtractorType == ExtractorType.MSS)
|
if (!readInfo || StreamExtractor.ExtractorType == ExtractorType.MSS)
|
||||||
{
|
{
|
||||||
var seg = segments.First();
|
var seg = segments.First();
|
||||||
segments = segments.Skip(1);
|
segments = segments.Skip(1);
|
||||||
//获取文件名
|
// 获取文件名
|
||||||
var filename = GetSegmentName(seg, allHasDatetime, SamePathDic[task.Id]);
|
var filename = GetSegmentName(seg, allHasDatetime, SamePathDic[task.Id]);
|
||||||
var index = seg.Index;
|
var index = seg.Index;
|
||||||
var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp");
|
var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp");
|
||||||
|
@ -269,7 +263,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
task.Increment(1);
|
task.Increment(1);
|
||||||
if (result != null && result.Success)
|
if (result != null && result.Success)
|
||||||
{
|
{
|
||||||
//修复MSS init
|
// 修复MSS init
|
||||||
if (StreamExtractor.ExtractorType == ExtractorType.MSS)
|
if (StreamExtractor.ExtractorType == ExtractorType.MSS)
|
||||||
{
|
{
|
||||||
var processor = new MSSMoovProcessor(streamSpec);
|
var processor = new MSSMoovProcessor(streamSpec);
|
||||||
|
@ -277,7 +271,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
await File.WriteAllBytesAsync(FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath, header);
|
await File.WriteAllBytesAsync(FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath, header);
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
//需要重新解密init
|
// 需要重新解密init
|
||||||
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
|
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
||||||
|
@ -287,14 +281,14 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//读取init信息
|
// 读取init信息
|
||||||
if (string.IsNullOrEmpty(currentKID))
|
if (string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID;
|
currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID;
|
||||||
}
|
}
|
||||||
//从文件读取KEY
|
// 从文件读取KEY
|
||||||
await SearchKeyAsync(currentKID);
|
await SearchKeyAsync(currentKID);
|
||||||
//实时解密
|
// 实时解密
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
|
@ -308,7 +302,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
if (!readInfo)
|
if (!readInfo)
|
||||||
{
|
{
|
||||||
//ffmpeg读取信息
|
// ffmpeg读取信息
|
||||||
Logger.WarnMarkUp(ResString.readingInfo);
|
Logger.WarnMarkUp(ResString.readingInfo);
|
||||||
mediaInfos = await MediainfoUtil.ReadInfoAsync(DownloaderConfig.MyOptions.FFmpegBinaryPath!, result!.ActualFilePath);
|
mediaInfos = await MediainfoUtil.ReadInfoAsync(DownloaderConfig.MyOptions.FFmpegBinaryPath!, result!.ActualFilePath);
|
||||||
mediaInfos.ForEach(info => Logger.InfoMarkUp(info.ToStringMarkUp()));
|
mediaInfos.ForEach(info => Logger.InfoMarkUp(info.ToStringMarkUp()));
|
||||||
|
@ -322,14 +316,14 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//开始下载
|
// 开始下载
|
||||||
var options = new ParallelOptions()
|
var options = new ParallelOptions()
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = DownloaderConfig.MyOptions.ThreadCount
|
MaxDegreeOfParallelism = DownloaderConfig.MyOptions.ThreadCount
|
||||||
};
|
};
|
||||||
await Parallel.ForEachAsync(segments, options, async (seg, _) =>
|
await Parallel.ForEachAsync(segments, options, async (seg, _) =>
|
||||||
{
|
{
|
||||||
//获取文件名
|
// 获取文件名
|
||||||
var filename = GetSegmentName(seg, allHasDatetime, SamePathDic[task.Id]);
|
var filename = GetSegmentName(seg, allHasDatetime, SamePathDic[task.Id]);
|
||||||
var index = seg.Index;
|
var index = seg.Index;
|
||||||
var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp");
|
var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp");
|
||||||
|
@ -337,7 +331,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
FileDic[seg] = result;
|
FileDic[seg] = result;
|
||||||
if (result != null && result.Success)
|
if (result != null && result.Success)
|
||||||
task.Increment(1);
|
task.Increment(1);
|
||||||
//实时解密
|
// 实时解密
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && result != null && result.Success && !string.IsNullOrEmpty(currentKID))
|
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && result != null && result.Success && !string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
|
@ -351,11 +345,11 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//自动修复VTT raw字幕
|
// 自动修复VTT raw字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("vtt"))
|
&& streamSpec.Extension != null && streamSpec.Extension.Contains("vtt"))
|
||||||
{
|
{
|
||||||
//排序字幕并修正时间戳
|
// 排序字幕并修正时间戳
|
||||||
var keys = FileDic.Keys.OrderBy(k => k.Index);
|
var keys = FileDic.Keys.OrderBy(k => k.Index);
|
||||||
foreach (var seg in keys)
|
foreach (var seg in keys)
|
||||||
{
|
{
|
||||||
|
@ -367,7 +361,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
var subOffset = audioStart != null ? (long)audioStart.Value.TotalMilliseconds : 0L;
|
var subOffset = audioStart != null ? (long)audioStart.Value.TotalMilliseconds : 0L;
|
||||||
var vtt = WebVttSub.Parse(vttContent, subOffset);
|
var vtt = WebVttSub.Parse(vttContent, subOffset);
|
||||||
//手动计算MPEGTS
|
// 手动计算MPEGTS
|
||||||
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||||
{
|
{
|
||||||
vtt.MpegtsTimestamp = 90000 * (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration);
|
vtt.MpegtsTimestamp = 90000 * (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration);
|
||||||
|
@ -377,7 +371,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//自动修复VTT mp4字幕
|
// 自动修复VTT mp4字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||||
&& streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s"))
|
&& streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s"))
|
||||||
{
|
{
|
||||||
|
@ -400,7 +394,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//自动修复TTML raw字幕
|
// 自动修复TTML raw字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("ttml"))
|
&& streamSpec.Extension != null && streamSpec.Extension.Contains("ttml"))
|
||||||
{
|
{
|
||||||
|
@ -416,7 +410,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
foreach (var seg in keys)
|
foreach (var seg in keys)
|
||||||
{
|
{
|
||||||
var vtt = MP4TtmlUtil.ExtractFromTTML(FileDic[seg]!.ActualFilePath, 0, baseTimestamp);
|
var vtt = MP4TtmlUtil.ExtractFromTTML(FileDic[seg]!.ActualFilePath, 0, baseTimestamp);
|
||||||
//手动计算MPEGTS
|
// 手动计算MPEGTS
|
||||||
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||||
{
|
{
|
||||||
vtt.MpegtsTimestamp = 90000 * (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration);
|
vtt.MpegtsTimestamp = 90000 * (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration);
|
||||||
|
@ -431,7 +425,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
foreach (var seg in keys)
|
foreach (var seg in keys)
|
||||||
{
|
{
|
||||||
var vtt = MP4TtmlUtil.ExtractFromTTML(FileDic[seg]!.ActualFilePath, 0, baseTimestamp);
|
var vtt = MP4TtmlUtil.ExtractFromTTML(FileDic[seg]!.ActualFilePath, 0, baseTimestamp);
|
||||||
//手动计算MPEGTS
|
// 手动计算MPEGTS
|
||||||
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||||
{
|
{
|
||||||
vtt.MpegtsTimestamp = 90000 * (RecordedDurDic[task.Id] + (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
|
vtt.MpegtsTimestamp = 90000 * (RecordedDurDic[task.Id] + (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
|
||||||
|
@ -441,15 +435,15 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//自动修复TTML mp4字幕
|
// 自动修复TTML mp4字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")
|
&& streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")
|
||||||
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp"))
|
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp"))
|
||||||
{
|
{
|
||||||
//sawTtml暂时不判断
|
// sawTtml暂时不判断
|
||||||
//var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault();
|
// var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault();
|
||||||
//var iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath);
|
// var iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath);
|
||||||
//var sawTtml = MP4TtmlUtil.CheckInit(iniFileBytes);
|
// var sawTtml = MP4TtmlUtil.CheckInit(iniFileBytes);
|
||||||
var keys = FileDic.OrderBy(s => s.Key.Index).Where(v => v.Value!.ActualFilePath.EndsWith(".m4s")).Select(s => s.Key);
|
var keys = FileDic.OrderBy(s => s.Key.Index).Where(v => v.Value!.ActualFilePath.EndsWith(".m4s")).Select(s => s.Key);
|
||||||
if (firstSub)
|
if (firstSub)
|
||||||
{
|
{
|
||||||
|
@ -462,7 +456,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
foreach (var seg in keys)
|
foreach (var seg in keys)
|
||||||
{
|
{
|
||||||
var vtt = MP4TtmlUtil.ExtractFromMp4(FileDic[seg]!.ActualFilePath, 0, baseTimestamp);
|
var vtt = MP4TtmlUtil.ExtractFromMp4(FileDic[seg]!.ActualFilePath, 0, baseTimestamp);
|
||||||
//手动计算MPEGTS
|
// 手动计算MPEGTS
|
||||||
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||||
{
|
{
|
||||||
vtt.MpegtsTimestamp = 90000 * (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration);
|
vtt.MpegtsTimestamp = 90000 * (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration);
|
||||||
|
@ -477,7 +471,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
foreach (var seg in keys)
|
foreach (var seg in keys)
|
||||||
{
|
{
|
||||||
var vtt = MP4TtmlUtil.ExtractFromMp4(FileDic[seg]!.ActualFilePath, 0, baseTimestamp);
|
var vtt = MP4TtmlUtil.ExtractFromMp4(FileDic[seg]!.ActualFilePath, 0, baseTimestamp);
|
||||||
//手动计算MPEGTS
|
// 手动计算MPEGTS
|
||||||
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||||
{
|
{
|
||||||
vtt.MpegtsTimestamp = 90000 * (RecordedDurDic[task.Id] + (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
|
vtt.MpegtsTimestamp = 90000 * (RecordedDurDic[task.Id] + (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
|
||||||
|
@ -489,7 +483,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
RecordedDurDic[task.Id] += (int)segmentsDuration;
|
RecordedDurDic[task.Id] += (int)segmentsDuration;
|
||||||
|
|
||||||
/*//写出m3u8
|
/*// 写出m3u8
|
||||||
if (DownloaderConfig.MyOptions.LiveWriteHLS)
|
if (DownloaderConfig.MyOptions.LiveWriteHLS)
|
||||||
{
|
{
|
||||||
var _saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
var _saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
||||||
|
@ -497,10 +491,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
await StreamingUtil.WriteStreamListAsync(FileDic, task.Id, 0, _saveName, _saveDir);
|
await StreamingUtil.WriteStreamListAsync(FileDic, task.Id, 0, _saveName, _saveDir);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
//合并逻辑
|
// 合并逻辑
|
||||||
if (DownloaderConfig.MyOptions.LiveRealTimeMerge)
|
if (DownloaderConfig.MyOptions.LiveRealTimeMerge)
|
||||||
{
|
{
|
||||||
//合并
|
// 合并
|
||||||
var outputExt = "." + streamSpec.Extension;
|
var outputExt = "." + streamSpec.Extension;
|
||||||
if (streamSpec.Extension == null) outputExt = ".ts";
|
if (streamSpec.Extension == null) outputExt = ".ts";
|
||||||
else if (streamSpec.MediaType == MediaType.AUDIO && streamSpec.Extension == "m4s") outputExt = ".m4a";
|
else if (streamSpec.MediaType == MediaType.AUDIO && streamSpec.Extension == "m4s") outputExt = ".m4a";
|
||||||
|
@ -513,17 +507,17 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
var output = Path.Combine(saveDir, saveName + outputExt);
|
var output = Path.Combine(saveDir, saveName + outputExt);
|
||||||
|
|
||||||
//移除无效片段
|
// 移除无效片段
|
||||||
var badKeys = FileDic.Where(i => i.Value == null).Select(i => i.Key);
|
var badKeys = FileDic.Where(i => i.Value == null).Select(i => i.Key);
|
||||||
foreach (var badKey in badKeys)
|
foreach (var badKey in badKeys)
|
||||||
{
|
{
|
||||||
FileDic!.Remove(badKey, out _);
|
FileDic!.Remove(badKey, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
//设置输出流
|
// 设置输出流
|
||||||
if (fileOutputStream == null)
|
if (fileOutputStream == null)
|
||||||
{
|
{
|
||||||
//检测目标文件是否存在
|
// 检测目标文件是否存在
|
||||||
while (File.Exists(output))
|
while (File.Exists(output))
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp($"{Path.GetFileName(output)} => {Path.GetFileName(output = Path.ChangeExtension(output, $"copy" + Path.GetExtension(output)))}");
|
Logger.WarnMarkUp($"{Path.GetFileName(output)} => {Path.GetFileName(output = Path.ChangeExtension(output, $"copy" + Path.GetExtension(output)))}");
|
||||||
|
@ -535,7 +529,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//创建管道
|
// 创建管道
|
||||||
output = Path.ChangeExtension(output, ".ts");
|
output = Path.ChangeExtension(output, ".ts");
|
||||||
var pipeName = $"RE_pipe_{Guid.NewGuid()}";
|
var pipeName = $"RE_pipe_{Guid.NewGuid()}";
|
||||||
fileOutputStream = PipeUtil.CreatePipe(pipeName);
|
fileOutputStream = PipeUtil.CreatePipe(pipeName);
|
||||||
|
@ -548,7 +542,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var t = PipeUtil.StartPipeMuxAsync(DownloaderConfig.MyOptions.FFmpegBinaryPath!, names, output);
|
var t = PipeUtil.StartPipeMuxAsync(DownloaderConfig.MyOptions.FFmpegBinaryPath!, names, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Windows only
|
// Windows only
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
await (fileOutputStream as NamedPipeServerStream)!.WaitForConnectionAsync();
|
await (fileOutputStream as NamedPipeServerStream)!.WaitForConnectionAsync();
|
||||||
}
|
}
|
||||||
|
@ -560,7 +554,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var files = FileDic.Where(f => f.Key != streamSpec.Playlist!.MediaInit).OrderBy(s => s.Key.Index).Select(f => f.Value).Select(v => v!.ActualFilePath).ToArray();
|
var files = FileDic.Where(f => f.Key != streamSpec.Playlist!.MediaInit).OrderBy(s => s.Key.Index).Select(f => f.Value).Select(v => v!.ActualFilePath).ToArray();
|
||||||
if (initResult != null && mp4InitFile != "")
|
if (initResult != null && mp4InitFile != "")
|
||||||
{
|
{
|
||||||
//shaka实时解密不需要init文件用于合并,mp4decrpyt需要
|
// shaka实时解密不需要init文件用于合并,mp4decrpyt需要
|
||||||
if (!DownloaderConfig.MyOptions.UseShakaPackager)
|
if (!DownloaderConfig.MyOptions.UseShakaPackager)
|
||||||
{
|
{
|
||||||
files = new string[] { initResult.ActualFilePath }.Concat(files).ToArray();
|
files = new string[] { initResult.ActualFilePath }.Concat(files).ToArray();
|
||||||
|
@ -598,7 +592,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//处理图形字幕
|
// 处理图形字幕
|
||||||
await SubtitleUtil.TryWriteImagePngsAsync(currentVtt, tmpDir);
|
await SubtitleUtil.TryWriteImagePngsAsync(currentVtt, tmpDir);
|
||||||
|
|
||||||
var subText = currentVtt.ToVtt();
|
var subText = currentVtt.ToVtt();
|
||||||
|
@ -616,7 +610,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//刷新buffer
|
// 刷新buffer
|
||||||
if (fileOutputStream != null)
|
if (fileOutputStream != null)
|
||||||
{
|
{
|
||||||
fileOutputStream.Flush();
|
fileOutputStream.Flush();
|
||||||
|
@ -631,7 +625,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
{
|
{
|
||||||
if (!DownloaderConfig.MyOptions.LivePipeMux)
|
if (!DownloaderConfig.MyOptions.LivePipeMux)
|
||||||
{
|
{
|
||||||
//记录所有文件信息
|
// 记录所有文件信息
|
||||||
OutputFiles.Add(new OutputFile()
|
OutputFiles.Add(new OutputFile()
|
||||||
{
|
{
|
||||||
Index = task.Id,
|
Index = task.Id,
|
||||||
|
@ -655,15 +649,15 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
{
|
{
|
||||||
if (WAIT_SEC != 0)
|
if (WAIT_SEC != 0)
|
||||||
{
|
{
|
||||||
//1. MPD 所有URL相同 单次请求即可获得所有轨道的信息
|
// 1. MPD 所有URL相同 单次请求即可获得所有轨道的信息
|
||||||
//2. M3U8 所有URL不同 才需要多次请求
|
// 2. M3U8 所有URL不同 才需要多次请求
|
||||||
|
|
||||||
await Parallel.ForEachAsync(dic, async (dic, _) =>
|
await Parallel.ForEachAsync(dic, async (dic, _) =>
|
||||||
{
|
{
|
||||||
var streamSpec = dic.Key;
|
var streamSpec = dic.Key;
|
||||||
var task = dic.Value;
|
var task = dic.Value;
|
||||||
|
|
||||||
//达到上限时 不需要刷新了
|
// 达到上限时 不需要刷新了
|
||||||
if (RecordLimitReachedDic[task.Id])
|
if (RecordLimitReachedDic[task.Id])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -674,20 +668,20 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var allSamePath = allName.Count() > 1 && allName.Distinct().Count() == 1;
|
var allSamePath = allName.Count() > 1 && allName.Distinct().Count() == 1;
|
||||||
SamePathDic[task.Id] = allSamePath;
|
SamePathDic[task.Id] = allSamePath;
|
||||||
}
|
}
|
||||||
//过滤不需要下载的片段
|
// 过滤不需要下载的片段
|
||||||
FilterMediaSegments(streamSpec, task, allHasDatetime, SamePathDic[task.Id]);
|
FilterMediaSegments(streamSpec, task, allHasDatetime, SamePathDic[task.Id]);
|
||||||
var newList = streamSpec.Playlist!.MediaParts[0].MediaSegments;
|
var newList = streamSpec.Playlist!.MediaParts[0].MediaSegments;
|
||||||
if (newList.Count > 0)
|
if (newList.Count > 0)
|
||||||
{
|
{
|
||||||
task.MaxValue += newList.Count;
|
task.MaxValue += newList.Count;
|
||||||
//推送给消费者
|
// 推送给消费者
|
||||||
await BlockDic[task.Id].SendAsync(newList);
|
await BlockDic[task.Id].SendAsync(newList);
|
||||||
//更新最新链接
|
// 更新最新链接
|
||||||
LastFileNameDic[task.Id] = GetSegmentName(newList.Last(), allHasDatetime, SamePathDic[task.Id]);
|
LastFileNameDic[task.Id] = GetSegmentName(newList.Last(), allHasDatetime, SamePathDic[task.Id]);
|
||||||
//尝试更新时间戳
|
// 尝试更新时间戳
|
||||||
var dt = newList.Last().DateTime;
|
var dt = newList.Last().DateTime;
|
||||||
DateTimeDic[task.Id] = dt != null ? GetUnixTimestamp(dt.Value) : 0L;
|
DateTimeDic[task.Id] = dt != null ? GetUnixTimestamp(dt.Value) : 0L;
|
||||||
//累加已获取到的时长
|
// 累加已获取到的时长
|
||||||
RefreshedDurDic[task.Id] += (int)newList.Sum(s => s.Duration);
|
RefreshedDurDic[task.Id] += (int)newList.Sum(s => s.Duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -696,7 +690,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
RecordLimitReachedDic[task.Id] = true;
|
RecordLimitReachedDic[task.Id] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//检测时长限制
|
// 检测时长限制
|
||||||
if (!STOP_FLAG && RecordLimitReachedDic.Values.All(x => x == true))
|
if (!STOP_FLAG && RecordLimitReachedDic.Values.All(x => x == true))
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimitReached}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimitReached}[/]");
|
||||||
|
@ -707,20 +701,20 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//Logger.WarnMarkUp($"wait {waitSec}s");
|
// Logger.WarnMarkUp($"wait {waitSec}s");
|
||||||
if (!STOP_FLAG) await Task.Delay(WAIT_SEC * 1000, CancellationTokenSource.Token);
|
if (!STOP_FLAG) await Task.Delay(WAIT_SEC * 1000, CancellationTokenSource.Token);
|
||||||
//刷新列表
|
// 刷新列表
|
||||||
if (!STOP_FLAG) await StreamExtractor.RefreshPlayListAsync(dic.Keys.ToList());
|
if (!STOP_FLAG) await StreamExtractor.RefreshPlayListAsync(dic.Keys.ToList());
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException oce) when (oce.CancellationToken == CancellationTokenSource.Token)
|
catch (OperationCanceledException oce) when (oce.CancellationToken == CancellationTokenSource.Token)
|
||||||
{
|
{
|
||||||
//不需要做事
|
// 不需要做事
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.ErrorMarkUp(e);
|
Logger.ErrorMarkUp(e);
|
||||||
STOP_FLAG = true;
|
STOP_FLAG = true;
|
||||||
//停止所有Block
|
// 停止所有Block
|
||||||
foreach (var target in BlockDic.Values)
|
foreach (var target in BlockDic.Values)
|
||||||
{
|
{
|
||||||
target.Complete();
|
target.Complete();
|
||||||
|
@ -738,7 +732,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var dateTime = DateTimeDic[task.Id];
|
var dateTime = DateTimeDic[task.Id];
|
||||||
var lastName = LastFileNameDic[task.Id];
|
var lastName = LastFileNameDic[task.Id];
|
||||||
|
|
||||||
//优先使用dateTime判断
|
// 优先使用dateTime判断
|
||||||
if (dateTime != 0 && streamSpec.Playlist!.MediaParts[0].MediaSegments.All(s => s.DateTime != null))
|
if (dateTime != 0 && streamSpec.Playlist!.MediaParts[0].MediaSegments.All(s => s.DateTime != null))
|
||||||
{
|
{
|
||||||
index = streamSpec.Playlist!.MediaParts[0].MediaSegments.FindIndex(s => GetUnixTimestamp(s.DateTime!.Value) == dateTime);
|
index = streamSpec.Playlist!.MediaParts[0].MediaSegments.FindIndex(s => GetUnixTimestamp(s.DateTime!.Value) == dateTime);
|
||||||
|
@ -750,7 +744,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
if (index > -1)
|
if (index > -1)
|
||||||
{
|
{
|
||||||
//修正Index
|
// 修正Index
|
||||||
var list = streamSpec.Playlist!.MediaParts[0].MediaSegments.Skip(index + 1).ToList();
|
var list = streamSpec.Playlist!.MediaParts[0].MediaSegments.Skip(index + 1).ToList();
|
||||||
if (list.Count > 0)
|
if (list.Count > 0)
|
||||||
{
|
{
|
||||||
|
@ -773,27 +767,27 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
public async Task<bool> StartRecordAsync()
|
public async Task<bool> StartRecordAsync()
|
||||||
{
|
{
|
||||||
var takeLastCount = DownloaderConfig.MyOptions.LiveTakeCount;
|
var takeLastCount = DownloaderConfig.MyOptions.LiveTakeCount;
|
||||||
ConcurrentDictionary<int, SpeedContainer> SpeedContainerDic = new(); //速度计算
|
ConcurrentDictionary<int, SpeedContainer> SpeedContainerDic = new(); // 速度计算
|
||||||
ConcurrentDictionary<StreamSpec, bool?> Results = new();
|
ConcurrentDictionary<StreamSpec, bool?> Results = new();
|
||||||
//同步流
|
// 同步流
|
||||||
FilterUtil.SyncStreams(SelectedSteams, takeLastCount);
|
FilterUtil.SyncStreams(SelectedSteams, takeLastCount);
|
||||||
//设置等待时间
|
// 设置等待时间
|
||||||
if (WAIT_SEC == 0)
|
if (WAIT_SEC == 0)
|
||||||
{
|
{
|
||||||
WAIT_SEC = (int)(SelectedSteams.Min(s => s.Playlist!.MediaParts[0].MediaSegments.Sum(s => s.Duration)) / 2);
|
WAIT_SEC = (int)(SelectedSteams.Min(s => s.Playlist!.MediaParts[0].MediaSegments.Sum(s => s.Duration)) / 2);
|
||||||
WAIT_SEC -= 2; //再提前两秒吧 留出冗余
|
WAIT_SEC -= 2; // 再提前两秒吧 留出冗余
|
||||||
if (DownloaderConfig.MyOptions.LiveWaitTime != null)
|
if (DownloaderConfig.MyOptions.LiveWaitTime != null)
|
||||||
WAIT_SEC = DownloaderConfig.MyOptions.LiveWaitTime.Value;
|
WAIT_SEC = DownloaderConfig.MyOptions.LiveWaitTime.Value;
|
||||||
if (WAIT_SEC <= 0) WAIT_SEC = 1;
|
if (WAIT_SEC <= 0) WAIT_SEC = 1;
|
||||||
Logger.WarnMarkUp($"set refresh interval to {WAIT_SEC} seconds");
|
Logger.WarnMarkUp($"set refresh interval to {WAIT_SEC} seconds");
|
||||||
}
|
}
|
||||||
//如果没有选中音频 取消通过音频修复vtt时间轴
|
// 如果没有选中音频 取消通过音频修复vtt时间轴
|
||||||
if (!SelectedSteams.Any(x => x.MediaType == MediaType.AUDIO))
|
if (!SelectedSteams.Any(x => x.MediaType == MediaType.AUDIO))
|
||||||
{
|
{
|
||||||
DownloaderConfig.MyOptions.LiveFixVttByAudio = false;
|
DownloaderConfig.MyOptions.LiveFixVttByAudio = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//写出master
|
/*// 写出master
|
||||||
if (DownloaderConfig.MyOptions.LiveWriteHLS)
|
if (DownloaderConfig.MyOptions.LiveWriteHLS)
|
||||||
{
|
{
|
||||||
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
||||||
|
@ -804,14 +798,14 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var progress = CustomAnsiConsole.Console.Progress().AutoClear(true);
|
var progress = CustomAnsiConsole.Console.Progress().AutoClear(true);
|
||||||
progress.AutoRefresh = DownloaderConfig.MyOptions.LogLevel != LogLevel.OFF;
|
progress.AutoRefresh = DownloaderConfig.MyOptions.LogLevel != LogLevel.OFF;
|
||||||
|
|
||||||
//进度条的列定义
|
// 进度条的列定义
|
||||||
var progressColumns = new ProgressColumn[]
|
var progressColumns = new ProgressColumn[]
|
||||||
{
|
{
|
||||||
new TaskDescriptionColumn() { Alignment = Justify.Left },
|
new TaskDescriptionColumn() { Alignment = Justify.Left },
|
||||||
new RecordingDurationColumn(RecordedDurDic, RefreshedDurDic), //时长显示
|
new RecordingDurationColumn(RecordedDurDic, RefreshedDurDic), // 时长显示
|
||||||
new RecordingStatusColumn(),
|
new RecordingStatusColumn(),
|
||||||
new PercentageColumn(),
|
new PercentageColumn(),
|
||||||
new DownloadSpeedColumn(SpeedContainerDic), //速度计算
|
new DownloadSpeedColumn(SpeedContainerDic), // 速度计算
|
||||||
new SpinnerColumn(),
|
new SpinnerColumn(),
|
||||||
};
|
};
|
||||||
if (DownloaderConfig.MyOptions.NoAnsiColor)
|
if (DownloaderConfig.MyOptions.NoAnsiColor)
|
||||||
|
@ -822,12 +816,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
await progress.StartAsync(async ctx =>
|
await progress.StartAsync(async ctx =>
|
||||||
{
|
{
|
||||||
//创建任务
|
// 创建任务
|
||||||
var dic = SelectedSteams.Select(item =>
|
var dic = SelectedSteams.Select(item =>
|
||||||
{
|
{
|
||||||
var task = ctx.AddTask(item.ToShortShortString(), autoStart: false, maxValue: 0);
|
var task = ctx.AddTask(item.ToShortShortString(), autoStart: false, maxValue: 0);
|
||||||
SpeedContainerDic[task.Id] = new SpeedContainer(); //速度计算
|
SpeedContainerDic[task.Id] = new SpeedContainer(); // 速度计算
|
||||||
//限速设置
|
// 限速设置
|
||||||
if (DownloaderConfig.MyOptions.MaxSpeed != null)
|
if (DownloaderConfig.MyOptions.MaxSpeed != null)
|
||||||
{
|
{
|
||||||
SpeedContainerDic[task.Id].SpeedLimit = DownloaderConfig.MyOptions.MaxSpeed.Value;
|
SpeedContainerDic[task.Id].SpeedLimit = DownloaderConfig.MyOptions.MaxSpeed.Value;
|
||||||
|
@ -837,7 +831,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
DateTimeDic[task.Id] = 0L;
|
DateTimeDic[task.Id] = 0L;
|
||||||
RecordedDurDic[task.Id] = 0;
|
RecordedDurDic[task.Id] = 0;
|
||||||
RefreshedDurDic[task.Id] = 0;
|
RefreshedDurDic[task.Id] = 0;
|
||||||
MaxIndexDic[task.Id] = item.Playlist?.MediaParts[0].MediaSegments.LastOrDefault()?.Index ?? 0L; //最大Index
|
MaxIndexDic[task.Id] = item.Playlist?.MediaParts[0].MediaSegments.LastOrDefault()?.Index ?? 0L; // 最大Index
|
||||||
BlockDic[task.Id] = new BufferBlock<List<MediaSegment>>();
|
BlockDic[task.Id] = new BufferBlock<List<MediaSegment>>();
|
||||||
return (item, task);
|
return (item, task);
|
||||||
}).ToDictionary(item => item.item, item => item.task);
|
}).ToDictionary(item => item.item, item => item.task);
|
||||||
|
@ -851,15 +845,15 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
|
var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
|
||||||
if (limit != TimeSpan.MaxValue)
|
if (limit != TimeSpan.MaxValue)
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimit}{GlobalUtil.FormatTime((int)limit.Value.TotalSeconds)}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimit}{GlobalUtil.FormatTime((int)limit.Value.TotalSeconds)}[/]");
|
||||||
//录制直播时,用户选了几个流就并发录几个
|
// 录制直播时,用户选了几个流就并发录几个
|
||||||
var options = new ParallelOptions()
|
var options = new ParallelOptions()
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = SelectedSteams.Count
|
MaxDegreeOfParallelism = SelectedSteams.Count
|
||||||
};
|
};
|
||||||
//开始刷新
|
// 开始刷新
|
||||||
var producerTask = PlayListProduceAsync(dic);
|
var producerTask = PlayListProduceAsync(dic);
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
//并发下载
|
// 并发下载
|
||||||
await Parallel.ForEachAsync(dic, options, async (kp, _) =>
|
await Parallel.ForEachAsync(dic, options, async (kp, _) =>
|
||||||
{
|
{
|
||||||
var task = kp.Value;
|
var task = kp.Value;
|
||||||
|
@ -870,7 +864,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
var success = Results.Values.All(v => v == true);
|
var success = Results.Values.All(v => v == true);
|
||||||
|
|
||||||
//删除临时文件夹
|
// 删除临时文件夹
|
||||||
if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && success)
|
if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && success)
|
||||||
{
|
{
|
||||||
foreach (var item in StreamExtractor.RawFiles)
|
foreach (var item in StreamExtractor.RawFiles)
|
||||||
|
@ -881,12 +875,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
OtherUtil.SafeDeleteDir(DownloaderConfig.DirPrefix);
|
OtherUtil.SafeDeleteDir(DownloaderConfig.DirPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
//混流
|
// 混流
|
||||||
if (success && DownloaderConfig.MyOptions.MuxAfterDone && OutputFiles.Count > 0)
|
if (success && DownloaderConfig.MyOptions.MuxAfterDone && OutputFiles.Count > 0)
|
||||||
{
|
{
|
||||||
OutputFiles = OutputFiles.OrderBy(o => o.Index).ToList();
|
OutputFiles = OutputFiles.OrderBy(o => o.Index).ToList();
|
||||||
//是否跳过字幕
|
// 是否跳过字幕
|
||||||
if (DownloaderConfig.MyOptions.MuxOptions.SkipSubtitle)
|
if (DownloaderConfig.MyOptions.MuxOptions!.SkipSubtitle)
|
||||||
{
|
{
|
||||||
OutputFiles = OutputFiles.Where(o => o.MediaType != MediaType.SUBTITLES).ToList();
|
OutputFiles = OutputFiles.Where(o => o.MediaType != MediaType.SUBTITLES).ToList();
|
||||||
}
|
}
|
||||||
|
@ -904,7 +898,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var result = false;
|
var result = false;
|
||||||
if (DownloaderConfig.MyOptions.MuxOptions.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MyOptions.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
|
if (DownloaderConfig.MyOptions.MuxOptions.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MyOptions.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
|
||||||
else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MyOptions.MuxOptions.MuxFormat, !DownloaderConfig.MyOptions.NoDateInfo);
|
else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MyOptions.MuxOptions.MuxFormat, !DownloaderConfig.MyOptions.NoDateInfo);
|
||||||
//完成后删除各轨道文件
|
// 完成后删除各轨道文件
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
if (!DownloaderConfig.MyOptions.MuxOptions.KeepFiles)
|
if (!DownloaderConfig.MyOptions.MuxOptions.KeepFiles)
|
||||||
|
@ -920,7 +914,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
success = false;
|
success = false;
|
||||||
Logger.ErrorMarkUp($"Mux failed");
|
Logger.ErrorMarkUp($"Mux failed");
|
||||||
}
|
}
|
||||||
//判断是否要改名
|
// 判断是否要改名
|
||||||
var newPath = Path.ChangeExtension(outPath, ext);
|
var newPath = Path.ChangeExtension(outPath, ext);
|
||||||
if (result && !File.Exists(newPath))
|
if (result && !File.Exists(newPath))
|
||||||
{
|
{
|
||||||
|
@ -931,5 +925,4 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,15 +1,9 @@
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Downloader
|
namespace N_m3u8DL_RE.Downloader;
|
||||||
|
|
||||||
|
internal interface IDownloader
|
||||||
{
|
{
|
||||||
internal interface IDownloader
|
|
||||||
{
|
|
||||||
Task<DownloadResult?> DownloadSegmentAsync(MediaSegment segment, string savePath, SpeedContainer speedContainer, Dictionary<string, string>? headers = null);
|
Task<DownloadResult?> DownloadSegmentAsync(MediaSegment segment, string savePath, SpeedContainer speedContainer, Dictionary<string, string>? headers = null);
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -6,23 +6,14 @@ using N_m3u8DL_RE.Crypto;
|
||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using N_m3u8DL_RE.Util;
|
using N_m3u8DL_RE.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Downloader
|
namespace N_m3u8DL_RE.Downloader;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 简单下载器
|
||||||
|
/// </summary>
|
||||||
|
internal class SimpleDownloader : IDownloader
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 简单下载器
|
|
||||||
/// </summary>
|
|
||||||
internal class SimpleDownloader : IDownloader
|
|
||||||
{
|
|
||||||
DownloaderConfig DownloaderConfig;
|
DownloaderConfig DownloaderConfig;
|
||||||
|
|
||||||
public SimpleDownloader(DownloaderConfig config)
|
public SimpleDownloader(DownloaderConfig config)
|
||||||
|
@ -61,22 +52,22 @@ namespace N_m3u8DL_RE.Downloader
|
||||||
}
|
}
|
||||||
else if (segment.EncryptInfo.Method == EncryptMethod.SAMPLE_AES_CTR)
|
else if (segment.EncryptInfo.Method == EncryptMethod.SAMPLE_AES_CTR)
|
||||||
{
|
{
|
||||||
//throw new NotSupportedException("SAMPLE-AES-CTR");
|
// throw new NotSupportedException("SAMPLE-AES-CTR");
|
||||||
}
|
}
|
||||||
|
|
||||||
//Image头处理
|
// Image头处理
|
||||||
if (dResult.ImageHeader)
|
if (dResult.ImageHeader)
|
||||||
{
|
{
|
||||||
await ImageHeaderUtil.ProcessAsync(dResult.ActualFilePath);
|
await ImageHeaderUtil.ProcessAsync(dResult.ActualFilePath);
|
||||||
}
|
}
|
||||||
//Gzip解压
|
// Gzip解压
|
||||||
if (dResult.GzipHeader)
|
if (dResult.GzipHeader)
|
||||||
{
|
{
|
||||||
await OtherUtil.DeGzipFileAsync(dResult.ActualFilePath);
|
await OtherUtil.DeGzipFileAsync(dResult.ActualFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//处理完成后改名
|
// 处理完成后改名
|
||||||
File.Move(dResult.ActualFilePath, des);
|
File.Move(dResult.ActualFilePath, des);
|
||||||
dResult.ActualFilePath = des;
|
dResult.ActualFilePath = des;
|
||||||
}
|
}
|
||||||
|
@ -92,14 +83,14 @@ namespace N_m3u8DL_RE.Downloader
|
||||||
cancellationTokenSource = new();
|
cancellationTokenSource = new();
|
||||||
var des = Path.ChangeExtension(path, null);
|
var des = Path.ChangeExtension(path, null);
|
||||||
|
|
||||||
//已下载跳过
|
// 已下载跳过
|
||||||
if (File.Exists(des))
|
if (File.Exists(des))
|
||||||
{
|
{
|
||||||
speedContainer.Add(new FileInfo(des).Length);
|
speedContainer.Add(new FileInfo(des).Length);
|
||||||
return (des, new DownloadResult() { ActualContentLength = 0, ActualFilePath = des });
|
return (des, new DownloadResult() { ActualContentLength = 0, ActualFilePath = des });
|
||||||
}
|
}
|
||||||
|
|
||||||
//已解密跳过
|
// 已解密跳过
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(des)!, Path.GetFileNameWithoutExtension(des) + "_dec" + Path.GetExtension(des));
|
var dec = Path.Combine(Path.GetDirectoryName(des)!, Path.GetFileNameWithoutExtension(des) + "_dec" + Path.GetExtension(des));
|
||||||
if (File.Exists(dec))
|
if (File.Exists(dec))
|
||||||
{
|
{
|
||||||
|
@ -107,7 +98,7 @@ namespace N_m3u8DL_RE.Downloader
|
||||||
return (dec, new DownloadResult() { ActualContentLength = 0, ActualFilePath = dec });
|
return (dec, new DownloadResult() { ActualContentLength = 0, ActualFilePath = dec });
|
||||||
}
|
}
|
||||||
|
|
||||||
//另起线程进行监控
|
// 另起线程进行监控
|
||||||
using var watcher = Task.Factory.StartNew(async () =>
|
using var watcher = Task.Factory.StartNew(async () =>
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
|
@ -123,7 +114,7 @@ namespace N_m3u8DL_RE.Downloader
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//调用下载
|
// 调用下载
|
||||||
var result = await DownloadUtil.DownloadToFileAsync(url, path, speedContainer, cancellationTokenSource, headers, fromPosition, toPosition);
|
var result = await DownloadUtil.DownloadToFileAsync(url, path, speedContainer, cancellationTokenSource, headers, fromPosition, toPosition);
|
||||||
return (des, result);
|
return (des, result);
|
||||||
|
|
||||||
|
@ -144,18 +135,17 @@ namespace N_m3u8DL_RE.Downloader
|
||||||
Logger.Extra($"The retry attempts have been exhausted and the download of this segment has failed.{Environment.NewLine}Exception => {ex.Message}{Environment.NewLine}Url => {url}");
|
Logger.Extra($"The retry attempts have been exhausted and the download of this segment has failed.{Environment.NewLine}Exception => {ex.Message}{Environment.NewLine}Url => {url}");
|
||||||
Logger.WarnMarkUp($"[grey]{ex.Message.EscapeMarkup()}[/]");
|
Logger.WarnMarkUp($"[grey]{ex.Message.EscapeMarkup()}[/]");
|
||||||
}
|
}
|
||||||
//throw new Exception("download failed", ex);
|
// throw new Exception("download failed", ex);
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (cancellationTokenSource != null)
|
if (cancellationTokenSource != null)
|
||||||
{
|
{
|
||||||
//调用后销毁
|
// 调用后销毁
|
||||||
cancellationTokenSource.Dispose();
|
cancellationTokenSource.Dispose();
|
||||||
cancellationTokenSource = null;
|
cancellationTokenSource = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,13 +1,7 @@
|
||||||
using System;
|
namespace N_m3u8DL_RE.Entity;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
public class CustomRange
|
||||||
{
|
{
|
||||||
public class CustomRange
|
|
||||||
{
|
|
||||||
public required string InputStr { get; set; }
|
public required string InputStr { get; set; }
|
||||||
public double? StartSec { get; set; }
|
public double? StartSec { get; set; }
|
||||||
public double? EndSec { get; set; }
|
public double? EndSec { get; set; }
|
||||||
|
@ -19,5 +13,4 @@ namespace N_m3u8DL_RE.Entity
|
||||||
{
|
{
|
||||||
return $"StartSec: {StartSec}, EndSec: {EndSec}, StartSegIndex: {StartSegIndex}, EndSegIndex: {EndSegIndex}";
|
return $"StartSec: {StartSec}, EndSec: {EndSec}, StartSegIndex: {StartSegIndex}, EndSegIndex: {EndSegIndex}";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -5,15 +5,14 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
namespace N_m3u8DL_RE.Entity;
|
||||||
|
|
||||||
|
internal class DownloadResult
|
||||||
{
|
{
|
||||||
internal class DownloadResult
|
|
||||||
{
|
|
||||||
public bool Success { get => (ActualContentLength != null && RespContentLength != null) ? (RespContentLength == ActualContentLength) : (ActualContentLength == null ? false : true); }
|
public bool Success { get => (ActualContentLength != null && RespContentLength != null) ? (RespContentLength == ActualContentLength) : (ActualContentLength == null ? false : true); }
|
||||||
public long? RespContentLength { get; set; }
|
public long? RespContentLength { get; set; }
|
||||||
public long? ActualContentLength { get; set; }
|
public long? ActualContentLength { get; set; }
|
||||||
public bool ImageHeader { get; set; } = false; //图片伪装
|
public bool ImageHeader { get; set; } = false; // 图片伪装
|
||||||
public bool GzipHeader { get; set; } = false; //GZip压缩
|
public bool GzipHeader { get; set; } = false; // GZip压缩
|
||||||
public required string ActualFilePath { get; set; }
|
public required string ActualFilePath { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,14 +1,9 @@
|
||||||
using System;
|
using Spectre.Console;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Spectre.Console;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
namespace N_m3u8DL_RE.Entity;
|
||||||
|
|
||||||
|
internal class Mediainfo
|
||||||
{
|
{
|
||||||
internal class Mediainfo
|
|
||||||
{
|
|
||||||
public string? Id { get; set; }
|
public string? Id { get; set; }
|
||||||
public string? Text { get; set; }
|
public string? Text { get; set; }
|
||||||
public string? BaseInfo { get; set; }
|
public string? BaseInfo { get; set; }
|
||||||
|
@ -29,5 +24,4 @@ namespace N_m3u8DL_RE.Entity
|
||||||
{
|
{
|
||||||
return "[steelblue]" + ToString().EscapeMarkup() + ((HDR && !DolbyVison) ? " [darkorange3_1][[HDR]][/]" : "") + (DolbyVison ? " [darkorange3_1][[DOVI]][/]" : "") + "[/]";
|
return "[steelblue]" + ToString().EscapeMarkup() + ((HDR && !DolbyVison) ? " [darkorange3_1][[HDR]][/]" : "") + (DolbyVison ? " [darkorange3_1][[DOVI]][/]" : "") + "[/]";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,13 +1,12 @@
|
||||||
using N_m3u8DL_RE.Enum;
|
using N_m3u8DL_RE.Enum;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
namespace N_m3u8DL_RE.Entity;
|
||||||
|
|
||||||
|
internal class MuxOptions
|
||||||
{
|
{
|
||||||
internal class MuxOptions
|
|
||||||
{
|
|
||||||
public bool UseMkvmerge { get; set; } = false;
|
public bool UseMkvmerge { get; set; } = false;
|
||||||
public MuxFormat MuxFormat { get; set; } = MuxFormat.MP4;
|
public MuxFormat MuxFormat { get; set; } = MuxFormat.MP4;
|
||||||
public bool KeepFiles { get; set; } = false;
|
public bool KeepFiles { get; set; } = false;
|
||||||
public bool SkipSubtitle { get; set; } = false;
|
public bool SkipSubtitle { get; set; } = false;
|
||||||
public string? BinPath { get; set; }
|
public string? BinPath { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,19 +1,13 @@
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
namespace N_m3u8DL_RE.Entity;
|
||||||
|
|
||||||
|
internal class OutputFile
|
||||||
{
|
{
|
||||||
internal class OutputFile
|
|
||||||
{
|
|
||||||
public MediaType? MediaType { get; set; }
|
public MediaType? MediaType { get; set; }
|
||||||
public required int Index { get; set; }
|
public required int Index { get; set; }
|
||||||
public required string FilePath { get; set; }
|
public required string FilePath { get; set; }
|
||||||
public string? LangCode { get; set; }
|
public string? LangCode { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public List<Mediainfo> Mediainfos { get; set; } = new();
|
public List<Mediainfo> Mediainfos { get; set; } = new();
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -7,18 +7,18 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
namespace N_m3u8DL_RE.Entity;
|
||||||
|
|
||||||
|
internal class SpeedContainer
|
||||||
{
|
{
|
||||||
internal class SpeedContainer
|
|
||||||
{
|
|
||||||
public bool SingleSegment { get; set; } = false;
|
public bool SingleSegment { get; set; } = false;
|
||||||
public long NowSpeed { get; set; } = 0L; //当前每秒速度
|
public long NowSpeed { get; set; } = 0L; // 当前每秒速度
|
||||||
public long SpeedLimit { get; set; } = long.MaxValue; //限速设置
|
public long SpeedLimit { get; set; } = long.MaxValue; // 限速设置
|
||||||
public long? ResponseLength { get; set; }
|
public long? ResponseLength { get; set; }
|
||||||
public long RDownloaded { get => _Rdownloaded; }
|
public long RDownloaded => _Rdownloaded;
|
||||||
private int _zeroSpeedCount = 0;
|
private int _zeroSpeedCount = 0;
|
||||||
public int LowSpeedCount { get => _zeroSpeedCount; }
|
public int LowSpeedCount => _zeroSpeedCount;
|
||||||
public bool ShouldStop { get => LowSpeedCount >= 20; }
|
public bool ShouldStop => LowSpeedCount >= 20;
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
///////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@ -55,5 +55,4 @@ namespace N_m3u8DL_RE.Entity
|
||||||
ResponseLength = null;
|
ResponseLength = null;
|
||||||
_Rdownloaded = 0L;
|
_Rdownloaded = 0L;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,15 +1,11 @@
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
namespace N_m3u8DL_RE.Entity;
|
||||||
|
|
||||||
|
public class StreamFilter
|
||||||
{
|
{
|
||||||
public class StreamFilter
|
|
||||||
{
|
|
||||||
public Regex? GroupIdReg { get; set; }
|
public Regex? GroupIdReg { get; set; }
|
||||||
public Regex? LanguageReg { get; set; }
|
public Regex? LanguageReg { get; set; }
|
||||||
public Regex? NameReg { get; set; }
|
public Regex? NameReg { get; set; }
|
||||||
|
@ -52,5 +48,4 @@ namespace N_m3u8DL_RE.Entity
|
||||||
|
|
||||||
return sb.ToString() + $"For: {For}";
|
return sb.ToString() + $"For: {For}";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -2,16 +2,11 @@
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Parser.Processor;
|
using N_m3u8DL_RE.Parser.Processor;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Processor
|
namespace N_m3u8DL_RE.Processor;
|
||||||
|
|
||||||
|
internal class DemoProcessor : ContentProcessor
|
||||||
{
|
{
|
||||||
internal class DemoProcessor : ContentProcessor
|
|
||||||
{
|
|
||||||
|
|
||||||
public override bool CanProcess(ExtractorType extractorType, string rawText, ParserConfig parserConfig)
|
public override bool CanProcess(ExtractorType extractorType, string rawText, ParserConfig parserConfig)
|
||||||
{
|
{
|
||||||
|
@ -23,5 +18,4 @@ namespace N_m3u8DL_RE.Processor
|
||||||
Logger.InfoMarkUp("[red]Match bitmovin![/]");
|
Logger.InfoMarkUp("[red]Match bitmovin![/]");
|
||||||
return rawText;
|
return rawText;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -5,16 +5,11 @@ using N_m3u8DL_RE.Common.Util;
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Parser.Processor;
|
using N_m3u8DL_RE.Parser.Processor;
|
||||||
using N_m3u8DL_RE.Parser.Processor.HLS;
|
using N_m3u8DL_RE.Parser.Processor.HLS;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Processor
|
namespace N_m3u8DL_RE.Processor;
|
||||||
|
|
||||||
|
internal class DemoProcessor2 : KeyProcessor
|
||||||
{
|
{
|
||||||
internal class DemoProcessor2 : KeyProcessor
|
|
||||||
{
|
|
||||||
public override bool CanProcess(ExtractorType extractorType, string keyLine, string m3u8Url, string m3u8Content, ParserConfig parserConfig)
|
public override bool CanProcess(ExtractorType extractorType, string keyLine, string m3u8Url, string m3u8Content, ParserConfig parserConfig)
|
||||||
{
|
{
|
||||||
return extractorType == ExtractorType.HLS && parserConfig.Url.Contains("playertest.longtailvideo.com");
|
return extractorType == ExtractorType.HLS && parserConfig.Url.Contains("playertest.longtailvideo.com");
|
||||||
|
@ -27,5 +22,4 @@ namespace N_m3u8DL_RE.Processor
|
||||||
Logger.InfoMarkUp("[red]" + HexUtil.BytesToHex(info.Key!, " ") + "[/]");
|
Logger.InfoMarkUp("[red]" + HexUtil.BytesToHex(info.Key!, " ") + "[/]");
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -6,19 +6,12 @@ using N_m3u8DL_RE.Parser.Util;
|
||||||
using NiL.JS.BaseLibrary;
|
using NiL.JS.BaseLibrary;
|
||||||
using NiL.JS.Core;
|
using NiL.JS.Core;
|
||||||
using NiL.JS.Extensions;
|
using NiL.JS.Extensions;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Processor
|
namespace N_m3u8DL_RE.Processor;
|
||||||
|
|
||||||
|
// "https://1429754964.rsc.cdn77.org/r/nh22/2022/VNUS_DE_NYKE/19_07_22_2302_skt/h264.mpd?secure=mSvVfvuciJt9wufUyzuBnA==,1658505709774" --urlprocessor-args "nowehoryzonty:timeDifference=-2274,filminfo.secureToken=vx54axqjal4f0yy2"
|
||||||
|
internal class NowehoryzontyUrlProcessor : UrlProcessor
|
||||||
{
|
{
|
||||||
//"https://1429754964.rsc.cdn77.org/r/nh22/2022/VNUS_DE_NYKE/19_07_22_2302_skt/h264.mpd?secure=mSvVfvuciJt9wufUyzuBnA==,1658505709774" --urlprocessor-args "nowehoryzonty:timeDifference=-2274,filminfo.secureToken=vx54axqjal4f0yy2"
|
|
||||||
internal class NowehoryzontyUrlProcessor : UrlProcessor
|
|
||||||
{
|
|
||||||
private static string START = "nowehoryzonty:";
|
private static string START = "nowehoryzonty:";
|
||||||
private static string? TimeDifferenceStr = null;
|
private static string? TimeDifferenceStr = null;
|
||||||
private static int? TimeDifference = null;
|
private static int? TimeDifference = null;
|
||||||
|
@ -220,5 +213,4 @@ namespace N_m3u8DL_RE.Processor
|
||||||
//console.log(md5('/r/nh22/2022/VNUS_DE_NYKE/19_07_22_2302_skt/subtitle_pl/34.m4s','vx54axqjal4f0yy2',-2274));
|
//console.log(md5('/r/nh22/2022/VNUS_DE_NYKE/19_07_22_2302_skt/subtitle_pl/34.m4s','vx54axqjal4f0yy2',-2274));
|
||||||
|
|
||||||
""";
|
""";
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -16,10 +16,10 @@ using N_m3u8DL_RE.CommandLine;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE
|
namespace N_m3u8DL_RE;
|
||||||
|
|
||||||
|
internal class Program
|
||||||
{
|
{
|
||||||
internal class Program
|
|
||||||
{
|
|
||||||
static async Task Main(string[] args)
|
static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
Console.CancelKeyPress += Console_CancelKeyPress;
|
Console.CancelKeyPress += Console_CancelKeyPress;
|
||||||
|
@ -31,7 +31,7 @@ namespace N_m3u8DL_RE
|
||||||
if (currLoc == "zh-CN" || currLoc == "zh-SG") loc = "zh-CN";
|
if (currLoc == "zh-CN" || currLoc == "zh-SG") loc = "zh-CN";
|
||||||
else if (currLoc.StartsWith("zh-")) loc = "zh-TW";
|
else if (currLoc.StartsWith("zh-")) loc = "zh-TW";
|
||||||
|
|
||||||
//处理用户-h等请求
|
// 处理用户-h等请求
|
||||||
var index = -1;
|
var index = -1;
|
||||||
var list = new List<string>(args);
|
var list = new List<string>(args);
|
||||||
if ((index = list.IndexOf("--ui-language")) != -1 && list.Count > index + 1 && new List<string> { "en-US", "zh-CN", "zh-TW" }.Contains(list[index + 1]))
|
if ((index = list.IndexOf("--ui-language")) != -1 && list.Count > index + 1 && new List<string> { "en-US", "zh-CN", "zh-TW" }.Contains(list[index + 1]))
|
||||||
|
@ -77,8 +77,8 @@ namespace N_m3u8DL_RE
|
||||||
Logger.Info(ResString.consoleRedirected);
|
Logger.Info(ResString.consoleRedirected);
|
||||||
}
|
}
|
||||||
CustomAnsiConsole.InitConsole(option.ForceAnsiConsole, option.NoAnsiColor);
|
CustomAnsiConsole.InitConsole(option.ForceAnsiConsole, option.NoAnsiColor);
|
||||||
//检测更新
|
// 检测更新
|
||||||
CheckUpdateAsync();
|
_ = CheckUpdateAsync();
|
||||||
|
|
||||||
Logger.IsWriteFile = !option.NoLog;
|
Logger.IsWriteFile = !option.NoLog;
|
||||||
Logger.InitLogFile();
|
Logger.InitLogFile();
|
||||||
|
@ -96,21 +96,21 @@ namespace N_m3u8DL_RE
|
||||||
HTTPUtil.HttpClientHandler.UseProxy = true;
|
HTTPUtil.HttpClientHandler.UseProxy = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//检查互斥的选项
|
// 检查互斥的选项
|
||||||
|
|
||||||
if (!option.MuxAfterDone && option.MuxImports != null && option.MuxImports.Count > 0)
|
if (!option.MuxAfterDone && option.MuxImports != null && option.MuxImports.Count > 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!");
|
throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
//LivePipeMux开启时 LiveRealTimeMerge必须开启
|
// LivePipeMux开启时 LiveRealTimeMerge必须开启
|
||||||
if (option.LivePipeMux && !option.LiveRealTimeMerge)
|
if (option.LivePipeMux && !option.LiveRealTimeMerge)
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp("LivePipeMux detected, forced enable LiveRealTimeMerge");
|
Logger.WarnMarkUp("LivePipeMux detected, forced enable LiveRealTimeMerge");
|
||||||
option.LiveRealTimeMerge = true;
|
option.LiveRealTimeMerge = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//预先检查ffmpeg
|
// 预先检查ffmpeg
|
||||||
if (option.FFmpegBinaryPath == null)
|
if (option.FFmpegBinaryPath == null)
|
||||||
option.FFmpegBinaryPath = GlobalUtil.FindExecutable("ffmpeg");
|
option.FFmpegBinaryPath = GlobalUtil.FindExecutable("ffmpeg");
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ namespace N_m3u8DL_RE
|
||||||
|
|
||||||
Logger.Extra($"ffmpeg => {option.FFmpegBinaryPath}");
|
Logger.Extra($"ffmpeg => {option.FFmpegBinaryPath}");
|
||||||
|
|
||||||
//预先检查mkvmerge
|
// 预先检查mkvmerge
|
||||||
if (option.MuxOptions != null && option.MuxOptions.UseMkvmerge && option.MuxAfterDone)
|
if (option.MuxOptions != null && option.MuxOptions.UseMkvmerge && option.MuxAfterDone)
|
||||||
{
|
{
|
||||||
if (option.MkvmergeBinaryPath == null)
|
if (option.MkvmergeBinaryPath == null)
|
||||||
|
@ -133,7 +133,7 @@ namespace N_m3u8DL_RE
|
||||||
Logger.Extra($"mkvmerge => {option.MkvmergeBinaryPath}");
|
Logger.Extra($"mkvmerge => {option.MkvmergeBinaryPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
//预先检查
|
// 预先检查
|
||||||
if ((option.Keys != null && option.Keys.Length > 0) || option.KeyTextFile != null)
|
if ((option.Keys != null && option.Keys.Length > 0) || option.KeyTextFile != null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(option.DecryptionBinaryPath))
|
if (string.IsNullOrEmpty(option.DecryptionBinaryPath))
|
||||||
|
@ -162,12 +162,12 @@ namespace N_m3u8DL_RE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//默认的headers
|
// 默认的headers
|
||||||
var headers = new Dictionary<string, string>()
|
var headers = new Dictionary<string, string>()
|
||||||
{
|
{
|
||||||
["user-agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
|
["user-agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
|
||||||
};
|
};
|
||||||
//添加或替换用户输入的headers
|
// 添加或替换用户输入的headers
|
||||||
foreach (var item in option.Headers)
|
foreach (var item in option.Headers)
|
||||||
{
|
{
|
||||||
headers[item.Key] = item.Value;
|
headers[item.Key] = item.Value;
|
||||||
|
@ -185,14 +185,14 @@ namespace N_m3u8DL_RE
|
||||||
CustomeIV = option.CustomHLSIv,
|
CustomeIV = option.CustomHLSIv,
|
||||||
};
|
};
|
||||||
|
|
||||||
//demo1
|
// demo1
|
||||||
parserConfig.ContentProcessors.Insert(0, new DemoProcessor());
|
parserConfig.ContentProcessors.Insert(0, new DemoProcessor());
|
||||||
//demo2
|
// demo2
|
||||||
parserConfig.KeyProcessors.Insert(0, new DemoProcessor2());
|
parserConfig.KeyProcessors.Insert(0, new DemoProcessor2());
|
||||||
//for www.nowehoryzonty.pl
|
// for www.nowehoryzonty.pl
|
||||||
parserConfig.UrlProcessors.Insert(0, new NowehoryzontyUrlProcessor());
|
parserConfig.UrlProcessors.Insert(0, new NowehoryzontyUrlProcessor());
|
||||||
|
|
||||||
//等待任务开始时间
|
// 等待任务开始时间
|
||||||
if (option.TaskStartAt != null && option.TaskStartAt > DateTime.Now)
|
if (option.TaskStartAt != null && option.TaskStartAt > DateTime.Now)
|
||||||
{
|
{
|
||||||
Logger.InfoMarkUp(ResString.taskStartAt + option.TaskStartAt);
|
Logger.InfoMarkUp(ResString.taskStartAt + option.TaskStartAt);
|
||||||
|
@ -204,7 +204,7 @@ namespace N_m3u8DL_RE
|
||||||
|
|
||||||
var url = option.Input;
|
var url = option.Input;
|
||||||
|
|
||||||
//流提取器配置
|
// 流提取器配置
|
||||||
var extractor = new StreamExtractor(parserConfig);
|
var extractor = new StreamExtractor(parserConfig);
|
||||||
// 从链接加载内容
|
// 从链接加载内容
|
||||||
await RetryUtil.WebRequestRetryAsync(async () =>
|
await RetryUtil.WebRequestRetryAsync(async () =>
|
||||||
|
@ -212,30 +212,30 @@ namespace N_m3u8DL_RE
|
||||||
await extractor.LoadSourceFromUrlAsync(url);
|
await extractor.LoadSourceFromUrlAsync(url);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
//解析流信息
|
// 解析流信息
|
||||||
var streams = await extractor.ExtractStreamsAsync();
|
var streams = await extractor.ExtractStreamsAsync();
|
||||||
|
|
||||||
|
|
||||||
//全部媒体
|
// 全部媒体
|
||||||
var lists = streams.OrderBy(p => p.MediaType).ThenByDescending(p => p.Bandwidth).ThenByDescending(GetOrder);
|
var lists = streams.OrderBy(p => p.MediaType).ThenByDescending(p => p.Bandwidth).ThenByDescending(GetOrder);
|
||||||
//基本流
|
// 基本流
|
||||||
var basicStreams = lists.Where(x => x.MediaType == null || x.MediaType == MediaType.VIDEO);
|
var basicStreams = lists.Where(x => x.MediaType == null || x.MediaType == MediaType.VIDEO);
|
||||||
//可选音频轨道
|
// 可选音频轨道
|
||||||
var audios = lists.Where(x => x.MediaType == MediaType.AUDIO);
|
var audios = lists.Where(x => x.MediaType == MediaType.AUDIO);
|
||||||
//可选字幕轨道
|
// 可选字幕轨道
|
||||||
var subs = lists.Where(x => x.MediaType == MediaType.SUBTITLES);
|
var subs = lists.Where(x => x.MediaType == MediaType.SUBTITLES);
|
||||||
|
|
||||||
//尝试从URL或文件读取文件名
|
// 尝试从URL或文件读取文件名
|
||||||
if (string.IsNullOrEmpty(option.SaveName))
|
if (string.IsNullOrEmpty(option.SaveName))
|
||||||
{
|
{
|
||||||
option.SaveName = OtherUtil.GetFileNameFromInput(option.Input);
|
option.SaveName = OtherUtil.GetFileNameFromInput(option.Input);
|
||||||
}
|
}
|
||||||
|
|
||||||
//生成文件夹
|
// 生成文件夹
|
||||||
var tmpDir = Path.Combine(option.TmpDir ?? Environment.CurrentDirectory, $"{option.SaveName ?? DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}");
|
var tmpDir = Path.Combine(option.TmpDir ?? Environment.CurrentDirectory, $"{option.SaveName ?? DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}");
|
||||||
//记录文件
|
// 记录文件
|
||||||
extractor.RawFiles["meta.json"] = GlobalUtil.ConvertToJson(lists);
|
extractor.RawFiles["meta.json"] = GlobalUtil.ConvertToJson(lists);
|
||||||
//写出文件
|
// 写出文件
|
||||||
await WriteRawFilesAsync(option, extractor, tmpDir);
|
await WriteRawFilesAsync(option, extractor, tmpDir);
|
||||||
|
|
||||||
Logger.Info(ResString.streamsInfo, lists.Count(), basicStreams.Count(), audios.Count(), subs.Count());
|
Logger.Info(ResString.streamsInfo, lists.Count(), basicStreams.Count(), audios.Count(), subs.Count());
|
||||||
|
@ -285,40 +285,40 @@ namespace N_m3u8DL_RE
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//展示交互式选择框
|
// 展示交互式选择框
|
||||||
selectedStreams = FilterUtil.SelectStreams(lists);
|
selectedStreams = FilterUtil.SelectStreams(lists);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedStreams.Any())
|
if (!selectedStreams.Any())
|
||||||
throw new Exception(ResString.noStreamsToDownload);
|
throw new Exception(ResString.noStreamsToDownload);
|
||||||
|
|
||||||
//HLS: 选中流中若有没加载出playlist的,加载playlist
|
// HLS: 选中流中若有没加载出playlist的,加载playlist
|
||||||
//DASH/MSS: 加载playlist (调用url预处理器)
|
// DASH/MSS: 加载playlist (调用url预处理器)
|
||||||
if (selectedStreams.Any(s => s.Playlist == null) || extractor.ExtractorType == ExtractorType.MPEG_DASH || extractor.ExtractorType == ExtractorType.MSS)
|
if (selectedStreams.Any(s => s.Playlist == null) || extractor.ExtractorType == ExtractorType.MPEG_DASH || extractor.ExtractorType == ExtractorType.MSS)
|
||||||
await extractor.FetchPlayListAsync(selectedStreams);
|
await extractor.FetchPlayListAsync(selectedStreams);
|
||||||
|
|
||||||
//直播检测
|
// 直播检测
|
||||||
var livingFlag = selectedStreams.Any(s => s.Playlist?.IsLive == true) && !option.LivePerformAsVod;
|
var livingFlag = selectedStreams.Any(s => s.Playlist?.IsLive == true) && !option.LivePerformAsVod;
|
||||||
if (livingFlag)
|
if (livingFlag)
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp($"[white on darkorange3_1]{ResString.liveFound}[/]");
|
Logger.WarnMarkUp($"[white on darkorange3_1]{ResString.liveFound}[/]");
|
||||||
}
|
}
|
||||||
|
|
||||||
//无法识别的加密方式,自动开启二进制合并
|
// 无法识别的加密方式,自动开启二进制合并
|
||||||
if (selectedStreams.Any(s => s.Playlist.MediaParts.Any(p => p.MediaSegments.Any(m => m.EncryptInfo.Method == EncryptMethod.UNKNOWN))))
|
if (selectedStreams.Any(s => s.Playlist!.MediaParts.Any(p => p.MediaSegments.Any(m => m.EncryptInfo.Method == EncryptMethod.UNKNOWN))))
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge3}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge3}[/]");
|
||||||
option.BinaryMerge = true;
|
option.BinaryMerge = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//应用用户自定义的分片范围
|
// 应用用户自定义的分片范围
|
||||||
if (!livingFlag)
|
if (!livingFlag)
|
||||||
FilterUtil.ApplyCustomRange(selectedStreams, option.CustomRange);
|
FilterUtil.ApplyCustomRange(selectedStreams, option.CustomRange);
|
||||||
|
|
||||||
//应用用户自定义的广告分片关键字
|
// 应用用户自定义的广告分片关键字
|
||||||
FilterUtil.CleanAd(selectedStreams, option.AdKeywords);
|
FilterUtil.CleanAd(selectedStreams, option.AdKeywords);
|
||||||
|
|
||||||
//记录文件
|
// 记录文件
|
||||||
extractor.RawFiles["meta_selected.json"] = GlobalUtil.ConvertToJson(selectedStreams);
|
extractor.RawFiles["meta_selected.json"] = GlobalUtil.ConvertToJson(selectedStreams);
|
||||||
|
|
||||||
Logger.Info(ResString.selectedStream);
|
Logger.Info(ResString.selectedStream);
|
||||||
|
@ -327,7 +327,7 @@ namespace N_m3u8DL_RE
|
||||||
Logger.InfoMarkUp(item.ToString());
|
Logger.InfoMarkUp(item.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
//写出文件
|
// 写出文件
|
||||||
await WriteRawFilesAsync(option, extractor, tmpDir);
|
await WriteRawFilesAsync(option, extractor, tmpDir);
|
||||||
|
|
||||||
if (option.SkipDownload)
|
if (option.SkipDownload)
|
||||||
|
@ -342,19 +342,19 @@ namespace N_m3u8DL_RE
|
||||||
|
|
||||||
Logger.InfoMarkUp(ResString.saveName + $"[deepskyblue1]{option.SaveName.EscapeMarkup()}[/]");
|
Logger.InfoMarkUp(ResString.saveName + $"[deepskyblue1]{option.SaveName.EscapeMarkup()}[/]");
|
||||||
|
|
||||||
//开始MuxAfterDone后自动使用二进制版
|
// 开始MuxAfterDone后自动使用二进制版
|
||||||
if (!option.BinaryMerge && option.MuxAfterDone)
|
if (!option.BinaryMerge && option.MuxAfterDone)
|
||||||
{
|
{
|
||||||
option.BinaryMerge = true;
|
option.BinaryMerge = true;
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge6}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge6}[/]");
|
||||||
}
|
}
|
||||||
|
|
||||||
//下载配置
|
// 下载配置
|
||||||
var downloadConfig = new DownloaderConfig()
|
var downloadConfig = new DownloaderConfig()
|
||||||
{
|
{
|
||||||
MyOptions = option,
|
MyOptions = option,
|
||||||
DirPrefix = tmpDir,
|
DirPrefix = tmpDir,
|
||||||
Headers = parserConfig.Headers, //使用命令行解析得到的Headers
|
Headers = parserConfig.Headers, // 使用命令行解析得到的Headers
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = false;
|
var result = false;
|
||||||
|
@ -366,7 +366,7 @@ namespace N_m3u8DL_RE
|
||||||
}
|
}
|
||||||
else if (!livingFlag)
|
else if (!livingFlag)
|
||||||
{
|
{
|
||||||
//开始下载
|
// 开始下载
|
||||||
var sdm = new SimpleDownloadManager(downloadConfig, selectedStreams, extractor);
|
var sdm = new SimpleDownloadManager(downloadConfig, selectedStreams, extractor);
|
||||||
result = await sdm.StartDownloadAsync();
|
result = await sdm.StartDownloadAsync();
|
||||||
}
|
}
|
||||||
|
@ -389,7 +389,7 @@ namespace N_m3u8DL_RE
|
||||||
|
|
||||||
private static async Task WriteRawFilesAsync(MyOption option, StreamExtractor extractor, string tmpDir)
|
private static async Task WriteRawFilesAsync(MyOption option, StreamExtractor extractor, string tmpDir)
|
||||||
{
|
{
|
||||||
//写出json文件
|
// 写出json文件
|
||||||
if (option.WriteMetaJson)
|
if (option.WriteMetaJson)
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(tmpDir)) Directory.CreateDirectory(tmpDir);
|
if (!Directory.Exists(tmpDir)) Directory.CreateDirectory(tmpDir);
|
||||||
|
@ -422,10 +422,10 @@ namespace N_m3u8DL_RE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//重定向
|
// 重定向
|
||||||
static async Task<string> Get302Async(string url)
|
static async Task<string> Get302Async(string url)
|
||||||
{
|
{
|
||||||
//this allows you to set the settings so that we can get the redirect url
|
// this allows you to set the settings so that we can get the redirect url
|
||||||
var handler = new HttpClientHandler()
|
var handler = new HttpClientHandler()
|
||||||
{
|
{
|
||||||
AllowAutoRedirect = false
|
AllowAutoRedirect = false
|
||||||
|
@ -448,5 +448,4 @@ namespace N_m3u8DL_RE
|
||||||
|
|
||||||
return redirectedUrl;
|
return redirectedUrl;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -2,8 +2,6 @@
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util;
|
namespace N_m3u8DL_RE.Util;
|
||||||
|
@ -111,16 +109,16 @@ internal static class DownloadUtil
|
||||||
size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token);
|
size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token);
|
||||||
speedContainer.Add(size);
|
speedContainer.Add(size);
|
||||||
await stream.WriteAsync(buffer, 0, size);
|
await stream.WriteAsync(buffer, 0, size);
|
||||||
//检测imageHeader
|
// 检测imageHeader
|
||||||
bool imageHeader = ImageHeaderUtil.IsImageHeader(buffer);
|
bool imageHeader = ImageHeaderUtil.IsImageHeader(buffer);
|
||||||
//检测GZip(For DDP Audio)
|
// 检测GZip(For DDP Audio)
|
||||||
bool gZipHeader = buffer.Length > 2 && buffer[0] == 0x1f && buffer[1] == 0x8b;
|
bool gZipHeader = buffer.Length > 2 && buffer[0] == 0x1f && buffer[1] == 0x8b;
|
||||||
|
|
||||||
while ((size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token)) > 0)
|
while ((size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token)) > 0)
|
||||||
{
|
{
|
||||||
speedContainer.Add(size);
|
speedContainer.Add(size);
|
||||||
await stream.WriteAsync(buffer, 0, size);
|
await stream.WriteAsync(buffer, 0, size);
|
||||||
//限速策略
|
// 限速策略
|
||||||
while (speedContainer.Downloaded > speedContainer.SpeedLimit)
|
while (speedContainer.Downloaded > speedContainer.SpeedLimit)
|
||||||
{
|
{
|
||||||
await Task.Delay(1);
|
await Task.Delay(1);
|
||||||
|
|
|
@ -4,12 +4,7 @@ using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util;
|
namespace N_m3u8DL_RE.Util;
|
||||||
|
|
||||||
|
@ -17,7 +12,7 @@ public static class FilterUtil
|
||||||
{
|
{
|
||||||
public static List<StreamSpec> DoFilterKeep(IEnumerable<StreamSpec> lists, StreamFilter? filter)
|
public static List<StreamSpec> DoFilterKeep(IEnumerable<StreamSpec> lists, StreamFilter? filter)
|
||||||
{
|
{
|
||||||
if (filter == null) return new List<StreamSpec>();
|
if (filter == null) return [];
|
||||||
|
|
||||||
var inputs = lists.Where(_ => true);
|
var inputs = lists.Where(_ => true);
|
||||||
if (filter.GroupIdReg != null)
|
if (filter.GroupIdReg != null)
|
||||||
|
@ -56,13 +51,13 @@ public static class FilterUtil
|
||||||
var bestNumberStr = filter.For.Replace("best", "");
|
var bestNumberStr = filter.For.Replace("best", "");
|
||||||
var worstNumberStr = filter.For.Replace("worst", "");
|
var worstNumberStr = filter.For.Replace("worst", "");
|
||||||
|
|
||||||
if (filter.For == "best" && inputs.Count() > 0)
|
if (filter.For == "best" && inputs.Any())
|
||||||
inputs = inputs.Take(1).ToList();
|
inputs = inputs.Take(1).ToList();
|
||||||
else if (filter.For == "worst" && inputs.Count() > 0)
|
else if (filter.For == "worst" && inputs.Any())
|
||||||
inputs = inputs.TakeLast(1).ToList();
|
inputs = inputs.TakeLast(1).ToList();
|
||||||
else if (int.TryParse(bestNumberStr, out int bestNumber) && inputs.Count() > 0)
|
else if (int.TryParse(bestNumberStr, out int bestNumber) && inputs.Any())
|
||||||
inputs = inputs.Take(bestNumber).ToList();
|
inputs = inputs.Take(bestNumber).ToList();
|
||||||
else if (int.TryParse(worstNumberStr, out int worstNumber) && inputs.Count() > 0)
|
else if (int.TryParse(worstNumberStr, out int worstNumber) && inputs.Any())
|
||||||
inputs = inputs.TakeLast(worstNumber).ToList();
|
inputs = inputs.TakeLast(worstNumber).ToList();
|
||||||
|
|
||||||
return inputs.ToList();
|
return inputs.ToList();
|
||||||
|
@ -82,15 +77,16 @@ public static class FilterUtil
|
||||||
|
|
||||||
public static List<StreamSpec> SelectStreams(IEnumerable<StreamSpec> lists)
|
public static List<StreamSpec> SelectStreams(IEnumerable<StreamSpec> lists)
|
||||||
{
|
{
|
||||||
if (lists.Count() == 1)
|
var streamSpecs = lists.ToList();
|
||||||
return new List<StreamSpec>(lists);
|
if (streamSpecs.Count == 1)
|
||||||
|
return [..streamSpecs];
|
||||||
|
|
||||||
//基本流
|
// 基本流
|
||||||
var basicStreams = lists.Where(x => x.MediaType == null);
|
var basicStreams = streamSpecs.Where(x => x.MediaType == null).ToList();
|
||||||
//可选音频轨道
|
// 可选音频轨道
|
||||||
var audios = lists.Where(x => x.MediaType == MediaType.AUDIO);
|
var audios = streamSpecs.Where(x => x.MediaType == MediaType.AUDIO).ToList();
|
||||||
//可选字幕轨道
|
// 可选字幕轨道
|
||||||
var subs = lists.Where(x => x.MediaType == MediaType.SUBTITLES);
|
var subs = streamSpecs.Where(x => x.MediaType == MediaType.SUBTITLES).ToList();
|
||||||
|
|
||||||
var prompt = new MultiSelectionPrompt<StreamSpec>()
|
var prompt = new MultiSelectionPrompt<StreamSpec>()
|
||||||
.Title(ResString.promptTitle)
|
.Title(ResString.promptTitle)
|
||||||
|
@ -107,8 +103,8 @@ public static class FilterUtil
|
||||||
.InstructionsText(ResString.promptInfo)
|
.InstructionsText(ResString.promptInfo)
|
||||||
;
|
;
|
||||||
|
|
||||||
//默认选中第一个
|
// 默认选中第一个
|
||||||
var first = lists.First();
|
var first = streamSpecs.First();
|
||||||
prompt.Select(first);
|
prompt.Select(first);
|
||||||
|
|
||||||
if (basicStreams.Any())
|
if (basicStreams.Any())
|
||||||
|
@ -119,7 +115,7 @@ public static class FilterUtil
|
||||||
if (audios.Any())
|
if (audios.Any())
|
||||||
{
|
{
|
||||||
prompt.AddChoiceGroup(new StreamSpec() { Name = "__Audio" }, audios);
|
prompt.AddChoiceGroup(new StreamSpec() { Name = "__Audio" }, audios);
|
||||||
//默认音轨
|
// 默认音轨
|
||||||
if (first.AudioId != null)
|
if (first.AudioId != null)
|
||||||
{
|
{
|
||||||
prompt.Select(audios.First(a => a.GroupId == first.AudioId));
|
prompt.Select(audios.First(a => a.GroupId == first.AudioId));
|
||||||
|
@ -128,17 +124,17 @@ public static class FilterUtil
|
||||||
if (subs.Any())
|
if (subs.Any())
|
||||||
{
|
{
|
||||||
prompt.AddChoiceGroup(new StreamSpec() { Name = "__Subtitle" }, subs);
|
prompt.AddChoiceGroup(new StreamSpec() { Name = "__Subtitle" }, subs);
|
||||||
//默认字幕轨
|
// 默认字幕轨
|
||||||
if (first.SubtitleId != null)
|
if (first.SubtitleId != null)
|
||||||
{
|
{
|
||||||
prompt.Select(subs.First(s => s.GroupId == first.SubtitleId));
|
prompt.Select(subs.First(s => s.GroupId == first.SubtitleId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//如果此时还是没有选中任何流,自动选择一个
|
// 如果此时还是没有选中任何流,自动选择一个
|
||||||
prompt.Select(basicStreams.Concat(audios).Concat(subs).First());
|
prompt.Select(basicStreams.Concat(audios).Concat(subs).First());
|
||||||
|
|
||||||
//多选
|
// 多选
|
||||||
var selectedStreams = CustomAnsiConsole.Console.Prompt(prompt);
|
var selectedStreams = CustomAnsiConsole.Console.Prompt(prompt);
|
||||||
|
|
||||||
return selectedStreams;
|
return selectedStreams;
|
||||||
|
@ -147,11 +143,11 @@ public static class FilterUtil
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 直播使用。对齐各个轨道的起始。
|
/// 直播使用。对齐各个轨道的起始。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="streams"></param>
|
/// <param name="selectedSteams"></param>
|
||||||
/// <param name="takeLastCount"></param>
|
/// <param name="takeLastCount"></param>
|
||||||
public static void SyncStreams(List<StreamSpec> selectedSteams, int takeLastCount = 15)
|
public static void SyncStreams(List<StreamSpec> selectedSteams, int takeLastCount = 15)
|
||||||
{
|
{
|
||||||
//通过Date同步
|
// 通过Date同步
|
||||||
if (selectedSteams.All(x => x.Playlist!.MediaParts[0].MediaSegments.All(x => x.DateTime != null)))
|
if (selectedSteams.All(x => x.Playlist!.MediaParts[0].MediaSegments.All(x => x.DateTime != null)))
|
||||||
{
|
{
|
||||||
var minDate = selectedSteams.Max(s => s.Playlist!.MediaParts[0].MediaSegments.Min(s => s.DateTime))!;
|
var minDate = selectedSteams.Max(s => s.Playlist!.MediaParts[0].MediaSegments.Min(s => s.DateTime))!;
|
||||||
|
@ -159,12 +155,12 @@ public static class FilterUtil
|
||||||
{
|
{
|
||||||
foreach (var part in item.Playlist!.MediaParts)
|
foreach (var part in item.Playlist!.MediaParts)
|
||||||
{
|
{
|
||||||
//秒级同步 忽略毫秒
|
// 秒级同步 忽略毫秒
|
||||||
part.MediaSegments = part.MediaSegments.Where(s => s.DateTime!.Value.Ticks / TimeSpan.TicksPerSecond >= minDate.Value.Ticks / TimeSpan.TicksPerSecond).ToList();
|
part.MediaSegments = part.MediaSegments.Where(s => s.DateTime!.Value.Ticks / TimeSpan.TicksPerSecond >= minDate.Value.Ticks / TimeSpan.TicksPerSecond).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else //通过index同步
|
else // 通过index同步
|
||||||
{
|
{
|
||||||
var minIndex = selectedSteams.Max(s => s.Playlist!.MediaParts[0].MediaSegments.Min(s => s.Index));
|
var minIndex = selectedSteams.Max(s => s.Playlist!.MediaParts[0].MediaSegments.Min(s => s.Index));
|
||||||
foreach (var item in selectedSteams)
|
foreach (var item in selectedSteams)
|
||||||
|
@ -176,7 +172,7 @@ public static class FilterUtil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//取最新的N个分片
|
// 取最新的N个分片
|
||||||
if (selectedSteams.Any(x => x.Playlist!.MediaParts[0].MediaSegments.Count > takeLastCount))
|
if (selectedSteams.Any(x => x.Playlist!.MediaParts[0].MediaSegments.Count > takeLastCount))
|
||||||
{
|
{
|
||||||
var skipCount = selectedSteams.Min(x => x.Playlist!.MediaParts[0].MediaSegments.Count) - takeLastCount + 1;
|
var skipCount = selectedSteams.Min(x => x.Playlist!.MediaParts[0].MediaSegments.Count) - takeLastCount + 1;
|
||||||
|
@ -198,17 +194,15 @@ public static class FilterUtil
|
||||||
/// <param name="customRange"></param>
|
/// <param name="customRange"></param>
|
||||||
public static void ApplyCustomRange(List<StreamSpec> selectedSteams, CustomRange? customRange)
|
public static void ApplyCustomRange(List<StreamSpec> selectedSteams, CustomRange? customRange)
|
||||||
{
|
{
|
||||||
var resultList = selectedSteams.Select(x => 0d).ToList();
|
|
||||||
|
|
||||||
if (customRange == null) return;
|
if (customRange == null) return;
|
||||||
|
|
||||||
Logger.InfoMarkUp($"{ResString.customRangeFound}[Cyan underline]{customRange.InputStr}[/]");
|
Logger.InfoMarkUp($"{ResString.customRangeFound}[Cyan underline]{customRange.InputStr}[/]");
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.customRangeWarn}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.customRangeWarn}[/]");
|
||||||
|
|
||||||
var filteByIndex = customRange.StartSegIndex != null && customRange.EndSegIndex != null;
|
var filterByIndex = customRange is { StartSegIndex: not null, EndSegIndex: not null };
|
||||||
var filteByTime = customRange.StartSec != null && customRange.EndSec != null;
|
var filterByTime = customRange is { StartSec: not null, EndSec: not null };
|
||||||
|
|
||||||
if (!filteByIndex && !filteByTime)
|
if (!filterByIndex && !filterByTime)
|
||||||
{
|
{
|
||||||
Logger.ErrorMarkUp(ResString.customRangeInvalid);
|
Logger.ErrorMarkUp(ResString.customRangeInvalid);
|
||||||
return;
|
return;
|
||||||
|
@ -220,8 +214,8 @@ public static class FilterUtil
|
||||||
if (stream.Playlist == null) continue;
|
if (stream.Playlist == null) continue;
|
||||||
foreach (var part in stream.Playlist.MediaParts)
|
foreach (var part in stream.Playlist.MediaParts)
|
||||||
{
|
{
|
||||||
var newSegments = new List<MediaSegment>();
|
List<MediaSegment> newSegments;
|
||||||
if (filteByIndex)
|
if (filterByIndex)
|
||||||
newSegments = part.MediaSegments.Where(seg => seg.Index >= customRange.StartSegIndex && seg.Index <= customRange.EndSegIndex).ToList();
|
newSegments = part.MediaSegments.Where(seg => seg.Index >= customRange.StartSegIndex && seg.Index <= customRange.EndSegIndex).ToList();
|
||||||
else
|
else
|
||||||
newSegments = part.MediaSegments.Where(seg => stream.Playlist.MediaParts.SelectMany(p => p.MediaSegments).Where(x => x.Index < seg.Index).Sum(x => x.Duration) >= customRange.StartSec
|
newSegments = part.MediaSegments.Where(seg => stream.Playlist.MediaParts.SelectMany(p => p.MediaSegments).Where(x => x.Index < seg.Index).Sum(x => x.Duration) >= customRange.StartSec
|
||||||
|
@ -239,11 +233,11 @@ public static class FilterUtil
|
||||||
/// 根据用户输入,清除广告分片
|
/// 根据用户输入,清除广告分片
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="selectedSteams"></param>
|
/// <param name="selectedSteams"></param>
|
||||||
/// <param name="customRange"></param>
|
/// <param name="keywords"></param>
|
||||||
public static void CleanAd(List<StreamSpec> selectedSteams, string[]? keywords)
|
public static void CleanAd(List<StreamSpec> selectedSteams, string[]? keywords)
|
||||||
{
|
{
|
||||||
if (keywords == null) return;
|
if (keywords == null) return;
|
||||||
var regList = keywords.Select(s => new Regex(s));
|
var regList = keywords.Select(s => new Regex(s)).ToList();
|
||||||
foreach ( var reg in regList)
|
foreach ( var reg in regList)
|
||||||
{
|
{
|
||||||
Logger.InfoMarkUp($"{ResString.customAdKeywordsFound}[Cyan underline]{reg}[/]");
|
Logger.InfoMarkUp($"{ResString.customAdKeywordsFound}[Cyan underline]{reg}[/]");
|
||||||
|
@ -257,19 +251,16 @@ public static class FilterUtil
|
||||||
|
|
||||||
foreach (var part in stream.Playlist.MediaParts)
|
foreach (var part in stream.Playlist.MediaParts)
|
||||||
{
|
{
|
||||||
//没有找到广告分片
|
// 没有找到广告分片
|
||||||
if (part.MediaSegments.All(x => regList.All(reg => !reg.IsMatch(x.Url))))
|
if (part.MediaSegments.All(x => regList.All(reg => !reg.IsMatch(x.Url))))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
//找到广告分片 清理
|
// 找到广告分片 清理
|
||||||
else
|
|
||||||
{
|
|
||||||
part.MediaSegments = part.MediaSegments.Where(x => regList.All(reg => !reg.IsMatch(x.Url))).ToList();
|
part.MediaSegments = part.MediaSegments.Where(x => regList.All(reg => !reg.IsMatch(x.Url))).ToList();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//清理已经为空的 part
|
// 清理已经为空的 part
|
||||||
stream.Playlist.MediaParts = stream.Playlist.MediaParts.Where(x => x.MediaSegments.Count > 0).ToList();
|
stream.Playlist.MediaParts = stream.Playlist.MediaParts.Where(x => x.MediaSegments.Count > 0).ToList();
|
||||||
|
|
||||||
var countAfter = stream.SegmentsCount;
|
var countAfter = stream.SegmentsCount;
|
||||||
|
|
|
@ -5,17 +5,17 @@ internal static class ImageHeaderUtil
|
||||||
public static bool IsImageHeader(byte[] bArr)
|
public static bool IsImageHeader(byte[] bArr)
|
||||||
{
|
{
|
||||||
var size = bArr.Length;
|
var size = bArr.Length;
|
||||||
//PNG HEADER检测
|
// PNG HEADER检测
|
||||||
if (size > 3 && 137 == bArr[0] && 80 == bArr[1] && 78 == bArr[2] && 71 == bArr[3])
|
if (size > 3 && 137 == bArr[0] && 80 == bArr[1] && 78 == bArr[2] && 71 == bArr[3])
|
||||||
return true;
|
return true;
|
||||||
//GIF HEADER检测
|
// GIF HEADER检测
|
||||||
else if (size > 3 && 0x47 == bArr[0] && 0x49 == bArr[1] && 0x46 == bArr[2] && 0x38 == bArr[3])
|
if (size > 3 && 0x47 == bArr[0] && 0x49 == bArr[1] && 0x46 == bArr[2] && 0x38 == bArr[3])
|
||||||
return true;
|
return true;
|
||||||
//BMP HEADER检测
|
// BMP HEADER检测
|
||||||
else if (size > 10 && 0x42 == bArr[0] && 0x4D == bArr[1] && 0x00 == bArr[5] && 0x00 == bArr[6] && 0x00 == bArr[7] && 0x00 == bArr[8])
|
if (size > 10 && 0x42 == bArr[0] && 0x4D == bArr[1] && 0x00 == bArr[5] && 0x00 == bArr[6] && 0x00 == bArr[7] && 0x00 == bArr[8])
|
||||||
return true;
|
return true;
|
||||||
//JPEG HEADER检测
|
// JPEG HEADER检测
|
||||||
else if (size > 3 && 0xFF == bArr[0] && 0xD8 == bArr[1] && 0xFF == bArr[2])
|
if (size > 3 && 0xFF == bArr[0] && 0xD8 == bArr[1] && 0xFF == bArr[2])
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ internal static class ImageHeaderUtil
|
||||||
{
|
{
|
||||||
var sourceData = await File.ReadAllBytesAsync(sourcePath);
|
var sourceData = await File.ReadAllBytesAsync(sourcePath);
|
||||||
|
|
||||||
//PNG HEADER
|
// PNG HEADER
|
||||||
if (137 == sourceData[0] && 80 == sourceData[1] && 78 == sourceData[2] && 71 == sourceData[3])
|
if (137 == sourceData[0] && 80 == sourceData[1] && 78 == sourceData[2] && 71 == sourceData[3])
|
||||||
{
|
{
|
||||||
if (sourceData.Length > 120 && 137 == sourceData[0] && 80 == sourceData[1] && 78 == sourceData[2] && 71 == sourceData[3] && 96 == sourceData[118] && 130 == sourceData[119])
|
if (sourceData.Length > 120 && 137 == sourceData[0] && 80 == sourceData[1] && 78 == sourceData[2] && 71 == sourceData[3] && 96 == sourceData[118] && 130 == sourceData[119])
|
||||||
|
@ -37,7 +37,7 @@ internal static class ImageHeaderUtil
|
||||||
sourceData = sourceData[771..];
|
sourceData = sourceData[771..];
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//手动查询结尾标记 0x47 出现两次
|
// 手动查询结尾标记 0x47 出现两次
|
||||||
int skip = 0;
|
int skip = 0;
|
||||||
for (int i = 4; i < sourceData.Length - 188 * 2 - 4; i++)
|
for (int i = 4; i < sourceData.Length - 188 * 2 - 4; i++)
|
||||||
{
|
{
|
||||||
|
@ -50,20 +50,20 @@ internal static class ImageHeaderUtil
|
||||||
sourceData = sourceData[skip..];
|
sourceData = sourceData[skip..];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//GIF HEADER
|
// GIF HEADER
|
||||||
else if (0x47 == sourceData[0] && 0x49 == sourceData[1] && 0x46 == sourceData[2] && 0x38 == sourceData[3])
|
else if (0x47 == sourceData[0] && 0x49 == sourceData[1] && 0x46 == sourceData[2] && 0x38 == sourceData[3])
|
||||||
{
|
{
|
||||||
sourceData = sourceData[42..];
|
sourceData = sourceData[42..];
|
||||||
}
|
}
|
||||||
//BMP HEADER
|
// BMP HEADER
|
||||||
else if (0x42 == sourceData[0] && 0x4D == sourceData[1] && 0x00 == sourceData[5] && 0x00 == sourceData[6] && 0x00 == sourceData[7] && 0x00 == sourceData[8])
|
else if (0x42 == sourceData[0] && 0x4D == sourceData[1] && 0x00 == sourceData[5] && 0x00 == sourceData[6] && 0x00 == sourceData[7] && 0x00 == sourceData[8])
|
||||||
{
|
{
|
||||||
sourceData = sourceData[0x3E..];
|
sourceData = sourceData[0x3E..];
|
||||||
}
|
}
|
||||||
//JPEG HEADER检测
|
// JPEG HEADER检测
|
||||||
else if (0xFF == sourceData[0] && 0xD8 == sourceData[1] && 0xFF == sourceData[2])
|
else if (0xFF == sourceData[0] && 0xD8 == sourceData[1] && 0xFF == sourceData[2])
|
||||||
{
|
{
|
||||||
//手动查询结尾标记 0x47 出现两次
|
// 手动查询结尾标记 0x47 出现两次
|
||||||
int skip = 0;
|
int skip = 0;
|
||||||
for (int i = 4; i < sourceData.Length - 188 * 2 - 4; i++)
|
for (int i = 4; i < sourceData.Length - 188 * 2 - 4; i++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,33 +1,19 @@
|
||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util;
|
namespace N_m3u8DL_RE.Util;
|
||||||
|
|
||||||
class Language
|
internal class Language(string extendCode, string code, string desc, string descA)
|
||||||
{
|
{
|
||||||
public string Code;
|
public readonly string Code = code;
|
||||||
public string ExtendCode;
|
public readonly string ExtendCode = extendCode;
|
||||||
public string Description;
|
public readonly string Description = desc;
|
||||||
public string DescriptionAudio;
|
public readonly string DescriptionAudio = descA;
|
||||||
|
|
||||||
public Language(string extendCode, string code, string desc, string descA)
|
|
||||||
{
|
|
||||||
Code = code;
|
|
||||||
ExtendCode = extendCode;
|
|
||||||
Description = desc;
|
|
||||||
DescriptionAudio = descA;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class LanguageCodeUtil
|
internal static class LanguageCodeUtil
|
||||||
{
|
{
|
||||||
|
|
||||||
private readonly static List<Language> ALL_LANGS = @"
|
private static readonly List<Language> ALL_LANGS = @"
|
||||||
af;afr;Afrikaans;Afrikaans
|
af;afr;Afrikaans;Afrikaans
|
||||||
af-ZA;afr;Afrikaans (South Africa);Afrikaans (South Africa)
|
af-ZA;afr;Afrikaans (South Africa);Afrikaans (South Africa)
|
||||||
am;amh;Amharic;Amharic
|
am;amh;Amharic;Amharic
|
||||||
|
@ -389,8 +375,8 @@ MA;msa;Melayu;Melayu
|
||||||
"
|
"
|
||||||
.Trim().Replace("\r", "").Split('\n').Where(x => !string.IsNullOrWhiteSpace(x)).Select(x =>
|
.Trim().Replace("\r", "").Split('\n').Where(x => !string.IsNullOrWhiteSpace(x)).Select(x =>
|
||||||
{
|
{
|
||||||
var arr = x.Trim().Split(';');
|
var arr = x.Trim().Split(';', StringSplitOptions.TrimEntries);
|
||||||
return new Language(arr[0].Trim(), arr[1].Trim(), arr[2].Trim(), arr[3].Trim());
|
return new Language(arr[0], arr[1], arr[2], arr[3]);
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
private static Dictionary<string, string> CODE_MAP = @"
|
private static Dictionary<string, string> CODE_MAP = @"
|
||||||
|
@ -504,8 +490,7 @@ sr;srp
|
||||||
|
|
||||||
private static string ConvertTwoToThree(string input)
|
private static string ConvertTwoToThree(string input)
|
||||||
{
|
{
|
||||||
if (CODE_MAP.TryGetValue(input, out var code)) return code;
|
return CODE_MAP.GetValueOrDefault(input, input);
|
||||||
return input;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -518,12 +503,12 @@ sr;srp
|
||||||
if (string.IsNullOrEmpty(outputFile.LangCode)) return;
|
if (string.IsNullOrEmpty(outputFile.LangCode)) return;
|
||||||
var originalLangCode = outputFile.LangCode;
|
var originalLangCode = outputFile.LangCode;
|
||||||
|
|
||||||
//先直接查找
|
// 先直接查找
|
||||||
var lang = ALL_LANGS.FirstOrDefault(a => a.ExtendCode.Equals(outputFile.LangCode, StringComparison.OrdinalIgnoreCase) || a.Code.Equals(outputFile.LangCode, StringComparison.OrdinalIgnoreCase));
|
var lang = ALL_LANGS.FirstOrDefault(a => a.ExtendCode.Equals(outputFile.LangCode, StringComparison.OrdinalIgnoreCase) || a.Code.Equals(outputFile.LangCode, StringComparison.OrdinalIgnoreCase));
|
||||||
//处理特殊的扩展语言标记
|
// 处理特殊的扩展语言标记
|
||||||
if (lang == null)
|
if (lang == null)
|
||||||
{
|
{
|
||||||
//2位转3位
|
// 2位转3位
|
||||||
var l = ConvertTwoToThree(outputFile.LangCode.Split('-').First());
|
var l = ConvertTwoToThree(outputFile.LangCode.Split('-').First());
|
||||||
lang = ALL_LANGS.FirstOrDefault(a => a.ExtendCode.Equals(l, StringComparison.OrdinalIgnoreCase) || a.Code.Equals(l, StringComparison.OrdinalIgnoreCase));
|
lang = ALL_LANGS.FirstOrDefault(a => a.ExtendCode.Equals(l, StringComparison.OrdinalIgnoreCase) || a.Code.Equals(l, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
@ -536,10 +521,10 @@ sr;srp
|
||||||
}
|
}
|
||||||
else if (outputFile.LangCode == null)
|
else if (outputFile.LangCode == null)
|
||||||
{
|
{
|
||||||
outputFile.LangCode = "und"; //无法识别直接置为und
|
outputFile.LangCode = "und"; // 无法识别直接置为und
|
||||||
}
|
}
|
||||||
|
|
||||||
//无描述,则把LangCode当作描述
|
// 无描述,则把LangCode当作描述
|
||||||
if (string.IsNullOrEmpty(outputFile.Description)) outputFile.Description = originalLangCode;
|
if (string.IsNullOrEmpty(outputFile.Description)) outputFile.Description = originalLangCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,13 +1,6 @@
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using NiL.JS.Expressions;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util;
|
namespace N_m3u8DL_RE.Util;
|
||||||
|
|
||||||
|
@ -15,17 +8,16 @@ internal static class LargeSingleFileSplitUtil
|
||||||
{
|
{
|
||||||
class Clip
|
class Clip
|
||||||
{
|
{
|
||||||
public required int index;
|
public required int Index;
|
||||||
public required long from;
|
public required long From;
|
||||||
public required long to;
|
public required long To;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// URL大文件切片处理
|
/// URL大文件切片处理
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="url"></param>
|
/// <param name="segment"></param>
|
||||||
/// <param name="headers"></param>
|
/// <param name="headers"></param>
|
||||||
/// <param name="splitSegments"></param>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static async Task<List<MediaSegment>?> SplitUrlAsync(MediaSegment segment, Dictionary<string,string> headers)
|
public static async Task<List<MediaSegment>?> SplitUrlAsync(MediaSegment segment, Dictionary<string,string> headers)
|
||||||
{
|
{
|
||||||
|
@ -43,10 +35,10 @@ internal static class LargeSingleFileSplitUtil
|
||||||
{
|
{
|
||||||
splitSegments.Add(new MediaSegment()
|
splitSegments.Add(new MediaSegment()
|
||||||
{
|
{
|
||||||
Index = clip.index,
|
Index = clip.Index,
|
||||||
Url = url,
|
Url = url,
|
||||||
StartRange = clip.from,
|
StartRange = clip.From,
|
||||||
ExpectLength = clip.to == -1 ? null : clip.to - clip.from + 1,
|
ExpectLength = clip.To == -1 ? null : clip.To - clip.From + 1,
|
||||||
EncryptInfo = segment.EncryptInfo,
|
EncryptInfo = segment.EncryptInfo,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -85,7 +77,7 @@ internal static class LargeSingleFileSplitUtil
|
||||||
return totalSizeBytes;
|
return totalSizeBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
//此函数主要是切片下载逻辑
|
// 此函数主要是切片下载逻辑
|
||||||
private static List<Clip> GetAllClips(string url, long fileSize)
|
private static List<Clip> GetAllClips(string url, long fileSize)
|
||||||
{
|
{
|
||||||
List<Clip> clips = new();
|
List<Clip> clips = new();
|
||||||
|
@ -96,11 +88,11 @@ internal static class LargeSingleFileSplitUtil
|
||||||
{
|
{
|
||||||
Clip c = new()
|
Clip c = new()
|
||||||
{
|
{
|
||||||
index = index,
|
Index = index,
|
||||||
from = counter,
|
From = counter,
|
||||||
to = counter + perSize
|
To = counter + perSize
|
||||||
};
|
};
|
||||||
//没到最后
|
// 没到最后
|
||||||
if (fileSize - perSize > 0)
|
if (fileSize - perSize > 0)
|
||||||
{
|
{
|
||||||
fileSize -= perSize;
|
fileSize -= perSize;
|
||||||
|
@ -108,10 +100,10 @@ internal static class LargeSingleFileSplitUtil
|
||||||
index++;
|
index++;
|
||||||
clips.Add(c);
|
clips.Add(c);
|
||||||
}
|
}
|
||||||
//已到最后
|
// 已到最后
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
c.to = -1;
|
c.To = -1;
|
||||||
clips.Add(c);
|
clips.Add(c);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace N_m3u8DL_RE.Util;
|
||||||
|
|
||||||
internal static class MP4DecryptUtil
|
internal static class MP4DecryptUtil
|
||||||
{
|
{
|
||||||
private static string ZeroKid = "00000000000000000000000000000000";
|
private static readonly string ZeroKid = "00000000000000000000000000000000";
|
||||||
public static async Task<bool> DecryptAsync(bool shakaPackager, string bin, string[]? keys, string source, string dest, string? kid, string init = "", bool isMultiDRM=false)
|
public static async Task<bool> DecryptAsync(bool shakaPackager, string bin, string[]? keys, string source, string dest, string? kid, string init = "", bool isMultiDRM=false)
|
||||||
{
|
{
|
||||||
if (keys == null || keys.Length == 0) return false;
|
if (keys == null || keys.Length == 0) return false;
|
||||||
|
@ -25,7 +25,7 @@ internal static class MP4DecryptUtil
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(kid))
|
if (!string.IsNullOrEmpty(kid))
|
||||||
{
|
{
|
||||||
var test = keyPairs.Where(k => k.StartsWith(kid));
|
var test = keyPairs.Where(k => k.StartsWith(kid)).ToList();
|
||||||
if (test.Any()) keyPair = test.First();
|
if (test.Any()) keyPair = test.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,16 +45,16 @@ internal static class MP4DecryptUtil
|
||||||
|
|
||||||
if (keyPair == null) return false;
|
if (keyPair == null) return false;
|
||||||
|
|
||||||
//shakaPackager 无法单独解密init文件
|
// shakaPackager 无法单独解密init文件
|
||||||
if (source.EndsWith("_init.mp4") && shakaPackager) return false;
|
if (source.EndsWith("_init.mp4") && shakaPackager) return false;
|
||||||
|
|
||||||
var cmd = "";
|
string cmd;
|
||||||
|
|
||||||
var tmpFile = "";
|
var tmpFile = "";
|
||||||
if (shakaPackager)
|
if (shakaPackager)
|
||||||
{
|
{
|
||||||
var enc = source;
|
var enc = source;
|
||||||
//shakaPackager 手动构造文件
|
// shakaPackager 手动构造文件
|
||||||
if (init != "")
|
if (init != "")
|
||||||
{
|
{
|
||||||
tmpFile = Path.ChangeExtension(source, ".itmp");
|
tmpFile = Path.ChangeExtension(source, ".itmp");
|
||||||
|
@ -101,8 +101,8 @@ internal static class MP4DecryptUtil
|
||||||
{
|
{
|
||||||
FileName = name,
|
FileName = name,
|
||||||
Arguments = arg,
|
Arguments = arg,
|
||||||
//RedirectStandardOutput = true,
|
// RedirectStandardOutput = true,
|
||||||
//RedirectStandardError = true,
|
// RedirectStandardError = true,
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
UseShellExecute = false
|
UseShellExecute = false
|
||||||
})!.WaitForExitAsync();
|
})!.WaitForExitAsync();
|
||||||
|
@ -124,8 +124,7 @@ internal static class MP4DecryptUtil
|
||||||
Logger.InfoMarkUp(ResString.searchKey);
|
Logger.InfoMarkUp(ResString.searchKey);
|
||||||
using var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
using var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
using var reader = new StreamReader(stream);
|
using var reader = new StreamReader(stream);
|
||||||
var line = "";
|
while (await reader.ReadLineAsync() is { } line)
|
||||||
while ((line = await reader.ReadLineAsync()) != null)
|
|
||||||
{
|
{
|
||||||
if (line.Trim().StartsWith(kid))
|
if (line.Trim().StartsWith(kid))
|
||||||
{
|
{
|
||||||
|
@ -152,17 +151,15 @@ internal static class MP4DecryptUtil
|
||||||
|
|
||||||
public static ParsedMP4Info GetMP4Info(string output)
|
public static ParsedMP4Info GetMP4Info(string output)
|
||||||
{
|
{
|
||||||
using (var fs = File.OpenRead(output))
|
using var fs = File.OpenRead(output);
|
||||||
{
|
var header = new byte[1 * 1024 * 1024]; // 1MB
|
||||||
var header = new byte[1 * 1024 * 1024]; //1MB
|
|
||||||
fs.Read(header);
|
fs.Read(header);
|
||||||
return GetMP4Info(header);
|
return GetMP4Info(header);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static string? ReadInitShaka(string output, string bin)
|
public static string? ReadInitShaka(string output, string bin)
|
||||||
{
|
{
|
||||||
Regex ShakaKeyIDRegex = new Regex("Key for key_id=([0-9a-f]+) was not found");
|
Regex shakaKeyIdRegex = new("Key for key_id=([0-9a-f]+) was not found");
|
||||||
|
|
||||||
// TODO: handle the case that shaka packager actually decrypted (key ID == ZeroKid)
|
// TODO: handle the case that shaka packager actually decrypted (key ID == ZeroKid)
|
||||||
// - stop process
|
// - stop process
|
||||||
|
@ -182,6 +179,6 @@ internal static class MP4DecryptUtil
|
||||||
p.Start();
|
p.Start();
|
||||||
var errorOutput = p.StandardError.ReadToEnd();
|
var errorOutput = p.StandardError.ReadToEnd();
|
||||||
p.WaitForExit();
|
p.WaitForExit();
|
||||||
return ShakaKeyIDRegex.Match(errorOutput).Groups[1].Value;
|
return shakaKeyIdRegex.Match(errorOutput).Groups[1].Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,6 @@
|
||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util;
|
namespace N_m3u8DL_RE.Util;
|
||||||
|
|
||||||
|
@ -48,7 +42,7 @@ internal static partial class MediainfoUtil
|
||||||
RedirectStandardError = true,
|
RedirectStandardError = true,
|
||||||
UseShellExecute = false
|
UseShellExecute = false
|
||||||
})!;
|
})!;
|
||||||
var output = p.StandardError.ReadToEnd();
|
var output = await p.StandardError.ReadToEndAsync();
|
||||||
await p.WaitForExitAsync();
|
await p.WaitForExitAsync();
|
||||||
|
|
||||||
foreach (Match stream in TextRegex().Matches(output))
|
foreach (Match stream in TextRegex().Matches(output))
|
||||||
|
@ -87,7 +81,7 @@ internal static partial class MediainfoUtil
|
||||||
|
|
||||||
if (result.Count == 0)
|
if (result.Count == 0)
|
||||||
{
|
{
|
||||||
result.Add(new Mediainfo()
|
result.Add(new Mediainfo
|
||||||
{
|
{
|
||||||
Type = "Unknown"
|
Type = "Unknown"
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,20 +27,16 @@ internal static class MergeUtil
|
||||||
if (!Directory.Exists(Path.GetDirectoryName(outputFilePath)))
|
if (!Directory.Exists(Path.GetDirectoryName(outputFilePath)))
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outputFilePath)!);
|
Directory.CreateDirectory(Path.GetDirectoryName(outputFilePath)!);
|
||||||
|
|
||||||
string[] inputFilePaths = files;
|
var inputFilePaths = files;
|
||||||
using (var outputStream = File.Create(outputFilePath))
|
using var outputStream = File.Create(outputFilePath);
|
||||||
{
|
|
||||||
foreach (var inputFilePath in inputFilePaths)
|
foreach (var inputFilePath in inputFilePaths)
|
||||||
{
|
{
|
||||||
if (inputFilePath == "")
|
if (inputFilePath == "")
|
||||||
continue;
|
continue;
|
||||||
using (var inputStream = File.OpenRead(inputFilePath))
|
using var inputStream = File.OpenRead(inputFilePath);
|
||||||
{
|
|
||||||
inputStream.CopyTo(outputStream);
|
inputStream.CopyTo(outputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int InvokeFFmpeg(string binary, string command, string workingDirectory)
|
private static int InvokeFFmpeg(string binary, string command, string workingDirectory)
|
||||||
{
|
{
|
||||||
|
@ -79,18 +75,18 @@ internal static class MergeUtil
|
||||||
div = 200;
|
div = 200;
|
||||||
|
|
||||||
string outputName = Path.Combine(Path.GetDirectoryName(files[0])!, "T");
|
string outputName = Path.Combine(Path.GetDirectoryName(files[0])!, "T");
|
||||||
int index = 0; //序号
|
int index = 0; // 序号
|
||||||
|
|
||||||
//按照div的容量分割为小数组
|
// 按照div的容量分割为小数组
|
||||||
string[][] li = Enumerable.Range(0, files.Count() / div + 1).Select(x => files.Skip(x * div).Take(div).ToArray()).ToArray();
|
string[][] li = Enumerable.Range(0, files.Count() / div + 1).Select(x => files.Skip(x * div).Take(div).ToArray()).ToArray();
|
||||||
foreach (var items in li)
|
foreach (var items in li)
|
||||||
{
|
{
|
||||||
if (items.Count() == 0)
|
if (!items.Any())
|
||||||
continue;
|
continue;
|
||||||
var output = outputName + index.ToString("0000") + ".ts";
|
var output = outputName + index.ToString("0000") + ".ts";
|
||||||
CombineMultipleFilesIntoSingleFile(items, output);
|
CombineMultipleFilesIntoSingleFile(items, output);
|
||||||
newFiles.Add(output);
|
newFiles.Add(output);
|
||||||
//合并后删除这些文件
|
// 合并后删除这些文件
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
File.Delete(item);
|
File.Delete(item);
|
||||||
|
@ -106,7 +102,7 @@ internal static class MergeUtil
|
||||||
bool writeDate = true, bool useConcatDemuxer = false, string poster = "", string audioName = "", string title = "",
|
bool writeDate = true, bool useConcatDemuxer = false, string poster = "", string audioName = "", string title = "",
|
||||||
string copyright = "", string comment = "", string encodingTool = "", string recTime = "")
|
string copyright = "", string comment = "", string encodingTool = "", string recTime = "")
|
||||||
{
|
{
|
||||||
//改为绝对路径
|
// 改为绝对路径
|
||||||
outputPath = Path.GetFullPath(outputPath);
|
outputPath = Path.GetFullPath(outputPath);
|
||||||
|
|
||||||
string dateString = string.IsNullOrEmpty(recTime) ? DateTime.Now.ToString("o") : recTime;
|
string dateString = string.IsNullOrEmpty(recTime) ? DateTime.Now.ToString("o") : recTime;
|
||||||
|
@ -185,13 +181,13 @@ internal static class MergeUtil
|
||||||
string dateString = DateTime.Now.ToString("o");
|
string dateString = DateTime.Now.ToString("o");
|
||||||
StringBuilder command = new StringBuilder("-loglevel warning -nostdin -y -dn ");
|
StringBuilder command = new StringBuilder("-loglevel warning -nostdin -y -dn ");
|
||||||
|
|
||||||
//INPUT
|
// INPUT
|
||||||
foreach (var item in files)
|
foreach (var item in files)
|
||||||
{
|
{
|
||||||
command.Append($" -i \"{item.FilePath}\" ");
|
command.Append($" -i \"{item.FilePath}\" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
//MAP
|
// MAP
|
||||||
for (int i = 0; i < files.Length; i++)
|
for (int i = 0; i < files.Length; i++)
|
||||||
{
|
{
|
||||||
command.Append($" -map {i} ");
|
command.Append($" -map {i} ");
|
||||||
|
@ -200,21 +196,21 @@ internal static class MergeUtil
|
||||||
var srt = files.Any(x => x.FilePath.EndsWith(".srt"));
|
var srt = files.Any(x => x.FilePath.EndsWith(".srt"));
|
||||||
|
|
||||||
if (muxFormat == MuxFormat.MP4)
|
if (muxFormat == MuxFormat.MP4)
|
||||||
command.Append($" -strict unofficial -c:a copy -c:v copy -c:s mov_text "); //mp4不支持vtt/srt字幕,必须转换格式
|
command.Append($" -strict unofficial -c:a copy -c:v copy -c:s mov_text "); // mp4不支持vtt/srt字幕,必须转换格式
|
||||||
else if (muxFormat == MuxFormat.TS)
|
else if (muxFormat == MuxFormat.TS)
|
||||||
command.Append($" -strict unofficial -c:a copy -c:v copy ");
|
command.Append($" -strict unofficial -c:a copy -c:v copy ");
|
||||||
else if (muxFormat == MuxFormat.MKV)
|
else if (muxFormat == MuxFormat.MKV)
|
||||||
command.Append($" -strict unofficial -c:a copy -c:v copy -c:s {(srt ? "srt" : "webvtt")} ");
|
command.Append($" -strict unofficial -c:a copy -c:v copy -c:s {(srt ? "srt" : "webvtt")} ");
|
||||||
else throw new ArgumentException($"unknown format: {muxFormat}");
|
else throw new ArgumentException($"unknown format: {muxFormat}");
|
||||||
|
|
||||||
//CLEAN
|
// CLEAN
|
||||||
command.Append(" -map_metadata -1 ");
|
command.Append(" -map_metadata -1 ");
|
||||||
|
|
||||||
//LANG and NAME
|
// LANG and NAME
|
||||||
var streamIndex = 0;
|
var streamIndex = 0;
|
||||||
for (int i = 0; i < files.Length; i++)
|
for (int i = 0; i < files.Length; i++)
|
||||||
{
|
{
|
||||||
//转换语言代码
|
// 转换语言代码
|
||||||
LanguageCodeUtil.ConvertLangCodeAndDisplayName(files[i]);
|
LanguageCodeUtil.ConvertLangCodeAndDisplayName(files[i]);
|
||||||
command.Append($" -metadata:s:{streamIndex} language=\"{files[i].LangCode ?? "und"}\" ");
|
command.Append($" -metadata:s:{streamIndex} language=\"{files[i].LangCode ?? "und"}\" ");
|
||||||
if (!string.IsNullOrEmpty(files[i].Description))
|
if (!string.IsNullOrEmpty(files[i].Description))
|
||||||
|
@ -236,11 +232,11 @@ internal static class MergeUtil
|
||||||
var audioTracks = files.Where(x => x.MediaType == Common.Enum.MediaType.AUDIO);
|
var audioTracks = files.Where(x => x.MediaType == Common.Enum.MediaType.AUDIO);
|
||||||
var subTracks = files.Where(x => x.MediaType == Common.Enum.MediaType.AUDIO);
|
var subTracks = files.Where(x => x.MediaType == Common.Enum.MediaType.AUDIO);
|
||||||
if (videoTracks.Any()) command.Append(" -disposition:v:0 default ");
|
if (videoTracks.Any()) command.Append(" -disposition:v:0 default ");
|
||||||
//字幕都不设置默认
|
// 字幕都不设置默认
|
||||||
if (subTracks.Any()) command.Append(" -disposition:s 0 ");
|
if (subTracks.Any()) command.Append(" -disposition:s 0 ");
|
||||||
if (audioTracks.Any())
|
if (audioTracks.Any())
|
||||||
{
|
{
|
||||||
//音频除了第一个音轨 都不设置默认
|
// 音频除了第一个音轨 都不设置默认
|
||||||
command.Append(" -disposition:a:0 default ");
|
command.Append(" -disposition:a:0 default ");
|
||||||
for (int i = 1; i < audioTracks.Count(); i++)
|
for (int i = 1; i < audioTracks.Count(); i++)
|
||||||
{
|
{
|
||||||
|
@ -265,16 +261,16 @@ internal static class MergeUtil
|
||||||
|
|
||||||
var dFlag = false;
|
var dFlag = false;
|
||||||
|
|
||||||
//LANG and NAME
|
// LANG and NAME
|
||||||
for (int i = 0; i < files.Length; i++)
|
for (int i = 0; i < files.Length; i++)
|
||||||
{
|
{
|
||||||
//转换语言代码
|
// 转换语言代码
|
||||||
LanguageCodeUtil.ConvertLangCodeAndDisplayName(files[i]);
|
LanguageCodeUtil.ConvertLangCodeAndDisplayName(files[i]);
|
||||||
command.Append($" --language 0:\"{files[i].LangCode ?? "und"}\" ");
|
command.Append($" --language 0:\"{files[i].LangCode ?? "und"}\" ");
|
||||||
//字幕都不设置默认
|
// 字幕都不设置默认
|
||||||
if (files[i].MediaType == Common.Enum.MediaType.SUBTITLES)
|
if (files[i].MediaType == Common.Enum.MediaType.SUBTITLES)
|
||||||
command.Append($" --default-track 0:no ");
|
command.Append($" --default-track 0:no ");
|
||||||
//音频除了第一个音轨 都不设置默认
|
// 音频除了第一个音轨 都不设置默认
|
||||||
if (files[i].MediaType == Common.Enum.MediaType.AUDIO)
|
if (files[i].MediaType == Common.Enum.MediaType.AUDIO)
|
||||||
{
|
{
|
||||||
if (dFlag)
|
if (dFlag)
|
||||||
|
|
|
@ -1,21 +1,16 @@
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Enum;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
|
||||||
using N_m3u8DL_RE.Enum;
|
|
||||||
using System.CommandLine;
|
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util;
|
namespace N_m3u8DL_RE.Util;
|
||||||
|
|
||||||
internal class OtherUtil
|
internal static class OtherUtil
|
||||||
{
|
{
|
||||||
public static Dictionary<string, string> SplitHeaderArrayToDic(string[]? headers)
|
public static Dictionary<string, string> SplitHeaderArrayToDic(string[]? headers)
|
||||||
{
|
{
|
||||||
Dictionary<string, string> dic = new();
|
Dictionary<string, string> dic = new();
|
||||||
|
if (headers == null) return dic;
|
||||||
|
|
||||||
if (headers != null)
|
|
||||||
{
|
|
||||||
foreach (string header in headers)
|
foreach (string header in headers)
|
||||||
{
|
{
|
||||||
var index = header.IndexOf(':');
|
var index = header.IndexOf(':');
|
||||||
|
@ -24,12 +19,11 @@ internal class OtherUtil
|
||||||
dic[header[..index].Trim().ToLower()] = header[(index + 1)..].Trim();
|
dic[header[..index].Trim().ToLower()] = header[(index + 1)..].Trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return dic;
|
return dic;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static char[] InvalidChars = "34,60,62,124,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,58,42,63,92,47"
|
private static readonly char[] InvalidChars = "34,60,62,124,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,58,42,63,92,47"
|
||||||
.Split(',').Select(s => (char)int.Parse(s)).ToArray();
|
.Split(',').Select(s => (char)int.Parse(s)).ToArray();
|
||||||
public static string GetValidFileName(string input, string re = ".", bool filterSlash = false)
|
public static string GetValidFileName(string input, string re = ".", bool filterSlash = false)
|
||||||
{
|
{
|
||||||
|
@ -50,6 +44,7 @@ internal class OtherUtil
|
||||||
/// 从输入自动获取文件名
|
/// 从输入自动获取文件名
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input"></param>
|
/// <param name="input"></param>
|
||||||
|
/// <param name="addSuffix"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string GetFileNameFromInput(string input, bool addSuffix = true)
|
public static string GetFileNameFromInput(string input, bool addSuffix = true)
|
||||||
{
|
{
|
||||||
|
@ -119,7 +114,7 @@ internal class OtherUtil
|
||||||
return hours * 3600 + minutes * 60 + seconds;
|
return hours * 3600 + minutes * 60 + seconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
//若该文件夹为空,删除,同时判断其父文件夹,直到遇到根目录或不为空的目录
|
// 若该文件夹为空,删除,同时判断其父文件夹,直到遇到根目录或不为空的目录
|
||||||
public static void SafeDeleteDir(string dirPath)
|
public static void SafeDeleteDir(string dirPath)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(dirPath) || !Directory.Exists(dirPath))
|
if (string.IsNullOrEmpty(dirPath) || !Directory.Exists(dirPath))
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.CommandLine;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util;
|
namespace N_m3u8DL_RE.Util;
|
||||||
|
|
||||||
|
@ -67,7 +61,7 @@ internal static class PipeUtil
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
command.Append($" -i \"\\\\.\\pipe\\{item}\" ");
|
command.Append($" -i \"\\\\.\\pipe\\{item}\" ");
|
||||||
else
|
else
|
||||||
//command.Append($" -i \"unix://{Path.Combine(Path.GetTempPath(), $"CoreFxPipe_{item}")}\" ");
|
// command.Append($" -i \"unix://{Path.Combine(Path.GetTempPath(), $"CoreFxPipe_{item}")}\" ");
|
||||||
command.Append($" -i \"{Path.Combine(pipeDir, item)}\" ");
|
command.Append($" -i \"{Path.Combine(pipeDir, item)}\" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +97,7 @@ internal static class PipeUtil
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
UseShellExecute = false
|
UseShellExecute = false
|
||||||
};
|
};
|
||||||
//p.StartInfo.Environment.Add("FFREPORT", "file=ffreport.log:level=42");
|
// p.StartInfo.Environment.Add("FFREPORT", "file=ffreport.log:level=42");
|
||||||
p.Start();
|
p.Start();
|
||||||
p.WaitForExit();
|
p.WaitForExit();
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util;
|
namespace N_m3u8DL_RE.Util;
|
||||||
|
|
||||||
|
@ -17,23 +12,21 @@ internal static class SubtitleUtil
|
||||||
/// <param name="finalVtt"></param>
|
/// <param name="finalVtt"></param>
|
||||||
/// <param name="tmpDir">临时目录</param>
|
/// <param name="tmpDir">临时目录</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static async Task<bool> TryWriteImagePngsAsync(WebVttSub? finalVtt, string tmpDir)
|
public static async Task TryWriteImagePngsAsync(WebVttSub? finalVtt, string tmpDir)
|
||||||
{
|
{
|
||||||
if (finalVtt != null && finalVtt.Cues.Any(v => v.Payload.StartsWith("Base64::")))
|
if (finalVtt != null && finalVtt.Cues.Any(v => v.Payload.StartsWith("Base64::")))
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp(ResString.processImageSub);
|
Logger.WarnMarkUp(ResString.processImageSub);
|
||||||
var _i = 0;
|
var i = 0;
|
||||||
foreach (var img in finalVtt.Cues.Where(v => v.Payload.StartsWith("Base64::")))
|
foreach (var img in finalVtt.Cues.Where(v => v.Payload.StartsWith("Base64::")))
|
||||||
{
|
{
|
||||||
var name = $"{_i++}.png";
|
var name = $"{i++}.png";
|
||||||
var dest = "";
|
var dest = "";
|
||||||
for (; File.Exists(dest = Path.Combine(tmpDir, name)); name = $"{_i++}.png") ;
|
for (; File.Exists(dest = Path.Combine(tmpDir, name)); name = $"{i++}.png") ;
|
||||||
var base64 = img.Payload[8..];
|
var base64 = img.Payload[8..];
|
||||||
await File.WriteAllBytesAsync(dest, Convert.FromBase64String(base64));
|
await File.WriteAllBytesAsync(dest, Convert.FromBase64String(base64));
|
||||||
img.Payload = name;
|
img.Payload = name;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue