优化直播字幕时间轴处理
This commit is contained in:
parent
d96a45251b
commit
31482c8d34
|
@ -164,6 +164,11 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||
}
|
||||
}
|
||||
|
||||
private IEnumerable<SubCue> GetCues()
|
||||
{
|
||||
return this.Cues.Where(c => !string.IsNullOrEmpty(c.Payload));
|
||||
}
|
||||
|
||||
private static TimeSpan ConvertToTS(string str)
|
||||
{
|
||||
var ms = Convert.ToInt32(str.Split('.').Last());
|
||||
|
@ -180,7 +185,7 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (var c in this.Cues)
|
||||
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.Payload);
|
||||
|
@ -190,9 +195,32 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string ToStringWithHeader()
|
||||
public string ToVtt()
|
||||
{
|
||||
return "WEBVTT" + Environment.NewLine + Environment.NewLine + ToString();
|
||||
}
|
||||
|
||||
public string ToSrt()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int index = 1;
|
||||
foreach (var c in GetCues())
|
||||
{
|
||||
sb.AppendLine($"{index++}");
|
||||
sb.AppendLine(c.StartTime.ToString(@"hh\:mm\:ss\,fff") + " --> " + c.EndTime.ToString(@"hh\:mm\:ss\,fff"));
|
||||
sb.AppendLine(c.Payload);
|
||||
sb.AppendLine();
|
||||
}
|
||||
sb.AppendLine();
|
||||
|
||||
var srt = sb.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(srt.Trim()))
|
||||
{
|
||||
srt = "1\r\n00:00:00,000 --> 00:00:01,000"; //空字幕
|
||||
}
|
||||
|
||||
return srt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,10 +62,10 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
|
||||
//选中第一个SmoothStreamingMedia节点
|
||||
var ssmElement = xmlDocument.Elements().First(e => e.Name.LocalName == "SmoothStreamingMedia");
|
||||
bool isLive = Convert.ToBoolean(ssmElement.Attribute("IsLive")?.Value ?? "FALSE");
|
||||
var timeScaleStr = ssmElement.Attribute("TimeScale")?.Value ?? "10000000";
|
||||
var durationStr = ssmElement.Attribute("Duration")?.Value;
|
||||
var isLiveStr = ssmElement.Attribute("IsLive")?.Value;
|
||||
bool isLive = Convert.ToBoolean(isLiveStr ?? "FALSE");
|
||||
|
||||
var isProtection = false;
|
||||
var protectionSystemId = "";
|
||||
|
@ -121,6 +121,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
var channels = qualityLevel.Attribute("Channels")?.Value;
|
||||
|
||||
StreamSpec streamSpec = new();
|
||||
streamSpec.PublishTime = DateTime.Now; //发布时间默认现在
|
||||
streamSpec.Extension = "m4s";
|
||||
streamSpec.OriginalUrl = ParserConfig.OriginalUrl;
|
||||
streamSpec.PeriodId = indexStr;
|
||||
|
|
|
@ -122,6 +122,11 @@ namespace Mp4SubtitleParser
|
|||
return MultiElementsFixRegex().Matches(xml).Select(m => m.Value).ToList();
|
||||
}
|
||||
|
||||
public static WebVttSub ExtractFromMp4(string item, long segTimeMs, long baseTimestamp = 0L)
|
||||
{
|
||||
return ExtractFromMp4s(new string[] { item }, segTimeMs, baseTimestamp);
|
||||
}
|
||||
|
||||
public static WebVttSub ExtractFromMp4s(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
|
||||
{
|
||||
//read ttmls
|
||||
|
@ -163,6 +168,11 @@ namespace Mp4SubtitleParser
|
|||
return ExtractSub(xmls, baseTimestamp);
|
||||
}
|
||||
|
||||
public static WebVttSub ExtractFromTTML(string item, long segTimeMs, long baseTimestamp = 0L)
|
||||
{
|
||||
return ExtractFromTTMLs(new string[] { item }, segTimeMs, baseTimestamp);
|
||||
}
|
||||
|
||||
public static WebVttSub ExtractFromTTMLs(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
|
||||
{
|
||||
//read ttmls
|
||||
|
|
|
@ -360,12 +360,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
FileDic.Clear();
|
||||
var index = 0;
|
||||
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
||||
var subContentFixed = finalVtt.ToStringWithHeader();
|
||||
var subContentFixed = finalVtt.ToVtt();
|
||||
//转换字幕格式
|
||||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
{
|
||||
path = Path.ChangeExtension(path, ".srt");
|
||||
subContentFixed = OtherUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
subContentFixed = finalVtt.ToSrt();
|
||||
}
|
||||
await File.WriteAllTextAsync(path, subContentFixed, Encoding.UTF8);
|
||||
FileDic[keys.First()] = new DownloadResult()
|
||||
|
@ -394,12 +394,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
FileDic.Clear();
|
||||
var index = 0;
|
||||
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
||||
var subContentFixed = finalVtt.ToStringWithHeader();
|
||||
var subContentFixed = finalVtt.ToVtt();
|
||||
//转换字幕格式
|
||||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
{
|
||||
path = Path.ChangeExtension(path, ".srt");
|
||||
subContentFixed = OtherUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
subContentFixed = finalVtt.ToSrt();
|
||||
}
|
||||
await File.WriteAllTextAsync(path, subContentFixed, Encoding.UTF8);
|
||||
FileDic[firstKey] = new DownloadResult()
|
||||
|
@ -420,7 +420,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
var keys = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Key);
|
||||
foreach (var seg in keys)
|
||||
{
|
||||
var vtt = MP4TtmlUtil.ExtractFromTTMLs(new string[] { FileDic[seg]!.ActualFilePath }, 0);
|
||||
var vtt = MP4TtmlUtil.ExtractFromTTML(FileDic[seg]!.ActualFilePath, 0);
|
||||
//手动计算MPEGTS
|
||||
if (finalVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||
{
|
||||
|
@ -452,12 +452,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
FileDic.Clear();
|
||||
var index = 0;
|
||||
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
||||
var subContentFixed = finalVtt.ToStringWithHeader();
|
||||
var subContentFixed = finalVtt.ToVtt();
|
||||
//转换字幕格式
|
||||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
{
|
||||
path = Path.ChangeExtension(path, ".srt");
|
||||
subContentFixed = OtherUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
subContentFixed = finalVtt.ToSrt();
|
||||
}
|
||||
await File.WriteAllTextAsync(path, subContentFixed, Encoding.UTF8);
|
||||
FileDic[firstKey] = new DownloadResult()
|
||||
|
@ -482,7 +482,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
var keys = FileDic.OrderBy(s => s.Key.Index).Where(v => v.Value!.ActualFilePath.EndsWith(".m4s")).Select(s => s.Key);
|
||||
foreach (var seg in keys)
|
||||
{
|
||||
var vtt = MP4TtmlUtil.ExtractFromMp4s(new string[] { FileDic[seg]!.ActualFilePath }, 0);
|
||||
var vtt = MP4TtmlUtil.ExtractFromMp4(FileDic[seg]!.ActualFilePath, 0);
|
||||
//手动计算MPEGTS
|
||||
if (finalVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||
{
|
||||
|
@ -515,12 +515,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
FileDic.Clear();
|
||||
var index = 0;
|
||||
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
|
||||
var subContentFixed = finalVtt.ToStringWithHeader();
|
||||
var subContentFixed = finalVtt.ToVtt();
|
||||
//转换字幕格式
|
||||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
{
|
||||
path = Path.ChangeExtension(path, ".srt");
|
||||
subContentFixed = OtherUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
subContentFixed = finalVtt.ToSrt();
|
||||
}
|
||||
await File.WriteAllTextAsync(path, subContentFixed, Encoding.UTF8);
|
||||
FileDic[firstKey] = new DownloadResult()
|
||||
|
|
|
@ -209,6 +209,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
//TryReceiveAll可以稍微缓解一下
|
||||
source.TryReceiveAll(out IList<List<MediaSegment>>? segmentsList);
|
||||
var segments = segmentsList!.SelectMany(s => s);
|
||||
var segmentsDuration = segments.Sum(s => s.Duration);
|
||||
Logger.DebugMarkUp(string.Join(",", segments.Select(sss => GetSegmentName(sss, false, false))));
|
||||
|
||||
//下载init
|
||||
|
@ -355,8 +356,6 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
});
|
||||
|
||||
RecordingDurDic[task.Id] += (int)segments.Sum(s => s.Duration);
|
||||
|
||||
//自动修复VTT raw字幕
|
||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("vtt"))
|
||||
|
@ -372,15 +371,8 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
{
|
||||
vtt.MpegtsTimestamp = 90000 * (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration);
|
||||
}
|
||||
if (firstSub)
|
||||
{
|
||||
currentVtt = vtt;
|
||||
firstSub = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentVtt.AddCuesFromOne(vtt);
|
||||
}
|
||||
if (firstSub) { currentVtt = vtt; firstSub = false; }
|
||||
else currentVtt.AddCuesFromOne(vtt);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -401,8 +393,8 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
}
|
||||
else
|
||||
{
|
||||
var finalVtt = MP4VttUtil.ExtractSub(mp4s, timescale);
|
||||
currentVtt.AddCuesFromOne(finalVtt);
|
||||
var vtt = MP4VttUtil.ExtractSub(mp4s, timescale);
|
||||
currentVtt.AddCuesFromOne(vtt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -411,21 +403,40 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("ttml"))
|
||||
{
|
||||
var mp4s = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).Where(p => p.EndsWith(".ttml")).ToArray();
|
||||
var keys = FileDic.OrderBy(s => s.Key.Index).Where(v => v.Value!.ActualFilePath.EndsWith(".m4s")).Select(s => s.Key);
|
||||
if (firstSub)
|
||||
{
|
||||
if (baseTimestamp != 0)
|
||||
{
|
||||
var total = segments.Sum(s => s.Duration);
|
||||
var total = segmentsDuration;
|
||||
baseTimestamp -= (long)TimeSpan.FromSeconds(total).TotalMilliseconds;
|
||||
}
|
||||
currentVtt = MP4TtmlUtil.ExtractFromTTMLs(mp4s, 0, baseTimestamp);
|
||||
var first = true;
|
||||
foreach (var seg in keys)
|
||||
{
|
||||
var vtt = MP4TtmlUtil.ExtractFromTTML(FileDic[seg]!.ActualFilePath, 0, first ? baseTimestamp : 0);
|
||||
//手动计算MPEGTS
|
||||
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||
{
|
||||
vtt.MpegtsTimestamp = 90000 * (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration);
|
||||
}
|
||||
if (first) { currentVtt = vtt; first = false; }
|
||||
else currentVtt.AddCuesFromOne(vtt);
|
||||
}
|
||||
firstSub = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var finalVtt = MP4TtmlUtil.ExtractFromTTMLs(mp4s, 0, baseTimestamp);
|
||||
currentVtt.AddCuesFromOne(finalVtt);
|
||||
foreach (var seg in keys)
|
||||
{
|
||||
var vtt = MP4TtmlUtil.ExtractFromTTML(FileDic[seg]!.ActualFilePath, 0);
|
||||
//手动计算MPEGTS
|
||||
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||
{
|
||||
vtt.MpegtsTimestamp = 90000 * (RecordingDurDic[task.Id] + (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
|
||||
}
|
||||
currentVtt.AddCuesFromOne(vtt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -438,23 +449,44 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
//var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault();
|
||||
//var iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath);
|
||||
//var sawTtml = MP4TtmlUtil.CheckInit(iniFileBytes);
|
||||
var mp4s = FileDic.OrderBy(s => s.Key.Index).Select(s => s.Value).Select(v => v!.ActualFilePath).Where(p => p.EndsWith(".m4s")).ToArray();
|
||||
var keys = FileDic.OrderBy(s => s.Key.Index).Where(v => v.Value!.ActualFilePath.EndsWith(".m4s")).Select(s => s.Key);
|
||||
if (firstSub)
|
||||
{
|
||||
if (baseTimestamp != 0)
|
||||
{
|
||||
var total = segments.Sum(s => s.Duration);
|
||||
var total = segmentsDuration;
|
||||
baseTimestamp -= (long)TimeSpan.FromSeconds(total).TotalMilliseconds;
|
||||
}
|
||||
currentVtt = MP4TtmlUtil.ExtractFromMp4s(mp4s, 0, baseTimestamp);
|
||||
var first = true;
|
||||
foreach (var seg in keys)
|
||||
{
|
||||
var vtt = MP4TtmlUtil.ExtractFromMp4(FileDic[seg]!.ActualFilePath, 0, first ? baseTimestamp : 0);
|
||||
//手动计算MPEGTS
|
||||
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||
{
|
||||
vtt.MpegtsTimestamp = 90000 * (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration);
|
||||
}
|
||||
if (first) { currentVtt = vtt; first = false; }
|
||||
else currentVtt.AddCuesFromOne(vtt);
|
||||
}
|
||||
firstSub = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var finalVtt = MP4TtmlUtil.ExtractFromMp4s(mp4s, 0, baseTimestamp);
|
||||
currentVtt.AddCuesFromOne(finalVtt);
|
||||
foreach (var seg in keys)
|
||||
{
|
||||
var vtt = MP4TtmlUtil.ExtractFromMp4(FileDic[seg]!.ActualFilePath, 0);
|
||||
//手动计算MPEGTS
|
||||
if (currentVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
|
||||
{
|
||||
vtt.MpegtsTimestamp = 90000 * (RecordingDurDic[task.Id] + (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
|
||||
}
|
||||
currentVtt.AddCuesFromOne(vtt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RecordingDurDic[task.Id] += (int)segmentsDuration;
|
||||
|
||||
/*//写出m3u8
|
||||
if (DownloaderConfig.MyOptions.LiveWriteHLS)
|
||||
|
@ -538,10 +570,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
File.Delete(inputFilePath);
|
||||
}
|
||||
}
|
||||
var subText = currentVtt.ToStringWithHeader();
|
||||
var subText = currentVtt.ToVtt();
|
||||
if (outputExt == ".srt")
|
||||
{
|
||||
subText = OtherUtil.WebVtt2Other(currentVtt, Enum.SubtitleFormat.SRT);
|
||||
subText = currentVtt.ToSrt();
|
||||
}
|
||||
var subBytes = Encoding.UTF8.GetBytes(subText);
|
||||
fileOutputStream.Position = 0;
|
||||
|
|
|
@ -27,40 +27,6 @@ namespace N_m3u8DL_RE.Util
|
|||
return dic;
|
||||
}
|
||||
|
||||
private static string WebVtt2Srt(WebVttSub vtt)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int index = 1;
|
||||
foreach (var c in vtt.Cues)
|
||||
{
|
||||
sb.AppendLine($"{index++}");
|
||||
sb.AppendLine(c.StartTime.ToString(@"hh\:mm\:ss\,fff") + " --> " + c.EndTime.ToString(@"hh\:mm\:ss\,fff"));
|
||||
sb.AppendLine(c.Payload);
|
||||
sb.AppendLine();
|
||||
}
|
||||
sb.AppendLine();
|
||||
|
||||
var srt = sb.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(srt.Trim()))
|
||||
{
|
||||
srt = "1\r\n00:00:00,000 --> 00:00:01,000"; //空字幕
|
||||
}
|
||||
|
||||
return srt;
|
||||
}
|
||||
|
||||
public static string WebVtt2Other(WebVttSub vtt, SubtitleFormat toFormat)
|
||||
{
|
||||
Logger.Debug($"Convert {SubtitleFormat.VTT} ==> {toFormat}");
|
||||
return toFormat switch
|
||||
{
|
||||
SubtitleFormat.VTT => vtt.ToStringWithHeader(),
|
||||
SubtitleFormat.SRT => WebVtt2Srt(vtt),
|
||||
_ => throw new NotSupportedException($"{toFormat} not supported!")
|
||||
};
|
||||
}
|
||||
|
||||
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"
|
||||
.Split(',').Select(s => (char)int.Parse(s)).ToArray();
|
||||
public static string GetValidFileName(string input, string re = ".", bool filterSlash = false)
|
||||
|
|
Loading…
Reference in New Issue