支持手动设置直播刷新间隔

This commit is contained in:
nilaoda 2022-09-27 22:57:56 +08:00
parent c0c821da63
commit 21e70674f5
7 changed files with 76 additions and 11 deletions

View File

@ -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; }

View File

@ -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"); }

View File

@ -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)",

View File

@ -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))

View File

@ -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;

View File

@ -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; }
} }
} }

View File

@ -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");
} }