mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-09-07 05:27:58 +00:00
Add dark mode highlighting for SubStation Alpha
This commit is contained in:
@@ -0,0 +1,685 @@
|
||||
// Copyright © 2017-2025 QL-Win Contributors
|
||||
//
|
||||
// This file is part of QuickLook program.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using ICSharpCode.AvalonEdit.Document;
|
||||
using ICSharpCode.AvalonEdit.Highlighting;
|
||||
using ICSharpCode.AvalonEdit.Rendering;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace QuickLook.Plugin.TextViewer.Themes.HighlightingDefinitions.Dark;
|
||||
|
||||
public class SubStationAlphaHighlightingDefinition : DarkHighlightingDefinition
|
||||
{
|
||||
public override string Name => "SubStation Alpha";
|
||||
|
||||
public override string Extension => ".ass;.ssa";
|
||||
|
||||
public override HighlightingRuleSet MainRuleSet => new()
|
||||
{
|
||||
Rules =
|
||||
{
|
||||
new HighlightingRule
|
||||
{
|
||||
Regex = new Regex(@";.*", RegexOptions.Compiled),
|
||||
Color = GetNamedColor("Comment")
|
||||
},
|
||||
new HighlightingRule
|
||||
{
|
||||
Regex = new Regex(@"!:.*", RegexOptions.Compiled),
|
||||
Color = GetNamedColor("Comment")
|
||||
},
|
||||
new HighlightingRule
|
||||
{
|
||||
Regex = new Regex(@"Comment:.*", RegexOptions.Compiled),
|
||||
Color = GetNamedColor("Comment")
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
public override HighlightingColor GetNamedColor(string name)
|
||||
{
|
||||
return name switch
|
||||
{
|
||||
"Comment" => new HighlightingColor
|
||||
{
|
||||
Name = "Comment",
|
||||
Foreground = new SimpleHighlightingBrush("#6A9949".ToColor()),
|
||||
},
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
public override IEnumerable<HighlightingColor> NamedHighlightingColors =>
|
||||
[
|
||||
GetNamedColor("Comment"),
|
||||
];
|
||||
|
||||
public override DocumentColorizingTransformer[] LineTransformers { get; } = [new KeyHighlighter()];
|
||||
|
||||
public class KeyHighlighter : DocumentColorizingTransformer
|
||||
{
|
||||
public Regex SessionRegex { get; } = new(@"\[[^\[\]]*\]", RegexOptions.Compiled);
|
||||
|
||||
public SessionStore Sessions { get; } = [];
|
||||
|
||||
protected override void ColorizeLine(DocumentLine line)
|
||||
{
|
||||
var text = CurrentContext.Document.GetText(line);
|
||||
var textTrimmed = text.TrimStart('\uFEFF').Trim(); // Skip UTF8-BOM (U+FEFF)
|
||||
|
||||
if (string.IsNullOrWhiteSpace(text) || textTrimmed.StartsWith(";") || textTrimmed.StartsWith("Comment:") || textTrimmed.StartsWith("!:"))
|
||||
return;
|
||||
|
||||
if (textTrimmed.StartsWith("[") && SessionRegex.IsMatch(textTrimmed))
|
||||
{
|
||||
var match = Regex.Match(textTrimmed, @"\[(.*?)\]");
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
string sessionName = match.Groups[1].Value;
|
||||
|
||||
var idxStart = text.IndexOf('[');
|
||||
var idxEnd = text.IndexOf(']');
|
||||
|
||||
// Session
|
||||
ChangeLinePart(line.Offset + idxStart, line.Offset + idxStart + 1, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#FFD705".ToBrush());
|
||||
});
|
||||
ChangeLinePart(line.Offset + idxEnd, line.Offset + idxEnd + 1, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#FFD705".ToBrush());
|
||||
});
|
||||
|
||||
if (!Sessions.ContainsKey(sessionName))
|
||||
Sessions.Add(sessionName, new Session
|
||||
{
|
||||
Name = sessionName,
|
||||
Formats = [],
|
||||
LineNumber = line.LineNumber,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Events
|
||||
// e.g. Comment: 0,0:00:19.41,0:00:21.87,Style1,,0,0,0,,ABCDEF
|
||||
// e.g. Dialogue: 0,0:00:19.41,0:00:21.87,Style2,,0,0,0,,ABCDEF
|
||||
if (Sessions.IsCurrentSession("Events", line.LineNumber))
|
||||
{
|
||||
if (text.StartsWith("Format:"))
|
||||
{
|
||||
Sessions["Events"].Formats = [.. text.Substring(text.IndexOf(':')).Split(',').Select(f => f.Trim())];
|
||||
SetFormatForegroundBrush();
|
||||
}
|
||||
else if (text.StartsWith("Dialogue:"))
|
||||
{
|
||||
if (Sessions.ContainsKey("Events"))
|
||||
{
|
||||
SetEventForegroundBrush("Events");
|
||||
}
|
||||
}
|
||||
}
|
||||
// V4 Styles
|
||||
// Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding
|
||||
else if (Sessions.IsCurrentSession("V4 Styles", line.LineNumber))
|
||||
{
|
||||
if (text.StartsWith("Format:"))
|
||||
{
|
||||
Sessions["V4 Styles"].Formats = [.. text.Substring("Format:".Length).Split(',').Select(f => f.Trim())];
|
||||
SetFormatForegroundBrush();
|
||||
}
|
||||
else if (text.StartsWith("Style:"))
|
||||
{
|
||||
if (Sessions.ContainsKey("V4 Styles"))
|
||||
{
|
||||
SetStyleForegroundBrush("V4 Styles");
|
||||
}
|
||||
}
|
||||
}
|
||||
// V4+ Styles
|
||||
// Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding, Layer
|
||||
else if (Sessions.IsCurrentSession("V4+ Styles", line.LineNumber))
|
||||
{
|
||||
if (text.StartsWith("Format:"))
|
||||
{
|
||||
Sessions["V4+ Styles"].Formats = [.. text.Substring("Format:".Length).Split(',').Select(f => f.Trim())];
|
||||
SetFormatForegroundBrush();
|
||||
}
|
||||
else if (text.StartsWith("Style:"))
|
||||
{
|
||||
if (Sessions.ContainsKey("V4+ Styles"))
|
||||
{
|
||||
SetStyleForegroundBrush("V4+ Styles");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Script Info
|
||||
// Aegisub Project Garbage
|
||||
else
|
||||
{
|
||||
SetInfoForegroundBrush();
|
||||
}
|
||||
|
||||
// Info
|
||||
void SetInfoForegroundBrush()
|
||||
{
|
||||
int idx = text.IndexOf(':');
|
||||
|
||||
if (idx <= 0)
|
||||
return;
|
||||
|
||||
var val = text.Substring(idx + 1);
|
||||
var valTrimmed = val.Trim();
|
||||
var type = DetecteType(val);
|
||||
|
||||
// Key
|
||||
ChangeLinePart(line.Offset, line.Offset + idx, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#3F9CD6".ToBrush());
|
||||
});
|
||||
|
||||
// Value
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
}
|
||||
|
||||
// Style
|
||||
void SetStyleForegroundBrush(string sessionName)
|
||||
{
|
||||
var sessionFormats = Sessions[sessionName].Formats;
|
||||
|
||||
int[] idxes = SpliteIndexes(text, ',');
|
||||
int idxPrev = text.IndexOf(':');
|
||||
|
||||
ChangeLinePart(line.Offset, line.Offset + idxPrev, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#3F9CD6".ToBrush());
|
||||
});
|
||||
|
||||
for (int i = default; i < Math.Min(idxes.Length, sessionFormats.Length); i++)
|
||||
{
|
||||
int idxStart = idxPrev + 1;
|
||||
int idxEnd = idxPrev = idxes[i];
|
||||
|
||||
var val = text.Substring(idxStart, idxEnd - idxStart);
|
||||
var valTrimmed = val.Trim();
|
||||
var type = DetecteType(val);
|
||||
|
||||
if (sessionFormats[i].EndsWith("Name", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart, line.Offset + idxEnd, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(ValueType.String).ToBrush());
|
||||
});
|
||||
}
|
||||
else if (sessionFormats[i].EndsWith("Colour"))
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart, line.Offset + idxEnd, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#DCDCAA".ToBrush());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart, line.Offset + idxEnd, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Events
|
||||
void SetEventForegroundBrush(string sessionName)
|
||||
{
|
||||
var sessionFormats = Sessions[sessionName].Formats;
|
||||
|
||||
int[] idxes = SpliteIndexes(text, ',');
|
||||
int idxPrev = text.IndexOf(':');
|
||||
|
||||
ChangeLinePart(line.Offset, line.Offset + idxPrev, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#3F9CD6".ToBrush());
|
||||
});
|
||||
|
||||
for (int i = default; i < Math.Min(idxes.Length, sessionFormats.Length); i++)
|
||||
{
|
||||
int idxStart = idxPrev + 1;
|
||||
int idxEnd = idxPrev = idxes[i];
|
||||
|
||||
var val = text.Substring(idxStart, idxEnd - idxStart);
|
||||
var valTrimmed = val.Trim();
|
||||
var type = DetecteType(val);
|
||||
|
||||
if (sessionFormats[i] == "Name" || sessionFormats[i] == "Style")
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart, line.Offset + idxEnd, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(ValueType.String).ToBrush());
|
||||
});
|
||||
}
|
||||
else if (sessionFormats[i] == "Start" || sessionFormats[i] == "End")
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart, line.Offset + idxEnd, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#DCDCAA".ToBrush());
|
||||
});
|
||||
}
|
||||
else if (sessionFormats[i] == "Text")
|
||||
{
|
||||
var valFixed = text.Substring(idxes[i - 1] + 1);
|
||||
SubtitleLine subtitleLine = SubtitleEffectParser.Parse(valFixed);
|
||||
|
||||
foreach (var item in subtitleLine)
|
||||
{
|
||||
var itemVal = item.Value;
|
||||
|
||||
if (!string.IsNullOrEmpty(itemVal.Effect))
|
||||
{
|
||||
// Effect All
|
||||
ChangeLinePart(line.Offset + idxStart + itemVal.StartIndex, line.Offset + idxStart + itemVal.EndIndex, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#4EC9A2".ToBrush());
|
||||
});
|
||||
|
||||
var words = SplitWords(itemVal.Effect);
|
||||
|
||||
// Effect Text
|
||||
foreach (var word in words)
|
||||
{
|
||||
var typeEffect = DetecteType(word.Text);
|
||||
|
||||
if (typeEffect == ValueType.Numeric)
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart + itemVal.StartIndex + word.StartIndex, line.Offset + idxStart + itemVal.StartIndex + word.EndIndex, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(typeEffect).ToBrush());
|
||||
});
|
||||
}
|
||||
else if (typeEffect == ValueType.String)
|
||||
{
|
||||
if (word.Text.StartsWith("fn")) // \fn<FontName>
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart + itemVal.StartIndex + word.StartIndex + 2, line.Offset + idxStart + itemVal.StartIndex + word.EndIndex, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(typeEffect).ToBrush());
|
||||
});
|
||||
}
|
||||
else if (word.Text.StartsWith("r")) // \r<StyleName>
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart + itemVal.StartIndex + word.StartIndex + 1, line.Offset + idxStart + itemVal.StartIndex + word.EndIndex, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(typeEffect).ToBrush());
|
||||
});
|
||||
}
|
||||
else if (word.Text.StartsWith("alpha") // \alpha[&][H][&]
|
||||
|| word.Text.StartsWith("1a") // \1a[&][H][&]
|
||||
|| word.Text.StartsWith("2a") // \2a[&][H][&]
|
||||
|| word.Text.StartsWith("3a") // \3a[&][H][&]
|
||||
|| word.Text.StartsWith("4a") // \4a[&][H][&]
|
||||
|| word.Text.StartsWith("c") // \c[&][H][&] or \c
|
||||
|| word.Text.StartsWith("1c") // \1c[&][H][&]
|
||||
|| word.Text.StartsWith("2c") // \2c[&][H][&]
|
||||
|| word.Text.StartsWith("3c") // \3c[&][H][&]
|
||||
|| word.Text.StartsWith("4c") // \4c[&][H][&]
|
||||
)
|
||||
{
|
||||
var idxAnd = word.Text.IndexOf('&');
|
||||
|
||||
if (idxAnd > 0)
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart + itemVal.StartIndex + word.StartIndex + idxAnd, line.Offset + idxStart + itemVal.StartIndex + word.EndIndex, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#DCDCAA".ToBrush());
|
||||
});
|
||||
}
|
||||
else if (word.Text.StartsWith("alpha"))
|
||||
{
|
||||
var idxNum = IndexOfFirstDigit(word.Text);
|
||||
|
||||
if (idxNum > 0)
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart + itemVal.StartIndex + word.StartIndex + idxNum, line.Offset + idxStart + itemVal.StartIndex + word.EndIndex, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(ValueType.Numeric).ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var idxNum = IndexOfFirstDigit(word.Text);
|
||||
|
||||
if (idxNum > 0)
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart + itemVal.StartIndex + word.StartIndex + idxNum, line.Offset + idxStart + itemVal.StartIndex + word.EndIndex, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(ValueType.Numeric).ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Effect Braces
|
||||
for (int j = default; j < itemVal.Effect.Length; j++)
|
||||
{
|
||||
var ch = itemVal.Effect[j];
|
||||
|
||||
if (ch == '{' || ch == '}')
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart + itemVal.StartIndex + j, line.Offset + idxStart + itemVal.StartIndex + j + 1, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#F1D700".ToBrush());
|
||||
});
|
||||
}
|
||||
else if (ch == '(' || ch == ')' || ch == ',')
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart + itemVal.StartIndex + j, line.Offset + idxStart + itemVal.StartIndex + j + 1, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#DA70D6".ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#if flase // Keep text color as default
|
||||
// Text
|
||||
ChangeLinePart(line.Offset + idxStart + itemVal.StartIndex, line.Offset + idxStart + itemVal.EndIndex, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(ValueType.String).ToBrush());
|
||||
});
|
||||
#endif
|
||||
|
||||
// Line Breaks (\N or \n) and Space (\h)
|
||||
for (int j = default; j < itemVal.Text.Length; j++)
|
||||
{
|
||||
var ch = itemVal.Text[j];
|
||||
|
||||
if (ch == '\\' && j + 1 < itemVal.Text.Length)
|
||||
{
|
||||
var chNext = itemVal.Text[j + 1];
|
||||
|
||||
if (chNext == 'N' || chNext == 'n' || chNext == 'h')
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart + itemVal.StartIndex + j, line.Offset + idxStart + itemVal.StartIndex + j + 2, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#A0A0A0".ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeLinePart(line.Offset + idxStart, line.Offset + idxEnd, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format
|
||||
void SetFormatForegroundBrush()
|
||||
{
|
||||
int[] idxes = SpliteIndexes(text, ',');
|
||||
int idxPrev = text.IndexOf(':');
|
||||
|
||||
ChangeLinePart(line.Offset, line.Offset + idxPrev, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#3F9CD6".ToBrush());
|
||||
});
|
||||
|
||||
for (int i = default; i < idxes.Length; i++)
|
||||
{
|
||||
int idxStart = idxPrev + 1;
|
||||
int idxEnd = idxPrev = idxes[i];
|
||||
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
_ = text.Substring(idxStart, idxEnd - idxStart);
|
||||
}
|
||||
|
||||
ChangeLinePart(line.Offset + idxStart, line.Offset + idxEnd, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#3F9CD6".ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ValueType DetecteType(string input)
|
||||
{
|
||||
if (double.TryParse(input, out _))
|
||||
return ValueType.Numeric;
|
||||
|
||||
if (bool.TryParse(input, out _))
|
||||
return ValueType.Boolean;
|
||||
|
||||
return ValueType.String;
|
||||
}
|
||||
|
||||
private string GetValueColor(ValueType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ValueType.Numeric => "#B5CEA8",
|
||||
ValueType.Boolean => "#719BD1",
|
||||
ValueType.String or _ => "#CE9178",
|
||||
};
|
||||
}
|
||||
|
||||
private static int[] SpliteIndexes(string text, char target)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return [];
|
||||
|
||||
var indexes = new List<int>();
|
||||
|
||||
for (int i = default; i < text.Length; i++)
|
||||
{
|
||||
if (target == text[i])
|
||||
{
|
||||
indexes.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last index to ensure the last segment is captured
|
||||
indexes.Add(text.Length);
|
||||
return [.. indexes];
|
||||
}
|
||||
|
||||
public static InlineText[] SplitWords(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return [];
|
||||
|
||||
var result = new List<InlineText>();
|
||||
// 定义正则表达式:匹配非分隔符的连续字符段
|
||||
// 分隔符包括:空格 \ { } ( ) , (注意 \\ 需要双转义)
|
||||
var matches = Regex.Matches(input, @"[^{}\(\)\\,\s]+");
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
result.Add(new InlineText
|
||||
{
|
||||
Text = match.Value,
|
||||
StartIndex = match.Index,
|
||||
EndIndex = match.Index + match.Length,
|
||||
});
|
||||
}
|
||||
|
||||
return [.. result];
|
||||
}
|
||||
|
||||
public static int IndexOfFirstDigit(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return -1;
|
||||
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (char.IsDigit(input[i]))
|
||||
{
|
||||
if (i > 0 && input[i - 1] == '-')
|
||||
return i - 1;
|
||||
else
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private enum ValueType
|
||||
{
|
||||
String,
|
||||
Numeric,
|
||||
Boolean,
|
||||
}
|
||||
|
||||
public sealed class SessionStore : Dictionary<string, Session>
|
||||
{
|
||||
public bool IsCurrentSession(string sessionName, int currentLineNumber)
|
||||
{
|
||||
return ContainsKey(sessionName) && currentLineNumber > this[sessionName].LineNumber;
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
public sealed class Session
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string[] Formats { get; set; }
|
||||
|
||||
public int LineNumber { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Name}] {string.Join(", ", Formats)} in line {LineNumber}";
|
||||
}
|
||||
}
|
||||
|
||||
public static class SubtitleEffectParser
|
||||
{
|
||||
/// <summary>
|
||||
/// 解析ASS字幕行,把特效和正文分块输出
|
||||
/// </summary>
|
||||
/// <param name="line">ASS原始字符串</param>
|
||||
/// <returns>SubtitleLine对象</returns>
|
||||
public static SubtitleLine Parse(string line)
|
||||
{
|
||||
var result = new SubtitleLine();
|
||||
int segIdx = 0;
|
||||
var regex = new Regex(@"\{.*?\}");
|
||||
|
||||
int lastIndex = 0;
|
||||
foreach (Match match in regex.Matches(line))
|
||||
{
|
||||
// 处理特效前的文本
|
||||
if (match.Index > lastIndex)
|
||||
{
|
||||
string text = line.Substring(lastIndex, match.Index - lastIndex);
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
result[segIdx.ToString()] = new SubtitleText
|
||||
{
|
||||
Effect = null,
|
||||
Text = text,
|
||||
StartIndex = lastIndex,
|
||||
EndIndex = match.Index
|
||||
};
|
||||
segIdx++;
|
||||
}
|
||||
}
|
||||
// 处理特效
|
||||
result[segIdx.ToString()] = new SubtitleText
|
||||
{
|
||||
Effect = match.Value,
|
||||
Text = null,
|
||||
StartIndex = match.Index,
|
||||
EndIndex = match.Index + match.Length
|
||||
};
|
||||
segIdx++;
|
||||
lastIndex = match.Index + match.Length;
|
||||
}
|
||||
// 处理最后的文本
|
||||
if (lastIndex < line.Length)
|
||||
{
|
||||
string text = line.Substring(lastIndex);
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
result[segIdx.ToString()] = new SubtitleText
|
||||
{
|
||||
Effect = null,
|
||||
Text = text,
|
||||
StartIndex = lastIndex,
|
||||
EndIndex = line.Length
|
||||
};
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public class SubtitleLine : Dictionary<string, SubtitleText>
|
||||
{
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
public class SubtitleText
|
||||
{
|
||||
public string Effect { get; set; }
|
||||
|
||||
public string Text { get; set; }
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
|
||||
public int EndIndex { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Effect} {Text} [{StartIndex}, {EndIndex}]";
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
public class InlineText
|
||||
{
|
||||
public string Text { get; set; }
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
|
||||
public int EndIndex { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Text} [{StartIndex}, {EndIndex}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user