支持TTML+PNG图形字幕

This commit is contained in:
nilaoda 2022-11-14 16:30:58 +08:00
parent 438b2dea7b
commit b0c25c7f25
6 changed files with 82 additions and 12 deletions

View File

@ -103,6 +103,7 @@ namespace N_m3u8DL_RE.Common.Resource
public static string writeJson { get => GetText("writeJson"); }
public static string noStreamsToDownload { get => GetText("noStreamsToDownload"); }
public static string newVersionFound { get => GetText("newVersionFound"); }
public static string processImageSub { get => GetText("processImageSub"); }
private static string GetText(string key)
{

View File

@ -10,6 +10,12 @@ namespace N_m3u8DL_RE.Common.Resource
{
public static Dictionary<string, TextContainer> LANG_DIC = new()
{
["processImageSub"] = new TextContainer
(
zhCN: "正在处理图形字幕",
zhTW: "正在處理圖形字幕",
enUS: "Processing Image Sub"
),
["newVersionFound"] = new TextContainer
(
zhCN: "检测到新版本,请尽快升级!",

View File

@ -34,6 +34,8 @@ namespace Mp4SubtitleParser
private static partial Regex LabelFixRegex();
[GeneratedRegex("\\<tt[\\s\\S]*?\\<\\/tt\\>")]
private static partial Regex MultiElementsFixRegex();
[GeneratedRegex("\\<smpte:image.*xml:id=\\\"(.*?)\\\".*\\>([\\s\\S]*?)<\\/smpte:image>")]
private static partial Regex ImageRegex();
public static bool CheckInit(byte[] data)
{
@ -229,38 +231,66 @@ namespace Mp4SubtitleParser
if (_div == null)
continue;
//PNG Subs
var imageDic = new Dictionary<string, string>(); //id, Base64
if (ImageRegex().IsMatch(xmlDoc.InnerXml))
{
foreach (Match img in ImageRegex().Matches(xmlDoc.InnerXml))
{
imageDic.Add(img.Groups[1].Value, img.Groups[2].Value);
}
}
//Parse <p> label
foreach (XmlElement _p in _div!.SelectNodes("ns:p", nsMgr)!)
{
var _begin = _p.GetAttribute("begin");
var _end = _p.GetAttribute("end");
var _region = _p.GetAttribute("region");
var _bgImg = _p.GetAttribute("smpte:backgroundImage");
var sub = new SubEntity
{
Begin = _begin,
End = _end,
Region = _region
};
var _spans = _p.ChildNodes;
//Collect <span>
foreach (XmlNode _node in _spans)
if (string.IsNullOrEmpty(_bgImg))
{
if (_node.NodeType == XmlNodeType.Element)
var _spans = _p.ChildNodes;
//Collect <span>
foreach (XmlNode _node in _spans)
{
var _span = (XmlElement)_node;
if (string.IsNullOrEmpty(_span.InnerText))
continue;
sub.Contents.Add(_span);
sub.ContentStrings.Add(_span.OuterXml);
if (_node.NodeType == XmlNodeType.Element)
{
var _span = (XmlElement)_node;
if (string.IsNullOrEmpty(_span.InnerText))
continue;
sub.Contents.Add(_span);
sub.ContentStrings.Add(_span.OuterXml);
}
else if (_node.NodeType == XmlNodeType.Text)
{
var _span = new XmlDocument().CreateElement("span");
_span.InnerText = _node.Value!;
sub.Contents.Add(_span);
sub.ContentStrings.Add(_span.OuterXml);
}
}
else if (_node.NodeType == XmlNodeType.Text)
}
else
{
var id = _bgImg.Replace("#", "");
if (imageDic.ContainsKey(id))
{
var _span = new XmlDocument().CreateElement("span");
_span.InnerText = _node.Value!;
_span.InnerText = $"Base64::{imageDic[id]}";
sub.Contents.Add(_span);
sub.ContentStrings.Add(_span.OuterXml);
}
}
//Check if one <p> has been splitted
var index = finalSubs.FindLastIndex(s => s.End == _begin && s.Region == _region && s.ContentStrings.SequenceEqual(sub.ContentStrings));
//Skip empty lines

View File

@ -18,7 +18,7 @@ namespace N_m3u8DL_RE.CommandLine
{
internal partial class CommandInvoker
{
public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20221113";
public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20221114";
[GeneratedRegex("((best|worst)\\d*|all)")]
private static partial Regex ForStrRegex();

View File

@ -410,6 +410,22 @@ namespace N_m3u8DL_RE.DownloadManager
//写出字幕
var firstKey = FileDic.Keys.First();
var files = FileDic.Values.Select(v => v!.ActualFilePath).OrderBy(s => s).ToArray();
//处理图形字幕
if (finalVtt.Cues.All(v => v.Payload.StartsWith("Base64::")))
{
Logger.WarnMarkUp(ResString.processImageSub);
var _pad = "0".PadLeft(finalVtt.Cues.Count().ToString().Length, '0');
var _i = 0;
foreach (var img in finalVtt.Cues)
{
var base64 = img.Payload[8..];
var name = _i++.ToString(_pad) + ".png";
await File.WriteAllBytesAsync(Path.Combine(tmpDir, name), Convert.FromBase64String(base64));
img.Payload = name;
}
}
foreach (var item in files) File.Delete(item);
FileDic.Clear();
var index = 0;
@ -444,6 +460,22 @@ namespace N_m3u8DL_RE.DownloadManager
//写出字幕
var firstKey = FileDic.Keys.First();
var files = FileDic.Values.Select(v => v!.ActualFilePath).OrderBy(s => s).ToArray();
//处理图形字幕
if (finalVtt.Cues.All(v => v.Payload.StartsWith("Base64::")))
{
Logger.WarnMarkUp(ResString.processImageSub);
var _pad = "0".PadLeft(finalVtt.Cues.Count().ToString().Length, '0');
var _i = 0;
foreach (var img in finalVtt.Cues)
{
var base64 = img.Payload[8..];
var name = _i++.ToString(_pad) + ".png";
await File.WriteAllBytesAsync(Path.Combine(tmpDir, name), Convert.FromBase64String(base64));
img.Payload = name;
}
}
foreach (var item in files) File.Delete(item);
FileDic.Clear();
var index = 0;

View File

@ -193,6 +193,7 @@ namespace N_m3u8DL_RE
//url = "https://a38avoddashs3ww-a.akamaihd.net/ondemand/iad_2/8e91/f2f2/ec5a/430f-bd7a-0779f4a0189d/685cda75-609c-41c1-86bb-688f4cdb5521_corrected.mpd";
//url = "http://ht.grelighting.cn/m3u8/OEtYNVNjMFF5N1g2VzNkZ2lwbWEvd1ZtTGJ0dlZXOEk=.m3u8"; //特殊的图片伪装
//url = "https://ali6.a.yximgs.com/udata/music/music_295e59bdb3084def8158873ad6f5c8250.jpg"; //PNG图片伪装
//url = "https://vod.cds.nowonline.com.br/Content/dsc/VOD/movie/df/83/4dd69316-2022-45c0-a4f7-c35cad87df83/manifest.mpd"; //TTML+PNG
//url = "";
if (!string.IsNullOrEmpty(option.Input))