mirror of
https://github.com/SubtitleEdit/subtitleedit.git
synced 2024-11-22 03:02:35 +01:00
Merge pull request #8100 from ivandrofly/feature/karaoke
Fixes #6234 #7947 #7909 Add karaoke effect for text transformation
This commit is contained in:
commit
499bc16b4d
@ -1,357 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Nikse.SubtitleEdit.Core.Common
|
||||
{
|
||||
public class EffectAnimationPart
|
||||
{
|
||||
public EffectAnimationPart()
|
||||
{
|
||||
Pre = string.Empty;
|
||||
Text = string.Empty;
|
||||
Post = string.Empty;
|
||||
}
|
||||
public string Pre { get; set; }
|
||||
public string Text { get; set; }
|
||||
public string Post { get; set; }
|
||||
public Color Color { get; set; }
|
||||
public string Face { get; set; }
|
||||
public string Size { get; set; }
|
||||
|
||||
public static Color NullColor => Color.FromArgb(0, 254, 1, 198);
|
||||
|
||||
public static string ToString(List<EffectAnimationPart> parts)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var part in parts)
|
||||
{
|
||||
sb.Append(part.Pre);
|
||||
sb.Append(part.Text);
|
||||
sb.Append(part.Post);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static Color GetColorFromFontString(string text, Color defaultColor)
|
||||
{
|
||||
var s = text.TrimEnd();
|
||||
var start = s.IndexOf("<font ", StringComparison.OrdinalIgnoreCase);
|
||||
if (start >= 0 && s.EndsWith("</font>", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
int end = s.IndexOf('>', start);
|
||||
if (end > 0)
|
||||
{
|
||||
string f = s.Substring(start, end - start);
|
||||
if (f.Contains(" color="))
|
||||
{
|
||||
int colorStart = f.IndexOf(" color=", StringComparison.OrdinalIgnoreCase);
|
||||
if (s.IndexOf('"', colorStart + " color=".Length + 1) > 0)
|
||||
{
|
||||
end = s.IndexOf('"', colorStart + " color=".Length + 1);
|
||||
}
|
||||
|
||||
s = s.Substring(colorStart, end - colorStart);
|
||||
s = s.Replace(" color=", string.Empty);
|
||||
s = s.Trim('\'').Trim('"').Trim('\'');
|
||||
try
|
||||
{
|
||||
if (s.StartsWith("rgb(", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var arr = s.Remove(0, 4).TrimEnd(')').Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
return Color.FromArgb(int.Parse(arr[0]), int.Parse(arr[1]), int.Parse(arr[2]));
|
||||
}
|
||||
|
||||
return HtmlUtil.GetColorFromString(s);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
public static string GetAttributeFromFontString(string text, string attributeName)
|
||||
{
|
||||
var s = text.TrimEnd();
|
||||
var start = s.IndexOf("<font ", StringComparison.OrdinalIgnoreCase);
|
||||
if (start >= 0)
|
||||
{
|
||||
int end = s.IndexOf('>', start);
|
||||
if (end > 0)
|
||||
{
|
||||
var f = s.Substring(start, end - start);
|
||||
var tag = $" {attributeName}=";
|
||||
if (f.Contains(tag))
|
||||
{
|
||||
int colorStart = f.IndexOf(tag, StringComparison.OrdinalIgnoreCase);
|
||||
if (s.IndexOf('"', colorStart + tag.Length + 1) > 0)
|
||||
{
|
||||
end = s.IndexOf('"', colorStart + tag.Length + 1);
|
||||
}
|
||||
|
||||
s = s.Substring(colorStart, end - colorStart);
|
||||
s = s.Replace(tag, string.Empty);
|
||||
s = s.Trim('\'').Trim('"').Trim('\'');
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static List<EffectAnimationPart> MakeBase(string text)
|
||||
{
|
||||
var i = 0;
|
||||
var allowedStartTags = new List<string> { "<i>", "<u>", "<b>", "<font ", "<span " };
|
||||
var allowedEndTags = new List<string> { "</i>", "</u>", "</b>", "</font>", "</span>" };
|
||||
var list = new List<EffectAnimationPart>();
|
||||
EffectAnimationPart p = null;
|
||||
var color = NullColor;
|
||||
var face = string.Empty;
|
||||
var size = string.Empty;
|
||||
var fontStack = new Stack<EffectAnimationPart>();
|
||||
while (i < text.Length)
|
||||
{
|
||||
var c = text[i];
|
||||
if (c == '{' && text.Substring(i).StartsWith("{\\") && text.IndexOf('}', i) > 0)
|
||||
{
|
||||
var endIndex = text.IndexOf('}', i);
|
||||
var tag = text.Substring(i, endIndex - i + 1);
|
||||
i += tag.Length;
|
||||
|
||||
if (p == null)
|
||||
{
|
||||
p = new EffectAnimationPart { Color = color, Face = face, Size = size };
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(p.Text))
|
||||
{
|
||||
list.Add(p);
|
||||
p = new EffectAnimationPart { Color = color, Face = face, Size = size };
|
||||
}
|
||||
|
||||
p.Pre += tag;
|
||||
}
|
||||
else if (c == '<' && allowedStartTags.Any(a => text.Substring(i).StartsWith(a, StringComparison.OrdinalIgnoreCase)) && text.IndexOf('>', i) > 0)
|
||||
{
|
||||
var endIndex = text.IndexOf('>', i);
|
||||
var tag = text.Substring(i, endIndex - i + 1);
|
||||
i += tag.Length;
|
||||
|
||||
if (p == null)
|
||||
{
|
||||
p = new EffectAnimationPart { Color = color, Face = face, Size = size };
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(p.Text))
|
||||
{
|
||||
list.Add(p);
|
||||
p = new EffectAnimationPart { Color = color, Face = face, Size = size };
|
||||
}
|
||||
|
||||
if (tag.StartsWith("<font", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
fontStack.Push(new EffectAnimationPart { Color = color, Face = face, Size = size });
|
||||
|
||||
// get color
|
||||
var tagColor = GetColorFromFontString(tag + "a</font>", NullColor);
|
||||
if (tagColor != NullColor)
|
||||
{
|
||||
p.Color = tagColor;
|
||||
color = tagColor;
|
||||
}
|
||||
|
||||
// get font name (face)
|
||||
var fontName = GetAttributeFromFontString(tag, "face");
|
||||
if (!string.IsNullOrEmpty(fontName))
|
||||
{
|
||||
p.Face = fontName;
|
||||
face = fontName;
|
||||
}
|
||||
|
||||
tag = string.Empty;
|
||||
}
|
||||
|
||||
p.Pre += tag;
|
||||
}
|
||||
else if (c == '<' && allowedEndTags.Any(a => text.Substring(i).StartsWith(a, StringComparison.OrdinalIgnoreCase)) && text.IndexOf('>', i) > 0)
|
||||
{
|
||||
var endIndex = text.IndexOf('>', i);
|
||||
var tag = text.Substring(i, endIndex - i + 1);
|
||||
i += tag.Length;
|
||||
|
||||
if (p == null)
|
||||
{
|
||||
p = new EffectAnimationPart { Color = color, Face = face, Size = size };
|
||||
}
|
||||
|
||||
// set color
|
||||
if (tag == "</font>")
|
||||
{
|
||||
if (fontStack.Count > 0)
|
||||
{
|
||||
var f = fontStack.Pop();
|
||||
color = f.Color;
|
||||
face = f.Face;
|
||||
size = f.Size;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = NullColor;
|
||||
face = string.Empty;
|
||||
size = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
tag = string.Empty;
|
||||
p.Post += tag;
|
||||
|
||||
}
|
||||
else if (c == '\r')
|
||||
{
|
||||
if (p == null)
|
||||
{
|
||||
p = new EffectAnimationPart { Color = color, Face = face, Size = size };
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(p.Text))
|
||||
{
|
||||
list.Add(p);
|
||||
p = new EffectAnimationPart { Color = color, Face = face, Size = size };
|
||||
}
|
||||
|
||||
p.Text = c.ToString();
|
||||
i++;
|
||||
|
||||
if (i < text.Length && text[i] == '\n') // only take one part for \r\n
|
||||
{
|
||||
p.Text += '\n';
|
||||
i++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (p == null)
|
||||
{
|
||||
p = new EffectAnimationPart { Color = color, Face = face, Size = size };
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(p.Text))
|
||||
{
|
||||
list.Add(p);
|
||||
p = new EffectAnimationPart { Color = color, Face = face, Size = size };
|
||||
}
|
||||
|
||||
p.Text = c.ToString();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (p != null && !string.IsNullOrEmpty(p.Pre + p.Text + p.Post))
|
||||
{
|
||||
list.Add(p);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static List<EffectAnimationPart> MakeEffectKaraoke(List<EffectAnimationPart> parts, Color animationColor, int inputLast)
|
||||
{
|
||||
if (parts == null || parts.Count == 0)
|
||||
{
|
||||
return new List<EffectAnimationPart>();
|
||||
}
|
||||
|
||||
var last = inputLast;
|
||||
if (last > parts.Count)
|
||||
{
|
||||
last = parts.Count - 1;
|
||||
}
|
||||
|
||||
// add karaoke color
|
||||
var list = new List<EffectAnimationPart>();
|
||||
for (var index = 0; index < parts.Count; index++)
|
||||
{
|
||||
var part = parts[index];
|
||||
list.Add(new EffectAnimationPart
|
||||
{
|
||||
Pre = part.Pre,
|
||||
Text = part.Text,
|
||||
Post = part.Post,
|
||||
Color = index <= last ? animationColor : part.Color,
|
||||
Face = part.Face,
|
||||
Size = part.Size,
|
||||
});
|
||||
}
|
||||
|
||||
var startTagsCount = 0;
|
||||
var endTagsCount = 0;
|
||||
for (var index = 0; index < list.Count - 1; index++)
|
||||
{
|
||||
var part = list[index];
|
||||
var next = list[index + 1];
|
||||
|
||||
if (index == 0)
|
||||
{
|
||||
part.Pre += MakeFontTag(part, ref startTagsCount);
|
||||
if (next.Color != part.Color || next.Face != part.Face || next.Size != part.Size)
|
||||
{
|
||||
part.Post = "</font>" + part.Post;
|
||||
endTagsCount++;
|
||||
next.Pre += MakeFontTag(next, ref startTagsCount);
|
||||
}
|
||||
}
|
||||
else if (next.Color != part.Color || next.Face != part.Face || next.Size != part.Size)
|
||||
{
|
||||
part.Post = "</font>" + part.Post;
|
||||
endTagsCount++;
|
||||
next.Pre += MakeFontTag(next, ref startTagsCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (startTagsCount > endTagsCount)
|
||||
{
|
||||
list.Last().Post += "</font>";
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static string MakeFontTag(EffectAnimationPart part, ref int startTagsCount)
|
||||
{
|
||||
var attributes = string.Empty;
|
||||
if (part.Color != NullColor)
|
||||
{
|
||||
attributes = $" color=\"{ToHtml(part.Color)}\"";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(part.Face))
|
||||
{
|
||||
attributes += $" face=\"{part.Face}\"";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(part.Size))
|
||||
{
|
||||
attributes += $" size=\"{part.Size}\"";
|
||||
}
|
||||
|
||||
var result = $"<font{attributes.TrimEnd()}>".Replace("<font>", string.Empty);
|
||||
|
||||
if (result.Length > 0)
|
||||
{
|
||||
startTagsCount++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string ToHtml(Color c)
|
||||
{
|
||||
return $"#{c.A:X2}{c.R:X2}{c.G:X2}{c.B:X2}";
|
||||
}
|
||||
}
|
||||
}
|
@ -1401,5 +1401,22 @@ namespace Nikse.SubtitleEdit.Core.Common
|
||||
text = Regex.Replace(text, "\\\\1c&[abcdefghABCDEFGH\\d]*&", string.Empty);
|
||||
return text;
|
||||
}
|
||||
|
||||
public static string GetClosingPair(string tag)
|
||||
{
|
||||
switch (tag)
|
||||
{
|
||||
case "<i>" : return "</i>";
|
||||
case "<b>" : return "</b>";
|
||||
case "<u>" : return "</u>";
|
||||
}
|
||||
return tag.StartsWith("<font ", StringComparison.Ordinal) ? "</font>" : string.Empty;
|
||||
}
|
||||
|
||||
public static char GetClosingPair(char ch) => ch == '<' ? '>' : '}';
|
||||
|
||||
public static bool IsOpenTag(string tag) => tag.Length > 1 && tag[1] != '/';
|
||||
|
||||
public static bool IsStartTagSymbol(char ch) => ch == '<' || ch == '{';
|
||||
}
|
||||
}
|
||||
|
40
src/libse/Common/TextEffect/KaraokeCharTransform.cs
Normal file
40
src/libse/Common/TextEffect/KaraokeCharTransform.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nikse.SubtitleEdit.Core.Common.TextEffect
|
||||
{
|
||||
public class KaraokeCharTransform : TypeWriterEffect
|
||||
{
|
||||
public override string[] Transform(string text)
|
||||
{
|
||||
var result = new List<string>();
|
||||
var len = text.Length;
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var ch = text[i];
|
||||
// skip already existing tag
|
||||
if (HtmlUtil.IsStartTagSymbol(ch))
|
||||
{
|
||||
var closingIdx = text.IndexOf(HtmlUtil.GetClosingPair(ch), i + 1);
|
||||
// closing not present, treat it as normal char
|
||||
if (closingIdx < 0)
|
||||
{
|
||||
result.Add(GetTextWithClosedFontTag(text, i));
|
||||
}
|
||||
else
|
||||
{
|
||||
i = closingIdx;
|
||||
}
|
||||
}
|
||||
else if (IsVisibleChar(ch))
|
||||
{
|
||||
result.Add(GetTextWithClosedFontTag(text, i));
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private readonly string _fontClose = "</font>";
|
||||
private string GetTextWithClosedFontTag(in string text, int index) => text.Substring(0, index + 1) + _fontClose + text.Substring(index + 1);
|
||||
}
|
||||
}
|
76
src/libse/Common/TextEffect/KaraokeEffect.cs
Normal file
76
src/libse/Common/TextEffect/KaraokeEffect.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Nikse.SubtitleEdit.Core.Common.TextEffect
|
||||
{
|
||||
public class KaraokeEffect
|
||||
{
|
||||
private readonly TextEffectBase _splitStrategy;
|
||||
|
||||
public KaraokeEffect(TextEffectBase splitStrategy) => _splitStrategy = splitStrategy;
|
||||
|
||||
public IEnumerable<Paragraph> Transform(Paragraph paragraph, Color color, double delay)
|
||||
{
|
||||
// remove any coloring tag already in text
|
||||
var text = HtmlUtil.RemoveColorTags(paragraph.Text);
|
||||
// calculate index where the first coloring will be inserted
|
||||
var fontInsertIndex = CalculateColorInsertIndex(text);
|
||||
text = text.Insert(fontInsertIndex, GetColor(color));
|
||||
var result = _splitStrategy.Transform(text);
|
||||
var duration = paragraph.DurationTotalMilliseconds - delay;
|
||||
var durationPerSentence = duration / result.Length;
|
||||
var baseStartTime = paragraph.StartTime.TotalMilliseconds;
|
||||
|
||||
// the gaps must be 0 to avoid flickering
|
||||
const double gapBetweenSentences = 0;
|
||||
|
||||
var animations = new List<Paragraph>();
|
||||
for (var i = 0; i < result.Length; i++)
|
||||
{
|
||||
var startTime = new TimeCode(baseStartTime + i * durationPerSentence);
|
||||
var endTime = new TimeCode(baseStartTime + (i + 1) * durationPerSentence - gapBetweenSentences);
|
||||
animations.Add(new Paragraph(startTime, endTime, result[i]));
|
||||
}
|
||||
|
||||
// fixes precision/fraction lost from division
|
||||
if (animations.Count > 0)
|
||||
{
|
||||
animations[animations.Count - 1].EndTime.TotalMilliseconds = paragraph.EndTime.TotalMilliseconds;
|
||||
}
|
||||
|
||||
return animations;
|
||||
}
|
||||
|
||||
private int CalculateColorInsertIndex(in string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var len = text.Length;
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var ch = text[i];
|
||||
if (HtmlUtil.IsStartTagSymbol(ch))
|
||||
{
|
||||
var closeIdx = text.IndexOf(HtmlUtil.GetClosingPair(ch), i + 1);
|
||||
if (closeIdx < 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
i = closeIdx;
|
||||
}
|
||||
else
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private string GetColor(Color color) => $"<font color=\"{Utilities.ColorToHex(color)}\">";
|
||||
}
|
||||
}
|
50
src/libse/Common/TextEffect/KaraokeWordTransform.cs
Normal file
50
src/libse/Common/TextEffect/KaraokeWordTransform.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nikse.SubtitleEdit.Core.Common.TextEffect
|
||||
{
|
||||
public class KaraokeWordTransform : TypeWriterEffect
|
||||
{
|
||||
public override string[] Transform(string text)
|
||||
{
|
||||
// <font>foobar foo bar <i>zz</i>
|
||||
var result = new List<string>();
|
||||
var len = text.Length;
|
||||
|
||||
var lastFontCloseIdx = len;
|
||||
const string fontClose = "</font>";
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var ch = text[i];
|
||||
if (HtmlUtil.IsStartTagSymbol(ch))
|
||||
{
|
||||
// skip entire tag or just the char where we are current
|
||||
i = Math.Max(i, text.IndexOf(HtmlUtil.GetClosingPair(ch), i + 1));
|
||||
}
|
||||
else if (char.IsWhiteSpace(ch))
|
||||
{
|
||||
var karaoke = text.Substring(0, i) + fontClose + text.Substring(i);
|
||||
lastFontCloseIdx = i;
|
||||
result.Add(karaoke);
|
||||
// skip only whitespace
|
||||
while (i < len && char.IsWhiteSpace(text[i]))
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert last sentence
|
||||
var lastInsertIndex = CalculateLastFontInsertIndex(text, lastFontCloseIdx);
|
||||
result.Add(text.Substring(0, lastInsertIndex) + fontClose + text.Substring(lastInsertIndex));
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private static int CalculateLastFontInsertIndex(in string text, int lastFontCloseIdx)
|
||||
{
|
||||
var nearestCloseIndex = text.IndexOf("</", lastFontCloseIdx, StringComparison.Ordinal);
|
||||
return nearestCloseIndex < 0 ? text.Length : nearestCloseIndex;
|
||||
}
|
||||
}
|
||||
}
|
16
src/libse/Common/TextEffect/TextEffectBase.cs
Normal file
16
src/libse/Common/TextEffect/TextEffectBase.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace Nikse.SubtitleEdit.Core.Common.TextEffect
|
||||
{
|
||||
public abstract class TextEffectBase
|
||||
{
|
||||
public abstract string[] Transform(string text);
|
||||
|
||||
// ReSharper disable once IdentifierTypo
|
||||
protected bool IsVisibleChar(char ch)
|
||||
{
|
||||
var uc = char.GetUnicodeCategory(ch);
|
||||
return uc != UnicodeCategory.Control && uc != UnicodeCategory.SpaceSeparator;
|
||||
}
|
||||
}
|
||||
}
|
69
src/libse/Common/TextEffect/TypeWriterEffect.cs
Normal file
69
src/libse/Common/TextEffect/TypeWriterEffect.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Nikse.SubtitleEdit.Core.Common.TextEffect
|
||||
{
|
||||
public class TypeWriterEffect : TextEffectBase
|
||||
{
|
||||
private readonly StringBuilder _sb = new StringBuilder();
|
||||
private readonly StringBuilder _sbClosing = new StringBuilder();
|
||||
|
||||
public override string[] Transform(string text)
|
||||
{
|
||||
var closingTags = new List<string>(text.Length / 3 + 1);
|
||||
var list = new List<string>();
|
||||
|
||||
for (var i = 0; i < text.Length; i++)
|
||||
{
|
||||
var ch = text[i];
|
||||
if (HtmlUtil.IsStartTagSymbol(ch))
|
||||
{
|
||||
var closingIdx = text.IndexOf(HtmlUtil.GetClosingPair(ch), i + 1);
|
||||
// invalid tag (no closing)
|
||||
if (closingIdx < 0)
|
||||
{
|
||||
_sb.Append(ch); // < or {
|
||||
}
|
||||
else
|
||||
{
|
||||
var tag = text.Substring(i, closingIdx - i + 1);
|
||||
// ass tag are always included, this logic is for html formatting tags
|
||||
if (ch == '<')
|
||||
{
|
||||
if (HtmlUtil.IsOpenTag(tag))
|
||||
{
|
||||
closingTags.Add(HtmlUtil.GetClosingPair(tag));
|
||||
}
|
||||
else
|
||||
{
|
||||
closingTags.Remove(tag);
|
||||
}
|
||||
}
|
||||
|
||||
_sb.Append(tag);
|
||||
i = closingIdx;
|
||||
}
|
||||
}
|
||||
else // anything else
|
||||
{
|
||||
_sb.Append(ch);
|
||||
|
||||
// always have char like white space to be followed by a visible character
|
||||
if (IsVisibleChar(ch))
|
||||
{
|
||||
_sbClosing.Clear();
|
||||
// write closing for all open tag
|
||||
for (var j = closingTags.Count - 1; j >= 0; j--)
|
||||
{
|
||||
_sbClosing.Append(closingTags[j]);
|
||||
}
|
||||
|
||||
list.Add(_sb + _sbClosing.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
239
src/ui/Forms/EffectKaraoke.Designer.cs
generated
239
src/ui/Forms/EffectKaraoke.Designer.cs
generated
@ -31,7 +31,6 @@
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.labelTotalMilliseconds = new System.Windows.Forms.Label();
|
||||
this.labelTM = new System.Windows.Forms.Label();
|
||||
this.numericUpDownDelay = new Nikse.SubtitleEdit.Controls.NikseUpDown();
|
||||
this.labelColor = new System.Windows.Forms.Label();
|
||||
this.labelEndDelay = new System.Windows.Forms.Label();
|
||||
this.buttonCancel = new System.Windows.Forms.Button();
|
||||
@ -42,6 +41,9 @@
|
||||
this.buttonPreview = new System.Windows.Forms.Button();
|
||||
this.timer1 = new System.Windows.Forms.Timer(this.components);
|
||||
this.richTextBoxPreview = new System.Windows.Forms.RichTextBox();
|
||||
this.radioButtonByWordEffect = new System.Windows.Forms.RadioButton();
|
||||
this.radioButtonByCharEffect = new System.Windows.Forms.RadioButton();
|
||||
this.numericUpDownDelay = new Nikse.SubtitleEdit.Controls.NikseUpDown();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// labelTotalMilliseconds
|
||||
@ -50,7 +52,7 @@
|
||||
this.labelTotalMilliseconds.Location = new System.Drawing.Point(169, 52);
|
||||
this.labelTotalMilliseconds.Name = "labelTotalMilliseconds";
|
||||
this.labelTotalMilliseconds.Size = new System.Drawing.Size(108, 13);
|
||||
this.labelTotalMilliseconds.TabIndex = 49;
|
||||
this.labelTotalMilliseconds.TabIndex = 5;
|
||||
this.labelTotalMilliseconds.Text = "labelTotalMilliseconds";
|
||||
//
|
||||
// labelTM
|
||||
@ -58,10 +60,135 @@
|
||||
this.labelTM.Location = new System.Drawing.Point(3, 52);
|
||||
this.labelTM.Name = "labelTM";
|
||||
this.labelTM.Size = new System.Drawing.Size(163, 13);
|
||||
this.labelTM.TabIndex = 48;
|
||||
this.labelTM.TabIndex = 4;
|
||||
this.labelTM.Text = "Total milliseconds:";
|
||||
this.labelTM.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
//
|
||||
// labelColor
|
||||
//
|
||||
this.labelColor.AutoSize = true;
|
||||
this.labelColor.Location = new System.Drawing.Point(228, 22);
|
||||
this.labelColor.Name = "labelColor";
|
||||
this.labelColor.Size = new System.Drawing.Size(32, 13);
|
||||
this.labelColor.TabIndex = 3;
|
||||
this.labelColor.Text = "Color";
|
||||
//
|
||||
// labelEndDelay
|
||||
//
|
||||
this.labelEndDelay.Location = new System.Drawing.Point(3, 76);
|
||||
this.labelEndDelay.Name = "labelEndDelay";
|
||||
this.labelEndDelay.Size = new System.Drawing.Size(163, 17);
|
||||
this.labelEndDelay.TabIndex = 6;
|
||||
this.labelEndDelay.Text = "End delay in milliseconds:";
|
||||
this.labelEndDelay.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
//
|
||||
// buttonCancel
|
||||
//
|
||||
this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.buttonCancel.BackColor = System.Drawing.SystemColors.Control;
|
||||
this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.buttonCancel.Location = new System.Drawing.Point(421, 252);
|
||||
this.buttonCancel.Name = "buttonCancel";
|
||||
this.buttonCancel.Size = new System.Drawing.Size(75, 23);
|
||||
this.buttonCancel.TabIndex = 13;
|
||||
this.buttonCancel.Text = "C&ancel";
|
||||
this.buttonCancel.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// buttonOK
|
||||
//
|
||||
this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.buttonOK.Location = new System.Drawing.Point(340, 252);
|
||||
this.buttonOK.Name = "buttonOK";
|
||||
this.buttonOK.Size = new System.Drawing.Size(75, 23);
|
||||
this.buttonOK.TabIndex = 12;
|
||||
this.buttonOK.Text = "OK";
|
||||
this.buttonOK.UseVisualStyleBackColor = true;
|
||||
this.buttonOK.Click += new System.EventHandler(this.ButtonOkClick);
|
||||
//
|
||||
// buttonChooseColor
|
||||
//
|
||||
this.buttonChooseColor.Location = new System.Drawing.Point(169, 17);
|
||||
this.buttonChooseColor.Name = "buttonChooseColor";
|
||||
this.buttonChooseColor.Size = new System.Drawing.Size(27, 23);
|
||||
this.buttonChooseColor.TabIndex = 1;
|
||||
this.buttonChooseColor.Text = "...";
|
||||
this.buttonChooseColor.UseVisualStyleBackColor = true;
|
||||
this.buttonChooseColor.Click += new System.EventHandler(this.ButtonChooseColorClick);
|
||||
//
|
||||
// panelColor
|
||||
//
|
||||
this.panelColor.Location = new System.Drawing.Point(202, 19);
|
||||
this.panelColor.Name = "panelColor";
|
||||
this.panelColor.Size = new System.Drawing.Size(20, 20);
|
||||
this.panelColor.TabIndex = 2;
|
||||
this.panelColor.TabStop = true;
|
||||
//
|
||||
// labelChooseColor
|
||||
//
|
||||
this.labelChooseColor.Location = new System.Drawing.Point(0, 22);
|
||||
this.labelChooseColor.Name = "labelChooseColor";
|
||||
this.labelChooseColor.Size = new System.Drawing.Size(166, 13);
|
||||
this.labelChooseColor.TabIndex = 0;
|
||||
this.labelChooseColor.Text = "Choose color:";
|
||||
this.labelChooseColor.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
//
|
||||
// buttonPreview
|
||||
//
|
||||
this.buttonPreview.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.buttonPreview.Location = new System.Drawing.Point(12, 233);
|
||||
this.buttonPreview.Name = "buttonPreview";
|
||||
this.buttonPreview.Size = new System.Drawing.Size(100, 23);
|
||||
this.buttonPreview.TabIndex = 11;
|
||||
this.buttonPreview.Text = "Preview";
|
||||
this.buttonPreview.UseVisualStyleBackColor = true;
|
||||
this.buttonPreview.Click += new System.EventHandler(this.ButtonPreviewClick);
|
||||
//
|
||||
// timer1
|
||||
//
|
||||
this.timer1.Tick += new System.EventHandler(this.Timer1Tick);
|
||||
//
|
||||
// richTextBoxPreview
|
||||
//
|
||||
this.richTextBoxPreview.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.richTextBoxPreview.BackColor = System.Drawing.Color.Black;
|
||||
this.richTextBoxPreview.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
||||
this.richTextBoxPreview.DetectUrls = false;
|
||||
this.richTextBoxPreview.Font = new System.Drawing.Font("Tahoma", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.richTextBoxPreview.ForeColor = System.Drawing.Color.White;
|
||||
this.richTextBoxPreview.Location = new System.Drawing.Point(13, 99);
|
||||
this.richTextBoxPreview.Name = "richTextBoxPreview";
|
||||
this.richTextBoxPreview.ReadOnly = true;
|
||||
this.richTextBoxPreview.Size = new System.Drawing.Size(483, 128);
|
||||
this.richTextBoxPreview.TabIndex = 10;
|
||||
this.richTextBoxPreview.Text = "";
|
||||
//
|
||||
// radioButtonByWordEffect
|
||||
//
|
||||
this.radioButtonByWordEffect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.radioButtonByWordEffect.AutoSize = true;
|
||||
this.radioButtonByWordEffect.Checked = true;
|
||||
this.radioButtonByWordEffect.Location = new System.Drawing.Point(375, 52);
|
||||
this.radioButtonByWordEffect.Name = "radioButtonByWordEffect";
|
||||
this.radioButtonByWordEffect.Size = new System.Drawing.Size(83, 17);
|
||||
this.radioButtonByWordEffect.TabIndex = 8;
|
||||
this.radioButtonByWordEffect.TabStop = true;
|
||||
this.radioButtonByWordEffect.Text = "Word effect";
|
||||
this.radioButtonByWordEffect.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// radioButtonByCharEffect
|
||||
//
|
||||
this.radioButtonByCharEffect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.radioButtonByCharEffect.AutoSize = true;
|
||||
this.radioButtonByCharEffect.Location = new System.Drawing.Point(375, 72);
|
||||
this.radioButtonByCharEffect.Name = "radioButtonByCharEffect";
|
||||
this.radioButtonByCharEffect.Size = new System.Drawing.Size(105, 17);
|
||||
this.radioButtonByCharEffect.TabIndex = 9;
|
||||
this.radioButtonByCharEffect.TabStop = true;
|
||||
this.radioButtonByCharEffect.Text = "Character effect";
|
||||
this.radioButtonByCharEffect.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// numericUpDownDelay
|
||||
//
|
||||
this.numericUpDownDelay.BackColor = System.Drawing.SystemColors.Window;
|
||||
@ -90,7 +217,7 @@
|
||||
-2147483648});
|
||||
this.numericUpDownDelay.Name = "numericUpDownDelay";
|
||||
this.numericUpDownDelay.Size = new System.Drawing.Size(54, 23);
|
||||
this.numericUpDownDelay.TabIndex = 47;
|
||||
this.numericUpDownDelay.TabIndex = 7;
|
||||
this.numericUpDownDelay.TabStop = false;
|
||||
this.numericUpDownDelay.ThousandsSeparator = false;
|
||||
this.numericUpDownDelay.Value = new decimal(new int[] {
|
||||
@ -99,111 +226,13 @@
|
||||
0,
|
||||
0});
|
||||
//
|
||||
// labelColor
|
||||
//
|
||||
this.labelColor.AutoSize = true;
|
||||
this.labelColor.Location = new System.Drawing.Point(228, 22);
|
||||
this.labelColor.Name = "labelColor";
|
||||
this.labelColor.Size = new System.Drawing.Size(32, 13);
|
||||
this.labelColor.TabIndex = 46;
|
||||
this.labelColor.Text = "Color";
|
||||
//
|
||||
// labelEndDelay
|
||||
//
|
||||
this.labelEndDelay.Location = new System.Drawing.Point(3, 76);
|
||||
this.labelEndDelay.Name = "labelEndDelay";
|
||||
this.labelEndDelay.Size = new System.Drawing.Size(163, 17);
|
||||
this.labelEndDelay.TabIndex = 45;
|
||||
this.labelEndDelay.Text = "End delay in milliseconds:";
|
||||
this.labelEndDelay.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
//
|
||||
// buttonCancel
|
||||
//
|
||||
this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.buttonCancel.BackColor = System.Drawing.SystemColors.Control;
|
||||
this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.buttonCancel.Location = new System.Drawing.Point(421, 252);
|
||||
this.buttonCancel.Name = "buttonCancel";
|
||||
this.buttonCancel.Size = new System.Drawing.Size(75, 23);
|
||||
this.buttonCancel.TabIndex = 44;
|
||||
this.buttonCancel.Text = "C&ancel";
|
||||
this.buttonCancel.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// buttonOK
|
||||
//
|
||||
this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.buttonOK.Location = new System.Drawing.Point(340, 252);
|
||||
this.buttonOK.Name = "buttonOK";
|
||||
this.buttonOK.Size = new System.Drawing.Size(75, 23);
|
||||
this.buttonOK.TabIndex = 43;
|
||||
this.buttonOK.Text = "OK";
|
||||
this.buttonOK.UseVisualStyleBackColor = true;
|
||||
this.buttonOK.Click += new System.EventHandler(this.ButtonOkClick);
|
||||
//
|
||||
// buttonChooseColor
|
||||
//
|
||||
this.buttonChooseColor.Location = new System.Drawing.Point(169, 17);
|
||||
this.buttonChooseColor.Name = "buttonChooseColor";
|
||||
this.buttonChooseColor.Size = new System.Drawing.Size(27, 23);
|
||||
this.buttonChooseColor.TabIndex = 42;
|
||||
this.buttonChooseColor.Text = "...";
|
||||
this.buttonChooseColor.UseVisualStyleBackColor = true;
|
||||
this.buttonChooseColor.Click += new System.EventHandler(this.ButtonChooseColorClick);
|
||||
//
|
||||
// panelColor
|
||||
//
|
||||
this.panelColor.Location = new System.Drawing.Point(202, 19);
|
||||
this.panelColor.Name = "panelColor";
|
||||
this.panelColor.Size = new System.Drawing.Size(20, 20);
|
||||
this.panelColor.TabIndex = 41;
|
||||
//
|
||||
// labelChooseColor
|
||||
//
|
||||
this.labelChooseColor.Location = new System.Drawing.Point(0, 22);
|
||||
this.labelChooseColor.Name = "labelChooseColor";
|
||||
this.labelChooseColor.Size = new System.Drawing.Size(166, 13);
|
||||
this.labelChooseColor.TabIndex = 40;
|
||||
this.labelChooseColor.Text = "Choose color:";
|
||||
this.labelChooseColor.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
//
|
||||
// buttonPreview
|
||||
//
|
||||
this.buttonPreview.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.buttonPreview.Location = new System.Drawing.Point(12, 233);
|
||||
this.buttonPreview.Name = "buttonPreview";
|
||||
this.buttonPreview.Size = new System.Drawing.Size(100, 23);
|
||||
this.buttonPreview.TabIndex = 39;
|
||||
this.buttonPreview.Text = "Preview";
|
||||
this.buttonPreview.UseVisualStyleBackColor = true;
|
||||
this.buttonPreview.Click += new System.EventHandler(this.ButtonPreviewClick);
|
||||
//
|
||||
// timer1
|
||||
//
|
||||
this.timer1.Tick += new System.EventHandler(this.Timer1Tick);
|
||||
//
|
||||
// richTextBoxPreview
|
||||
//
|
||||
this.richTextBoxPreview.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.richTextBoxPreview.BackColor = System.Drawing.Color.Black;
|
||||
this.richTextBoxPreview.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
||||
this.richTextBoxPreview.DetectUrls = false;
|
||||
this.richTextBoxPreview.Font = new System.Drawing.Font("Tahoma", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.richTextBoxPreview.ForeColor = System.Drawing.Color.White;
|
||||
this.richTextBoxPreview.Location = new System.Drawing.Point(13, 99);
|
||||
this.richTextBoxPreview.Name = "richTextBoxPreview";
|
||||
this.richTextBoxPreview.ReadOnly = true;
|
||||
this.richTextBoxPreview.Size = new System.Drawing.Size(483, 128);
|
||||
this.richTextBoxPreview.TabIndex = 50;
|
||||
this.richTextBoxPreview.TabStop = false;
|
||||
this.richTextBoxPreview.Text = "";
|
||||
//
|
||||
// EffectKaraoke
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(508, 287);
|
||||
this.Controls.Add(this.radioButtonByCharEffect);
|
||||
this.Controls.Add(this.radioButtonByWordEffect);
|
||||
this.Controls.Add(this.richTextBoxPreview);
|
||||
this.Controls.Add(this.labelTotalMilliseconds);
|
||||
this.Controls.Add(this.labelTM);
|
||||
@ -246,5 +275,7 @@
|
||||
private System.Windows.Forms.Button buttonPreview;
|
||||
private System.Windows.Forms.Timer timer1;
|
||||
private System.Windows.Forms.RichTextBox richTextBoxPreview;
|
||||
private System.Windows.Forms.RadioButton radioButtonByWordEffect;
|
||||
private System.Windows.Forms.RadioButton radioButtonByCharEffect;
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using Nikse.SubtitleEdit.Core.Common.TextEffect;
|
||||
|
||||
namespace Nikse.SubtitleEdit.Forms
|
||||
{
|
||||
@ -26,6 +27,7 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
buttonPreview.Text = LanguageSettings.Current.General.Preview;
|
||||
buttonOK.Text = LanguageSettings.Current.General.Ok;
|
||||
buttonCancel.Text = LanguageSettings.Current.General.Cancel;
|
||||
|
||||
UiUtil.FixLargeFonts(this, buttonOK);
|
||||
}
|
||||
|
||||
@ -109,34 +111,25 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
private void MakeAnimation()
|
||||
{
|
||||
_animation = new List<Paragraph>();
|
||||
|
||||
if (HtmlUtil.RemoveHtmlTags(_paragraph.Text, true).Length == 0 || _paragraph.DurationTotalMilliseconds < 0.001)
|
||||
{
|
||||
_animation.Add(new Paragraph(_paragraph));
|
||||
return;
|
||||
}
|
||||
|
||||
var duration = _paragraph.DurationTotalMilliseconds - ((double)numericUpDownDelay.Value * TimeCode.BaseUnit);
|
||||
var partsBase = EffectAnimationPart.MakeBase(_paragraph.Text);
|
||||
var stepsLength = duration / partsBase.Count+1;
|
||||
for (var index = 0; index <= partsBase.Count; index++)
|
||||
var delaySeconds = (double)numericUpDownDelay.Value;
|
||||
var karaokeEffect = new KaraokeEffect(SelectStrategy());
|
||||
_animation.AddRange(karaokeEffect.Transform(_paragraph, panelColor.BackColor, delaySeconds * TimeCode.BaseUnit));
|
||||
}
|
||||
|
||||
private TextEffectBase SelectStrategy()
|
||||
{
|
||||
if (radioButtonByCharEffect.Checked)
|
||||
{
|
||||
var list = EffectAnimationPart.MakeEffectKaraoke(partsBase, panelColor.BackColor, index);
|
||||
var text = EffectAnimationPart.ToString(list);
|
||||
var startMilliseconds = index * stepsLength;
|
||||
startMilliseconds += _paragraph.StartTime.TotalMilliseconds;
|
||||
var endMilliseconds = ((index + 1) * stepsLength) - 1;
|
||||
endMilliseconds += _paragraph.StartTime.TotalMilliseconds;
|
||||
var start = new TimeCode(startMilliseconds);
|
||||
var end = new TimeCode(endMilliseconds);
|
||||
_animation.Add(new Paragraph(start, end, text));
|
||||
return new KaraokeCharTransform();
|
||||
}
|
||||
|
||||
// All remaining time should go to the last paragraph.
|
||||
if (_animation.Count > 0)
|
||||
{
|
||||
_animation[_animation.Count - 1].EndTime.TotalMilliseconds = _paragraph.EndTime.TotalMilliseconds;
|
||||
}
|
||||
return new KaraokeWordTransform();
|
||||
}
|
||||
|
||||
private void Timer1Tick(object sender, EventArgs e)
|
||||
|
Loading…
Reference in New Issue
Block a user