支持手动设置直播刷新间隔
This commit is contained in:
parent
c0c821da63
commit
21e70674f5
|
@ -11,6 +11,7 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
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 long? StartRange { get; set; }
|
public long? StartRange { get; set; }
|
||||||
public long? StopRange { get => (StartRange != null && ExpectLength != null) ? StartRange + ExpectLength - 1 : null; }
|
public long? StopRange { get => (StartRange != null && ExpectLength != null) ? StartRange + ExpectLength - 1 : null; }
|
||||||
|
|
|
@ -64,6 +64,7 @@ namespace N_m3u8DL_RE.Common.Resource
|
||||||
public static string cmd_customProxy { get => GetText("cmd_customProxy"); }
|
public static string cmd_customProxy { get => GetText("cmd_customProxy"); }
|
||||||
public static string cmd_liveKeepSegments { get => GetText("cmd_liveKeepSegments"); }
|
public static string cmd_liveKeepSegments { get => GetText("cmd_liveKeepSegments"); }
|
||||||
public static string cmd_liveRecordLimit { get => GetText("cmd_liveRecordLimit"); }
|
public static string cmd_liveRecordLimit { get => GetText("cmd_liveRecordLimit"); }
|
||||||
|
public static string cmd_liveWaitTime { get => GetText("cmd_liveWaitTime"); }
|
||||||
public static string cmd_liveRealTimeMerge { get => GetText("cmd_liveRealTimeMerge"); }
|
public static string cmd_liveRealTimeMerge { get => GetText("cmd_liveRealTimeMerge"); }
|
||||||
public static string cmd_livePerformAsVod { get => GetText("cmd_livePerformAsVod"); }
|
public static string cmd_livePerformAsVod { get => GetText("cmd_livePerformAsVod"); }
|
||||||
public static string cmd_muxAfterDone { get => GetText("cmd_muxAfterDone"); }
|
public static string cmd_muxAfterDone { get => GetText("cmd_muxAfterDone"); }
|
||||||
|
|
|
@ -262,6 +262,12 @@ namespace N_m3u8DL_RE.Common.Resource
|
||||||
zhTW: "以點播方式下載直播流",
|
zhTW: "以點播方式下載直播流",
|
||||||
enUS: "Download live streams as vod"
|
enUS: "Download live streams as vod"
|
||||||
),
|
),
|
||||||
|
["cmd_liveWaitTime"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "手动设置直播列表刷新间隔",
|
||||||
|
zhTW: "手動設置直播列表刷新間隔",
|
||||||
|
enUS: "Manually set the live playlist refresh interval"
|
||||||
|
),
|
||||||
["cmd_customHLSMethod"] = new TextContainer
|
["cmd_customHLSMethod"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "指定HLS加密方式 (AES_128|AES_128_ECB|CENC|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)",
|
zhCN: "指定HLS加密方式 (AES_128|AES_128_ECB|CENC|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)",
|
||||||
|
|
|
@ -295,7 +295,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
//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));
|
||||||
}
|
}
|
||||||
//解析不连续标记,需要单独合并(timestamp不同)
|
//解析不连续标记,需要单独合并(timestamp不同)
|
||||||
else if (line.StartsWith(HLSTags.ext_x_discontinuity))
|
else if (line.StartsWith(HLSTags.ext_x_discontinuity))
|
||||||
|
|
|
@ -70,6 +70,7 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
private readonly static Option<bool> LiveRealTimeMerge = new(new string[] { "--live-real-time-merge" }, description: ResString.cmd_liveRealTimeMerge, getDefaultValue: () => false);
|
private readonly static Option<bool> LiveRealTimeMerge = new(new string[] { "--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 readonly static Option<bool> LiveKeepSegments = new(new string[] { "--live-keep-segments" }, description: ResString.cmd_liveKeepSegments, getDefaultValue: () => true);
|
||||||
private readonly static Option<TimeSpan?> LiveRecordLimit = new(new string[] { "--live-record-limit" }, description: ResString.cmd_liveRecordLimit, parseArgument: ParseLiveLimit) { ArgumentHelpName = "HH:mm:ss" };
|
private readonly static Option<TimeSpan?> LiveRecordLimit = new(new string[] { "--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_liveRecordLimit) { ArgumentHelpName = "SEC" };
|
||||||
|
|
||||||
|
|
||||||
//复杂命令行如下
|
//复杂命令行如下
|
||||||
|
@ -374,6 +375,7 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
LivePerformAsVod = bindingContext.ParseResult.GetValueForOption(LivePerformAsVod),
|
LivePerformAsVod = bindingContext.ParseResult.GetValueForOption(LivePerformAsVod),
|
||||||
UseSystemProxy = bindingContext.ParseResult.GetValueForOption(UseSystemProxy),
|
UseSystemProxy = bindingContext.ParseResult.GetValueForOption(UseSystemProxy),
|
||||||
CustomProxy = bindingContext.ParseResult.GetValueForOption(CustomProxy),
|
CustomProxy = bindingContext.ParseResult.GetValueForOption(CustomProxy),
|
||||||
|
LiveWaitTime = bindingContext.ParseResult.GetValueForOption(LiveWaitTime),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (bindingContext.ParseResult.HasOption(CustomHLSMethod)) option.CustomHLSMethod = bindingContext.ParseResult.GetValueForOption(CustomHLSMethod);
|
if (bindingContext.ParseResult.HasOption(CustomHLSMethod)) option.CustomHLSMethod = bindingContext.ParseResult.GetValueForOption(CustomHLSMethod);
|
||||||
|
@ -439,7 +441,7 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
|
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
|
||||||
MuxAfterDone,
|
MuxAfterDone,
|
||||||
CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy,
|
CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy,
|
||||||
LivePerformAsVod, LiveRealTimeMerge, LiveKeepSegments, LiveRecordLimit,
|
LivePerformAsVod, LiveRealTimeMerge, LiveKeepSegments, LiveRecordLimit, LiveWaitTime,
|
||||||
MuxImports, VideoFilter, AudioFilter, SubtitleFilter, MoreHelp
|
MuxImports, VideoFilter, AudioFilter, SubtitleFilter, MoreHelp
|
||||||
};
|
};
|
||||||
rootCommand.TreatUnmatchedTokensAsErrors = true;
|
rootCommand.TreatUnmatchedTokensAsErrors = true;
|
||||||
|
|
|
@ -196,6 +196,10 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
/// See: <see cref="CommandInvoker.CustomProxy"/>.
|
/// See: <see cref="CommandInvoker.CustomProxy"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public WebProxy? CustomProxy { get; set; }
|
public WebProxy? CustomProxy { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// See: <see cref="CommandInvoker.LiveWaitTime"/>.
|
||||||
|
/// </summary>
|
||||||
|
public int? LiveWaitTime { get; set; }
|
||||||
public bool MuxKeepFiles { get; set; }
|
public bool MuxKeepFiles { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -40,6 +40,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
int WAIT_SEC = 0; //刷新间隔
|
int WAIT_SEC = 0; //刷新间隔
|
||||||
ConcurrentDictionary<int, int> RecordingDurDic = new(); //已录制时长
|
ConcurrentDictionary<int, int> RecordingDurDic = new(); //已录制时长
|
||||||
ConcurrentDictionary<string, string> LastUrlDic = new(); //上次下载的url
|
ConcurrentDictionary<string, string> LastUrlDic = new(); //上次下载的url
|
||||||
|
ConcurrentDictionary<string, long> DateTimeDic = new(); //上次下载的dateTime
|
||||||
CancellationTokenSource CancellationTokenSource = new(); //取消Wait
|
CancellationTokenSource CancellationTokenSource = new(); //取消Wait
|
||||||
|
|
||||||
public SimpleLiveRecordManager2(DownloaderConfig downloaderConfig, List<StreamSpec> selectedSteams, StreamExtractor streamExtractor)
|
public SimpleLiveRecordManager2(DownloaderConfig downloaderConfig, List<StreamSpec> selectedSteams, StreamExtractor streamExtractor)
|
||||||
|
@ -84,6 +85,39 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取时间戳
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dateTime"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private long GetUnixTimestamp(DateTime dateTime)
|
||||||
|
{
|
||||||
|
return new DateTimeOffset(dateTime.ToUniversalTime()).ToUnixTimeSeconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取分段文件夹
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="segment"></param>
|
||||||
|
/// <param name="allHasDatetime"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private string GetSegmentName(MediaSegment segment, bool allHasDatetime)
|
||||||
|
{
|
||||||
|
bool hls = StreamExtractor.ExtractorType == ExtractorType.HLS;
|
||||||
|
|
||||||
|
string name = OtherUtil.GetFileNameFromInput(segment.Url, false);
|
||||||
|
if (hls && allHasDatetime)
|
||||||
|
{
|
||||||
|
name = GetUnixTimestamp(segment.DateTime!.Value).ToString();
|
||||||
|
}
|
||||||
|
else if (hls && segment.Index > 10)
|
||||||
|
{
|
||||||
|
name = segment.Index.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
private void ChangeSpecInfo(StreamSpec streamSpec, List<Mediainfo> mediainfos, ref bool useAACFilter)
|
private void ChangeSpecInfo(StreamSpec streamSpec, List<Mediainfo> mediainfos, ref bool useAACFilter)
|
||||||
{
|
{
|
||||||
if (!DownloaderConfig.MyOptions.BinaryMerge && mediainfos.Any(m => m.DolbyVison == true))
|
if (!DownloaderConfig.MyOptions.BinaryMerge && mediainfos.Any(m => m.DolbyVison == true))
|
||||||
|
@ -125,7 +159,6 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var readInfo = false; //是否读取过
|
var readInfo = false; //是否读取过
|
||||||
bool useAACFilter = false; //ffmpeg合并flag
|
bool useAACFilter = false; //ffmpeg合并flag
|
||||||
bool initDownloaded = false; //是否下载过init文件
|
bool initDownloaded = false; //是否下载过init文件
|
||||||
bool hls = StreamExtractor.ExtractorType == ExtractorType.HLS;
|
|
||||||
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
|
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
|
||||||
List<Mediainfo> mediaInfos = new();
|
List<Mediainfo> mediaInfos = new();
|
||||||
FileStream? fileOutputStream = null;
|
FileStream? fileOutputStream = null;
|
||||||
|
@ -205,8 +238,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//计算填零个数
|
var allHasDatetime = segments.All(s => s.DateTime != null);
|
||||||
var pad = "0".PadLeft(segments.Count().ToString().Length, '0');
|
|
||||||
|
|
||||||
//下载第一个分片
|
//下载第一个分片
|
||||||
if (!readInfo)
|
if (!readInfo)
|
||||||
|
@ -214,7 +246,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
var seg = segments.First();
|
var seg = segments.First();
|
||||||
segments = segments.Skip(1);
|
segments = segments.Skip(1);
|
||||||
//获取文件名
|
//获取文件名
|
||||||
var filename = hls && seg.Index > 10 ? seg.Index.ToString(pad) : OtherUtil.GetFileNameFromInput(seg.Url, false);
|
var filename = GetSegmentName(seg, allHasDatetime);
|
||||||
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");
|
||||||
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
|
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
|
||||||
|
@ -262,7 +294,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
await Parallel.ForEachAsync(segments, options, async (seg, _) =>
|
await Parallel.ForEachAsync(segments, options, async (seg, _) =>
|
||||||
{
|
{
|
||||||
//获取文件名
|
//获取文件名
|
||||||
var filename = hls && seg.Index > 10 ? seg.Index.ToString(pad) : OtherUtil.GetFileNameFromInput(seg.Url, false);
|
var filename = GetSegmentName(seg, allHasDatetime);
|
||||||
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");
|
||||||
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
|
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
|
||||||
|
@ -505,7 +537,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
if (WAIT_SEC != 0)
|
if (WAIT_SEC != 0)
|
||||||
{
|
{
|
||||||
//过滤不需要下载的片段
|
//过滤不需要下载的片段
|
||||||
FilterMediaSegments(streamSpec, LastUrlDic[streamSpec.ToShortString()]);
|
FilterMediaSegments(streamSpec, LastUrlDic[streamSpec.ToShortString()], DateTimeDic[streamSpec.ToShortString()]);
|
||||||
var newList = streamSpec.Playlist!.MediaParts[0].MediaSegments;
|
var newList = streamSpec.Playlist!.MediaParts[0].MediaSegments;
|
||||||
if (newList.Count > 0)
|
if (newList.Count > 0)
|
||||||
{
|
{
|
||||||
|
@ -513,6 +545,9 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
await target.SendAsync(newList);
|
await target.SendAsync(newList);
|
||||||
//更新最新链接
|
//更新最新链接
|
||||||
LastUrlDic[streamSpec.ToShortString()] = GetPath(newList.Last().Url);
|
LastUrlDic[streamSpec.ToShortString()] = GetPath(newList.Last().Url);
|
||||||
|
//尝试更新时间戳
|
||||||
|
var dt = newList.Last().DateTime;
|
||||||
|
DateTimeDic[streamSpec.ToShortString()] = dt != null ? GetUnixTimestamp(dt.Value) : 0L;
|
||||||
task.MaxValue += newList.Count;
|
task.MaxValue += newList.Count;
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
|
@ -532,11 +567,22 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
target.Complete();
|
target.Complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FilterMediaSegments(StreamSpec streamSpec, string lastUrl)
|
private void FilterMediaSegments(StreamSpec streamSpec, string lastUrl, long dateTime)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(lastUrl)) return;
|
if (string.IsNullOrEmpty(lastUrl) && dateTime == 0) return;
|
||||||
|
|
||||||
|
var index = -1;
|
||||||
|
|
||||||
|
//优先使用dateTime判断
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
index = streamSpec.Playlist!.MediaParts[0].MediaSegments.FindIndex(s => GetPath(s.Url) == lastUrl);
|
||||||
|
}
|
||||||
|
|
||||||
var index = streamSpec.Playlist!.MediaParts[0].MediaSegments.FindIndex(s => GetPath(s.Url) == lastUrl);
|
|
||||||
if (index > -1)
|
if (index > -1)
|
||||||
{
|
{
|
||||||
streamSpec.Playlist!.MediaParts[0].MediaSegments = streamSpec.Playlist!.MediaParts[0].MediaSegments.Skip(index + 1).ToList();
|
streamSpec.Playlist!.MediaParts[0].MediaSegments = streamSpec.Playlist!.MediaParts[0].MediaSegments.Skip(index + 1).ToList();
|
||||||
|
@ -566,11 +612,16 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
foreach (var item in SelectedSteams)
|
foreach (var item in SelectedSteams)
|
||||||
{
|
{
|
||||||
LastUrlDic[item.ToShortString()] = "";
|
LastUrlDic[item.ToShortString()] = "";
|
||||||
|
DateTimeDic[item.ToShortString()] = 0L;
|
||||||
}
|
}
|
||||||
//设置等待时间
|
//设置等待时间
|
||||||
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; //再提前两秒吧 留出冗余
|
||||||
|
if (DownloaderConfig.MyOptions.LiveWaitTime != null)
|
||||||
|
WAIT_SEC = DownloaderConfig.MyOptions.LiveWaitTime.Value;
|
||||||
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue