private static void ApplyGlitchKaraokeEffect(AssLine stepLine, AssSection singingSection) { if (singingSection.Text.Length == 0) { return; } ApplySimpleKaraokeEffect(singingSection); DateTime glitchEndTime = TimeUtil.Min(stepLine.Start.AddMilliseconds(70), stepLine.End); Util.CharacterRange[] charRanges = GetGlitchKaraokeCharacterRanges(singingSection.Text[0]); singingSection.Animations.Add(new GlitchingCharAnimation(stepLine.Start, glitchEndTime, charRanges)); }
protected override void Assign(Section section) { base.Assign(section); AssSection assSection = (AssSection)section; SecondaryColor = assSection.SecondaryColor; CurrentWordForeColor = assSection.CurrentWordForeColor; CurrentWordOutlineColor = assSection.CurrentWordOutlineColor; CurrentWordShadowColor = assSection.CurrentWordShadowColor; Duration = assSection.Duration; Animations.Clear(); Animations.AddRange(assSection.Animations); }
internal void ApplyStyle(AssSection section, AssStyle style, AssStyleOptions options) { section.Font = style.Font; section.Scale = style.LineHeight / DefaultStyle.LineHeight; section.Bold = style.Bold; section.Italic = style.Italic; section.Underline = style.Underline; section.ForeColor = style.PrimaryColor; section.SecondaryColor = style.SecondaryColor; if (options?.IsKaraoke ?? false) { section.CurrentWordForeColor = options.CurrentWordTextColor; section.CurrentWordOutlineColor = options.CurrentWordOutlineColor; section.CurrentWordShadowColor = options.CurrentWordShadowColor; } else { section.CurrentWordForeColor = Color.Empty; section.CurrentWordOutlineColor = Color.Empty; section.CurrentWordShadowColor = Color.Empty; } section.BackColor = Color.Empty; section.ShadowColors.Clear(); if (style.HasShadow) { foreach (ShadowType shadowType in options?.ShadowTypes ?? new List <ShadowType> { ShadowType.SoftShadow }) { section.ShadowColors[shadowType] = style.ShadowColor; } } if (style.HasOutline) { if (style.OutlineIsBox) { section.BackColor = style.OutlineColor; } else { section.ShadowColors[ShadowType.Glow] = style.OutlineColor; } } section.Blur = 0; }
private static void ApplyGlitchKaraokeEffect(AssLine stepLine, List <AssSection> singingSections) { AssSection singingSection = singingSections.LastOrDefault(s => s.RubyPart == RubyPart.None || s.RubyPart == RubyPart.Text); if (singingSection == null || singingSection.Text.Length == 0) { return; } ApplySimpleKaraokeEffect(singingSections); DateTime glitchEndTime = TimeUtil.Min(stepLine.Start.AddMilliseconds(70), stepLine.End); Util.CharacterRange[] charRanges = GetGlitchKaraokeCharacterRanges(singingSection.Text[0]); singingSection.Animations.Add(new GlitchingCharAnimation(stepLine.Start, glitchEndTime, charRanges)); }
public AssLine(Line line) : base(line) { if (line is AssLine || Sections.All(s => s.StartOffset == TimeSpan.Zero)) { return; } for (int i = 0; i < Sections.Count - 1; i++) { ((AssSection)Sections[i]).Duration = Sections[i + 1].StartOffset - Sections[i].StartOffset; } AssSection lastSection = (AssSection)Sections.Last(); lastSection.Duration = End - Start - lastSection.StartOffset; }
private static void CreateRubySections(AssLine line) { for (int sectionIdx = line.Sections.Count - 1; sectionIdx >= 0; sectionIdx--) { AssSection section = (AssSection)line.Sections[sectionIdx]; if (section.RubyPosition == RubyPosition.None) { continue; } MatchCollection matches = Regex.Matches(section.Text, @"\[(?<text>.+?)/(?<ruby>.+?)\]"); if (matches.Count == 0) { continue; } line.Sections.RemoveAt(sectionIdx); int interStartPos = 0; int numSubSections = 0; foreach (Match match in matches) { if (match.Index > interStartPos) { InsertRubySection(line, section, section.Text.Substring(interStartPos, match.Index - interStartPos), RubyPart.None, sectionIdx, ref numSubSections); } InsertRubySection(line, section, match.Groups["text"].Value, RubyPart.Text, sectionIdx, ref numSubSections); InsertRubySection(line, section, "(", RubyPart.Parenthesis, sectionIdx, ref numSubSections); InsertRubySection(line, section, match.Groups["ruby"].Value, section.RubyPosition == RubyPosition.Below ? RubyPart.RubyBelow : RubyPart.RubyAbove, sectionIdx, ref numSubSections); InsertRubySection(line, section, ")", RubyPart.Parenthesis, sectionIdx, ref numSubSections); interStartPos = match.Index + match.Length; } if (interStartPos < section.Text.Length) { InsertRubySection(line, section, section.Text.Substring(interStartPos), RubyPart.None, sectionIdx, ref numSubSections); } ((AssSection)line.Sections[sectionIdx]).Duration = section.Duration; } }
private static void ApplySimpleKaraokeEffect(AssSection singingSection) { if (!singingSection.CurrentWordForeColor.IsEmpty) { singingSection.ForeColor = singingSection.CurrentWordForeColor; } if (!singingSection.CurrentWordShadowColor.IsEmpty) { foreach (ShadowType shadowType in singingSection.ShadowColors.Keys.ToList()) { singingSection.ShadowColors[shadowType] = singingSection.CurrentWordShadowColor; } } if (!singingSection.CurrentWordOutlineColor.IsEmpty && singingSection.ShadowColors.ContainsKey(ShadowType.Glow)) { singingSection.ShadowColors[ShadowType.Glow] = singingSection.CurrentWordOutlineColor; } }
private static void AddKaraokeEffects(AssLine originalLine, AssLine stepLine, SortedList <TimeSpan, int> activeSectionsPerStep, int stepIdx) { int numActiveSections = activeSectionsPerStep.Values[stepIdx]; AssSection singingSection = (AssSection)stepLine.Sections[numActiveSections - 1]; switch (stepLine.KaraokeType) { case KaraokeType.Simple: ApplySimpleKaraokeEffect(singingSection); break; case KaraokeType.Fade: ApplyFadeKaraokeEffect(originalLine, stepLine, activeSectionsPerStep, stepIdx); break; case KaraokeType.Glitch: ApplyGlitchKaraokeEffect(stepLine, singingSection); break; } }
private static void MoveLineBreaksToSeparateSections(AssLine line) { for (int sectionIdx = line.Sections.Count - 1; sectionIdx >= 0; sectionIdx--) { AssSection section = (AssSection)line.Sections[sectionIdx]; Match match = Regex.Match(section.Text, @"((?:\r\n)+|[^\r\n]+)+"); if (!match.Success || match.Groups[1].Captures.Count == 1) { continue; } line.Sections.RemoveAt(sectionIdx); for (int i = 0; i < match.Groups[1].Captures.Count; i++) { AssSection subSection = (AssSection)section.Clone(); subSection.Text = match.Groups[1].Captures[i].Value; line.Sections.Insert(sectionIdx + i, subSection); } } }
// Aegisub's background boxes can have padding just like on YouTube, and the horizontal and vertical padding // can even be different. The big downside of this feature, however, is that background boxes of adjacent // sections overlap each other which looks like a mess. Therefore we don't use it at all (all the styles // have an outline thickness of 0.01) and instead add a space at the start and end of each line of text // to emulate the YouTube horizontal padding manually. private static void EmulateBorders(AssLine line) { for (int i = 0; i < line.Sections.Count; i++) { AssSection section = (AssSection)line.Sections[i]; if (Regex.IsMatch(section.Text, @"^[\r\n]+$")) { continue; } if (i == 0 || line.Sections[i - 1].Text.EndsWith("\r\n")) { section.Text = " " + section.Text; } if (i == line.Sections.Count - 1 || line.Sections[i + 1].Text.StartsWith("\r\n")) { section.Text = section.Text + " "; } } }
private static void ApplyFadeOutKaraokeEffect(AssLine originalLine, AssLine stepLine, SortedList <TimeSpan, int> activeSectionsPerStep, int stepIdx) { int stepFirstSectionIdx = 0; for (int prevStepIdx = 0; prevStepIdx < stepIdx; prevStepIdx++) { DateTime fadeStartTime = TimeUtil.SnapTimeToFrame((originalLine.Start + activeSectionsPerStep.Keys[prevStepIdx + 1]).AddMilliseconds(20)); DateTime fadeEndTime = fadeStartTime.AddMilliseconds(1000); int stepLastSectionIdx = activeSectionsPerStep.Values[prevStepIdx] - 1; for (int sectionIdx = stepFirstSectionIdx; sectionIdx <= stepLastSectionIdx; sectionIdx++) { AssSection section = (AssSection)stepLine.Sections[sectionIdx]; if (!section.CurrentWordForeColor.IsEmpty && section.CurrentWordForeColor != section.ForeColor) { section.Animations.Add(new ForeColorAnimation(fadeStartTime, section.CurrentWordForeColor, fadeEndTime, section.ForeColor)); } if (!section.CurrentWordShadowColor.IsEmpty) { foreach (KeyValuePair <ShadowType, Color> shadowColor in section.ShadowColors) { if (section.CurrentWordShadowColor != shadowColor.Value) { section.Animations.Add(new ShadowColorAnimation(shadowColor.Key, fadeStartTime, section.CurrentWordShadowColor, fadeEndTime, shadowColor.Value)); } } } if (!section.CurrentWordOutlineColor.IsEmpty && section.CurrentWordOutlineColor != section.ShadowColors.GetOrDefault(ShadowType.Glow)) { section.Animations.Add(new ShadowColorAnimation(ShadowType.Glow, fadeStartTime, section.CurrentWordOutlineColor, fadeEndTime, section.ShadowColors[ShadowType.Glow])); } } stepFirstSectionIdx = stepLastSectionIdx + 1; } }
private static void ApplyFadeInKaraokeEffect(AssLine stepLine, AssSection singingSection) { DateTime fadeEndTime = TimeUtil.Min(stepLine.Start.AddMilliseconds(500), stepLine.End); if (singingSection.CurrentWordForeColor.IsEmpty) { if (singingSection.ForeColor != singingSection.SecondaryColor) { singingSection.Animations.Add(new ForeColorAnimation(stepLine.Start, singingSection.SecondaryColor, fadeEndTime, singingSection.ForeColor)); } } else { if (singingSection.CurrentWordForeColor != singingSection.SecondaryColor) { singingSection.Animations.Add(new ForeColorAnimation(stepLine.Start, singingSection.SecondaryColor, fadeEndTime, singingSection.CurrentWordForeColor)); } } if (!singingSection.CurrentWordShadowColor.IsEmpty) { foreach (KeyValuePair <ShadowType, Color> shadowColor in singingSection.ShadowColors) { if (singingSection.CurrentWordShadowColor != shadowColor.Value) { singingSection.Animations.Add(new ShadowColorAnimation(shadowColor.Key, stepLine.Start, shadowColor.Value, fadeEndTime, singingSection.CurrentWordShadowColor)); } } } if (!singingSection.CurrentWordOutlineColor.IsEmpty && singingSection.CurrentWordOutlineColor != singingSection.ShadowColors.GetOrDefault(ShadowType.Glow)) { singingSection.Animations.Add(new ShadowColorAnimation( ShadowType.Glow, stepLine.Start, singingSection.ShadowColors[ShadowType.Glow], fadeEndTime, singingSection.CurrentWordOutlineColor)); } }
private void AppendSectionTags(AssSection section, AssSection prevSection, AssLineContentBuilder lineContent) { if (section.Font != prevSection.Font) { lineContent.AppendTag("fn", section.Font); } float prevLineHeight = ScaleToLineHeight(prevSection.Font, prevSection.Scale); float lineHeight = ScaleToLineHeight(section.Font, section.Scale); if (lineHeight != prevLineHeight) { lineContent.AppendTag("fs", lineHeight); } if (section.Bold != prevSection.Bold) { lineContent.AppendTag("b", section.Bold); } if (section.Italic != prevSection.Italic) { lineContent.AppendTag("i", section.Italic); } if (section.Underline != prevSection.Underline) { lineContent.AppendTag("u", section.Underline); } AppendColorTags("c", "1a", prevSection.ForeColor, section.ForeColor, lineContent); AppendColorTags("2c", "2a", prevSection.SecondaryColor, section.SecondaryColor, lineContent); if (section.BackColor.A > 0) { AppendColorTags("3c", "3a", prevSection.BackColor, section.BackColor, lineContent); if (prevSection.ShadowColors.Count == 1 && section.ShadowColors.Count == 1) { AppendColorTags("4c", "4a", prevSection.ShadowColors.Values.First(), section.ShadowColors.Values.First(), lineContent); } } else if (prevSection.ShadowColors.Count == 1 && section.ShadowColors.Count == 1) { if (section.ShadowColors.Keys.First() == ShadowType.Glow) { AppendColorTags("3c", "3a", prevSection.ShadowColors.Values.First(), section.ShadowColors.Values.First(), lineContent); } else { AppendColorTags("4c", "4a", prevSection.ShadowColors.Values.First(), section.ShadowColors.Values.First(), lineContent); } } if (section.Offset != prevSection.Offset) { lineContent.AppendTag( section.Offset switch { OffsetType.Subscript => "ytsub", OffsetType.Superscript => "ytsup", OffsetType.Regular => "ytsur", }
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)); }