diff --git a/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs b/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs
index 932d2c5..d9db4f1 100644
--- a/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs
+++ b/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs
@@ -29,6 +29,8 @@ namespace N_m3u8DL_RE.Common.Entity
public string? VideoRange { get; set; }
//补充信息-特征
public string? Characteristics { get; set; }
+ //发布时间(仅MPD需要)
+ public DateTime? PublishTime { get; set; }
//外部轨道GroupId (后续寻找对应轨道信息)
public string? AudioId { get; set; }
diff --git a/src/N_m3u8DL-RE.Common/Entity/WebVttSub.cs b/src/N_m3u8DL-RE.Common/Entity/WebVttSub.cs
index b23a88f..51db7ad 100644
--- a/src/N_m3u8DL-RE.Common/Entity/WebVttSub.cs
+++ b/src/N_m3u8DL-RE.Common/Entity/WebVttSub.cs
@@ -24,9 +24,9 @@ namespace N_m3u8DL_RE.Common.Entity
///
///
///
- public static WebVttSub Parse(byte[] textBytes)
+ public static WebVttSub Parse(byte[] textBytes, long BaseTimestamp = 0L)
{
- return Parse(Encoding.UTF8.GetString(textBytes));
+ return Parse(Encoding.UTF8.GetString(textBytes), BaseTimestamp);
}
///
@@ -35,9 +35,9 @@ namespace N_m3u8DL_RE.Common.Entity
///
///
///
- public static WebVttSub Parse(byte[] textBytes, Encoding encoding)
+ public static WebVttSub Parse(byte[] textBytes, Encoding encoding, long BaseTimestamp = 0L)
{
- return Parse(encoding.GetString(textBytes));
+ return Parse(encoding.GetString(textBytes), BaseTimestamp);
}
///
@@ -45,7 +45,7 @@ namespace N_m3u8DL_RE.Common.Entity
///
///
///
- public static WebVttSub Parse(string text)
+ public static WebVttSub Parse(string text, long BaseTimestamp = 0L)
{
if (!text.Trim().StartsWith("WEBVTT"))
throw new Exception("Bad vtt!");
@@ -98,6 +98,22 @@ namespace N_m3u8DL_RE.Common.Entity
}
}
+ if (BaseTimestamp != 0)
+ {
+ foreach (var item in webSub.Cues)
+ {
+ if (item.StartTime.TotalMilliseconds - BaseTimestamp >= 0)
+ {
+ item.StartTime = TimeSpan.FromMilliseconds(item.StartTime.TotalMilliseconds - BaseTimestamp);
+ item.EndTime = TimeSpan.FromMilliseconds(item.EndTime.TotalMilliseconds - BaseTimestamp);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
return webSub;
}
diff --git a/src/N_m3u8DL-RE.Parser/Extractor/DASHExtractor2.cs b/src/N_m3u8DL-RE.Parser/Extractor/DASHExtractor2.cs
index d7fb5cd..3a4bb9a 100644
--- a/src/N_m3u8DL-RE.Parser/Extractor/DASHExtractor2.cs
+++ b/src/N_m3u8DL-RE.Parser/Extractor/DASHExtractor2.cs
@@ -189,6 +189,12 @@ namespace N_m3u8DL_RE.Parser.Extractor
{
streamSpec.Channels = audioChannelConfiguration.Attribute("value")?.Value;
}
+
+ //发布时间
+ if (!string.IsNullOrEmpty(publishTime))
+ {
+ streamSpec.PublishTime = DateTime.Parse(publishTime);
+ }
//第一种形式 SegmentBase
diff --git a/src/N_m3u8DL-RE.Parser/Mp4/MP4TtmlUtil.cs b/src/N_m3u8DL-RE.Parser/Mp4/MP4TtmlUtil.cs
index e7c41b8..b433da3 100644
--- a/src/N_m3u8DL-RE.Parser/Mp4/MP4TtmlUtil.cs
+++ b/src/N_m3u8DL-RE.Parser/Mp4/MP4TtmlUtil.cs
@@ -118,7 +118,7 @@ namespace Mp4SubtitleParser
return MultiElementsFixRegex().Matches(xml).Select(m => m.Value).ToList();
}
- public static WebVttSub ExtractFromMp4s(IEnumerable items, long segTimeMs)
+ public static WebVttSub ExtractFromMp4s(IEnumerable items, long segTimeMs, long baseTimestamp = 0L)
{
//read ttmls
List xmls = new List();
@@ -156,10 +156,10 @@ namespace Mp4SubtitleParser
segIndex++;
}
- return ExtractSub(xmls);
+ return ExtractSub(xmls, baseTimestamp);
}
- public static WebVttSub ExtractFromTTMLs(IEnumerable items, long segTimeMs)
+ public static WebVttSub ExtractFromTTMLs(IEnumerable items, long segTimeMs, long baseTimestamp = 0L)
{
//read ttmls
List xmls = new List();
@@ -178,10 +178,10 @@ namespace Mp4SubtitleParser
segIndex++;
}
- return ExtractSub(xmls);
+ return ExtractSub(xmls, baseTimestamp);
}
- private static WebVttSub ExtractSub(List xmls)
+ private static WebVttSub ExtractSub(List xmls, long baseTimestamp)
{
//parsing
var xmlDoc = new XmlDocument();
@@ -306,7 +306,7 @@ namespace Mp4SubtitleParser
vtt.AppendLine();
}
- return WebVttSub.Parse(vtt.ToString());
+ return WebVttSub.Parse(vtt.ToString(), baseTimestamp);
}
}
}
diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs
index 7931029..894a0d3 100644
--- a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs
+++ b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs
@@ -35,6 +35,7 @@ namespace N_m3u8DL_RE.DownloadManager
StreamExtractor StreamExtractor;
List SelectedSteams;
DateTime NowDateTime;
+ DateTime? PublishDateTime;
bool STOP_FLAG = false;
int WAIT_SEC = 0; //刷新间隔
ConcurrentDictionary RecordingDurDic = new(); //已录制时长
@@ -46,6 +47,7 @@ namespace N_m3u8DL_RE.DownloadManager
this.DownloaderConfig = downloaderConfig;
Downloader = new SimpleDownloader(DownloaderConfig);
NowDateTime = DateTime.Now;
+ PublishDateTime = selectedSteams.FirstOrDefault()?.PublishTime;
StreamExtractor = streamExtractor;
SelectedSteams = selectedSteams;
}
@@ -115,6 +117,7 @@ namespace N_m3u8DL_RE.DownloadManager
private async Task RecordStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer, ISourceBlock> source)
{
+ var baseTimestamp = PublishDateTime == null ? 0L : (long)(PublishDateTime.Value.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
//mp4decrypt
var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
var mp4InitFile = "";
@@ -321,6 +324,7 @@ namespace N_m3u8DL_RE.DownloadManager
if (firstSub)
{
currentVtt = MP4VttUtil.ExtractSub(mp4s, timescale);
+ firstSub = false;
}
else
{
@@ -337,11 +341,17 @@ namespace N_m3u8DL_RE.DownloadManager
var mp4s = FileDic.Values.Select(v => v!.ActualFilePath).Where(p => p.EndsWith(".ttml")).OrderBy(s => s).ToArray();
if (firstSub)
{
- currentVtt = MP4TtmlUtil.ExtractFromTTMLs(mp4s, 0);
+ if (baseTimestamp != 0)
+ {
+ var total = segments.Sum(s => s.Duration);
+ baseTimestamp -= (long)TimeSpan.FromSeconds(total).TotalMilliseconds;
+ }
+ currentVtt = MP4TtmlUtil.ExtractFromTTMLs(mp4s, 0, baseTimestamp);
+ firstSub = false;
}
else
{
- var finalVtt = MP4TtmlUtil.ExtractFromTTMLs(mp4s, 0);
+ var finalVtt = MP4TtmlUtil.ExtractFromTTMLs(mp4s, 0, baseTimestamp);
currentVtt.AddCuesFromOne(finalVtt);
}
}
@@ -358,11 +368,17 @@ namespace N_m3u8DL_RE.DownloadManager
var mp4s = FileDic.Values.Select(v => v!.ActualFilePath).Where(p => p.EndsWith(".m4s")).OrderBy(s => s).ToArray();
if (firstSub)
{
- currentVtt = MP4TtmlUtil.ExtractFromMp4s(mp4s, 0);
+ if (baseTimestamp != 0)
+ {
+ var total = segments.Sum(s => s.Duration);
+ baseTimestamp -= (long)TimeSpan.FromSeconds(total).TotalMilliseconds;
+ }
+ currentVtt = MP4TtmlUtil.ExtractFromMp4s(mp4s, 0, baseTimestamp);
+ firstSub = false;
}
else
{
- var finalVtt = MP4TtmlUtil.ExtractFromMp4s(mp4s, 0);
+ var finalVtt = MP4TtmlUtil.ExtractFromMp4s(mp4s, 0, baseTimestamp);
currentVtt.AddCuesFromOne(finalVtt);
}
}