public AssDocument(string filePath, List <AssStyleOptions> styleOptions = null) { RegisterTagHandlers(); Dictionary <string, AssDocumentSection> fileSections = ReadDocument(filePath); AssDocumentSection infoSection = fileSections["Script Info"]; VideoDimensions = new Size(infoSection.GetItemInt("PlayResX", 384), infoSection.GetItemInt("PlayResY", 288)); _styles = fileSections["V4+ Styles"].MapItems("Style", i => new AssStyle(i)) .ToDictionary(s => s.Name); if (styleOptions != null) { _styleOptions = styleOptions.ToDictionary(o => o.Name); } foreach (AssDialogue dialogue in fileSections["Events"].MapItems("Dialogue", i => new AssDialogue(i))) { AssStyle style = GetStyle(dialogue.Style); if (style == null) { throw new Exception($"Line \"{dialogue.Text}\" refers to style \"{dialogue.Style}\" which doesn't exist."); } AssStyleOptions options = GetStyleOptions(dialogue.Style); List <AssLine> lines = ParseLine(dialogue, style, options); Lines.AddRange(lines.SelectMany(ExpandLine)); } foreach (AssLine line in Lines) { MergeIdenticallyFormattedSections(line); line.NormalizeAlpha(); } }
private List <AssLine> ParseLine(AssDialogue dialogue, AssStyle style, AssStyleOptions styleOptions) { AssLine line = new AssLine(dialogue.Start, dialogue.End) { AnchorPoint = style.AnchorPoint, KaraokeType = SimpleKaraokeType }; AssTagContext context = new AssTagContext { Document = this, InitialStyle = style, InitialStyleOptions = styleOptions, Style = style, StyleOptions = styleOptions, Line = line, Section = new AssSection(null) }; ApplyStyle(context.Section, style, styleOptions); CreateTagSections(line, dialogue.Text, context); CreateRubySections(line); List <AssLine> lines = new List <AssLine> { line }; foreach (AssTagContext.PostProcessor postProcessor in context.PostProcessors) { List <AssLine> extraLines = postProcessor(); if (extraLines != null) { lines.AddRange(extraLines); } } return(lines); }
private void WriteLineMetadata(AssLine line, AssStyle style, StreamWriter writer) { string effects = !line.AndroidDarkTextHackAllowed ? EffectNames.NoAndroidDarkTextHack : string.Empty; writer.Write($"Dialogue: 0,{line.Start:H:mm:ss.ff},{line.End:H:mm:ss.ff},{style.Name},,0,0,0,{effects},"); }
protected virtual void WriteLine(AssLine line, StreamWriter writer) { if (line.Sections.Count == 0) { return; } AssStyle style = GetStyleMatchingStructure((AssSection)line.Sections[0]); WriteLineMetadata(line, style, writer); AssSection prevSection = new AssSection(); AssStyle prevStyle = style; ApplyStyle(prevSection, prevStyle); AssLineContentBuilder lineContent = new AssLineContentBuilder(); AppendLineTags(line, style, lineContent); RubyPart currentRubyPosition = RubyPart.None; for (int i = 0; i < line.Sections.Count; i++) { AssSection section = (AssSection)line.Sections[i]; style = GetStyleMatchingStructure(section); if (style != prevStyle || (currentRubyPosition != RubyPart.None && section.RubyPart == RubyPart.None)) { lineContent.AppendTag("r", style); currentRubyPosition = RubyPart.None; prevSection = (AssSection)prevSection.Clone(); ApplyStyle(prevSection, style); } AppendSectionTags(section, prevSection, lineContent); if (section.RubyPart == RubyPart.Text) { RubyPart rubyPart; if (i + 4 > line.Sections.Count || ((rubyPart = line.Sections[i + 2].RubyPart) != RubyPart.RubyAbove && rubyPart != RubyPart.RubyBelow)) { throw new InvalidDataException("Invalid ruby sequence"); } if (rubyPart != currentRubyPosition) { lineContent.AppendTag("ytruby", rubyPart == RubyPart.RubyAbove ? 8 : 2); currentRubyPosition = rubyPart; } lineContent.AppendText($"[{section.Text}/{line.Sections[i + 2].Text}]"); i += 3; } else { lineContent.AppendText(section.Text); } prevSection = section; prevStyle = style; } writer.WriteLine(lineContent); }
internal void ApplyStyle(AssSection section, AssStyle style) { ApplyStyle(section, style, GetStyleOptions(style.Name)); }
public AssStyleOptions GetStyleOptions(AssStyle style) { return(GetStyleOptions(style.Name)); }
private List <AssLine> ParseLine(AssDialogue dialogue, AssStyle style, AssStyleOptions styleOptions) { DateTime startTime = TimeUtil.SnapTimeToFrame(dialogue.Start.AddMilliseconds(32)); DateTime endTime = TimeUtil.SnapTimeToFrame(dialogue.End).AddMilliseconds(32); AssLine line = new AssLine(startTime, endTime) { AnchorPoint = style.AnchorPoint }; AssTagContext context = new AssTagContext { Document = this, Dialogue = dialogue, InitialStyle = style, InitialStyleOptions = styleOptions, Style = style, StyleOptions = styleOptions, Line = line, Section = new AssSection(null) }; ApplyStyle(context.Section, style, styleOptions); string text = Regex.Replace(dialogue.Text, @"(?:\\N)+$", ""); int start = 0; foreach (Match match in Regex.Matches(text, @"\{(?:\\(?<tag>fn|\d?[a-z]+)(?<arg>\([^\{\}\(\)]*\)|[^\{\}\(\)\\]*))+\}")) { int end = match.Index; if (end > start) { context.Section.Text = text.Substring(start, end - start).Replace("\\N", "\r\n"); line.Sections.Add(context.Section); context.Section = (AssSection)context.Section.Clone(); context.Section.Text = null; context.Section.Duration = TimeSpan.Zero; } CaptureCollection tags = match.Groups["tag"].Captures; CaptureCollection arguments = match.Groups["arg"].Captures; for (int i = 0; i < tags.Count; i++) { if (_tagHandlers.TryGetValue(tags[i].Value, out AssTagHandlerBase handler)) { handler.Handle(context, arguments[i].Value); } } start = match.Index + match.Length; } if (start < text.Length) { context.Section.Text = text.Substring(start, text.Length - start).Replace("\\N", "\r\n"); line.Sections.Add(context.Section); } if (line.RubyPosition != RubyPosition.None) { CreateRubySections(line); } List <AssLine> lines = new List <AssLine> { line }; foreach (AssTagContext.PostProcessor postProcessor in context.PostProcessors) { List <AssLine> extraLines = postProcessor(); if (extraLines != null) { lines.AddRange(extraLines); } } return(lines); }