Merge pull request #8100 from ivandrofly/feature/karaoke

Fixes #6234 #7947 #7909  Add karaoke effect for text transformation
This commit is contained in:
Nikolaj Olsson 2024-03-31 17:08:25 +02:00 committed by GitHub
commit 499bc16b4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 415 additions and 480 deletions

View File

@ -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}";
}
}
}

View File

@ -1401,5 +1401,22 @@ namespace Nikse.SubtitleEdit.Core.Common
text = Regex.Replace(text, "\\\\1c&[abcdefghABCDEFGH\\d]*&", string.Empty); text = Regex.Replace(text, "\\\\1c&[abcdefghABCDEFGH\\d]*&", string.Empty);
return text; 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 == '{';
} }
} }

View 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);
}
}

View 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)}\">";
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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();
}
}
}

View File

@ -31,7 +31,6 @@
this.components = new System.ComponentModel.Container(); this.components = new System.ComponentModel.Container();
this.labelTotalMilliseconds = new System.Windows.Forms.Label(); this.labelTotalMilliseconds = new System.Windows.Forms.Label();
this.labelTM = 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.labelColor = new System.Windows.Forms.Label();
this.labelEndDelay = new System.Windows.Forms.Label(); this.labelEndDelay = new System.Windows.Forms.Label();
this.buttonCancel = new System.Windows.Forms.Button(); this.buttonCancel = new System.Windows.Forms.Button();
@ -42,6 +41,9 @@
this.buttonPreview = new System.Windows.Forms.Button(); this.buttonPreview = new System.Windows.Forms.Button();
this.timer1 = new System.Windows.Forms.Timer(this.components); this.timer1 = new System.Windows.Forms.Timer(this.components);
this.richTextBoxPreview = new System.Windows.Forms.RichTextBox(); 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(); this.SuspendLayout();
// //
// labelTotalMilliseconds // labelTotalMilliseconds
@ -50,7 +52,7 @@
this.labelTotalMilliseconds.Location = new System.Drawing.Point(169, 52); this.labelTotalMilliseconds.Location = new System.Drawing.Point(169, 52);
this.labelTotalMilliseconds.Name = "labelTotalMilliseconds"; this.labelTotalMilliseconds.Name = "labelTotalMilliseconds";
this.labelTotalMilliseconds.Size = new System.Drawing.Size(108, 13); this.labelTotalMilliseconds.Size = new System.Drawing.Size(108, 13);
this.labelTotalMilliseconds.TabIndex = 49; this.labelTotalMilliseconds.TabIndex = 5;
this.labelTotalMilliseconds.Text = "labelTotalMilliseconds"; this.labelTotalMilliseconds.Text = "labelTotalMilliseconds";
// //
// labelTM // labelTM
@ -58,10 +60,135 @@
this.labelTM.Location = new System.Drawing.Point(3, 52); this.labelTM.Location = new System.Drawing.Point(3, 52);
this.labelTM.Name = "labelTM"; this.labelTM.Name = "labelTM";
this.labelTM.Size = new System.Drawing.Size(163, 13); this.labelTM.Size = new System.Drawing.Size(163, 13);
this.labelTM.TabIndex = 48; this.labelTM.TabIndex = 4;
this.labelTM.Text = "Total milliseconds:"; this.labelTM.Text = "Total milliseconds:";
this.labelTM.TextAlign = System.Drawing.ContentAlignment.TopRight; 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 // numericUpDownDelay
// //
this.numericUpDownDelay.BackColor = System.Drawing.SystemColors.Window; this.numericUpDownDelay.BackColor = System.Drawing.SystemColors.Window;
@ -90,7 +217,7 @@
-2147483648}); -2147483648});
this.numericUpDownDelay.Name = "numericUpDownDelay"; this.numericUpDownDelay.Name = "numericUpDownDelay";
this.numericUpDownDelay.Size = new System.Drawing.Size(54, 23); this.numericUpDownDelay.Size = new System.Drawing.Size(54, 23);
this.numericUpDownDelay.TabIndex = 47; this.numericUpDownDelay.TabIndex = 7;
this.numericUpDownDelay.TabStop = false; this.numericUpDownDelay.TabStop = false;
this.numericUpDownDelay.ThousandsSeparator = false; this.numericUpDownDelay.ThousandsSeparator = false;
this.numericUpDownDelay.Value = new decimal(new int[] { this.numericUpDownDelay.Value = new decimal(new int[] {
@ -99,111 +226,13 @@
0, 0,
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 // EffectKaraoke
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(508, 287); 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.richTextBoxPreview);
this.Controls.Add(this.labelTotalMilliseconds); this.Controls.Add(this.labelTotalMilliseconds);
this.Controls.Add(this.labelTM); this.Controls.Add(this.labelTM);
@ -246,5 +275,7 @@
private System.Windows.Forms.Button buttonPreview; private System.Windows.Forms.Button buttonPreview;
private System.Windows.Forms.Timer timer1; private System.Windows.Forms.Timer timer1;
private System.Windows.Forms.RichTextBox richTextBoxPreview; private System.Windows.Forms.RichTextBox richTextBoxPreview;
private System.Windows.Forms.RadioButton radioButtonByWordEffect;
private System.Windows.Forms.RadioButton radioButtonByCharEffect;
} }
} }

View File

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using Nikse.SubtitleEdit.Core.Common.TextEffect;
namespace Nikse.SubtitleEdit.Forms namespace Nikse.SubtitleEdit.Forms
{ {
@ -26,6 +27,7 @@ namespace Nikse.SubtitleEdit.Forms
buttonPreview.Text = LanguageSettings.Current.General.Preview; buttonPreview.Text = LanguageSettings.Current.General.Preview;
buttonOK.Text = LanguageSettings.Current.General.Ok; buttonOK.Text = LanguageSettings.Current.General.Ok;
buttonCancel.Text = LanguageSettings.Current.General.Cancel; buttonCancel.Text = LanguageSettings.Current.General.Cancel;
UiUtil.FixLargeFonts(this, buttonOK); UiUtil.FixLargeFonts(this, buttonOK);
} }
@ -109,34 +111,25 @@ namespace Nikse.SubtitleEdit.Forms
private void MakeAnimation() private void MakeAnimation()
{ {
_animation = new List<Paragraph>(); _animation = new List<Paragraph>();
if (HtmlUtil.RemoveHtmlTags(_paragraph.Text, true).Length == 0 || _paragraph.DurationTotalMilliseconds < 0.001) if (HtmlUtil.RemoveHtmlTags(_paragraph.Text, true).Length == 0 || _paragraph.DurationTotalMilliseconds < 0.001)
{ {
_animation.Add(new Paragraph(_paragraph)); _animation.Add(new Paragraph(_paragraph));
return; return;
} }
var duration = _paragraph.DurationTotalMilliseconds - ((double)numericUpDownDelay.Value * TimeCode.BaseUnit); var delaySeconds = (double)numericUpDownDelay.Value;
var partsBase = EffectAnimationPart.MakeBase(_paragraph.Text); var karaokeEffect = new KaraokeEffect(SelectStrategy());
var stepsLength = duration / partsBase.Count+1; _animation.AddRange(karaokeEffect.Transform(_paragraph, panelColor.BackColor, delaySeconds * TimeCode.BaseUnit));
for (var index = 0; index <= partsBase.Count; index++) }
private TextEffectBase SelectStrategy()
{
if (radioButtonByCharEffect.Checked)
{ {
var list = EffectAnimationPart.MakeEffectKaraoke(partsBase, panelColor.BackColor, index); return new KaraokeCharTransform();
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));
} }
// All remaining time should go to the last paragraph. return new KaraokeWordTransform();
if (_animation.Count > 0)
{
_animation[_animation.Count - 1].EndTime.TotalMilliseconds = _paragraph.EndTime.TotalMilliseconds;
}
} }
private void Timer1Tick(object sender, EventArgs e) private void Timer1Tick(object sender, EventArgs e)