SubtitleEdit/libse/Utilities.cs

2769 lines
108 KiB
C#
Raw Normal View History

2018-03-16 09:31:53 +01:00
using Nikse.SubtitleEdit.Core.ContainerFormats.Matroska;
2016-02-01 20:31:53 +01:00
using Nikse.SubtitleEdit.Core.SubtitleFormats;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
2016-02-01 20:31:53 +01:00
using System.Net;
using System.Reflection;
2018-02-24 21:34:23 +01:00
using System.Security.Authentication;
2016-02-01 20:31:53 +01:00
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
namespace Nikse.SubtitleEdit.Core
{
public static class Utilities
{
public const string WinXP2KUnicodeFontName = "Times New Roman";
/// <summary>
/// Cached environment new line characters for faster lookup.
/// </summary>
public static readonly char[] NewLineChars = { '\r', '\n' };
2016-02-01 20:31:53 +01:00
2020-03-12 11:41:54 +01:00
private static readonly Regex NumberSeparatorNumberRegEx = new Regex(@"\b\d+[\.:;] \d+\b", RegexOptions.Compiled);
public static string[] VideoFileExtensions { get; } = { ".avi", ".mkv", ".wmv", ".mpg", ".mpeg", ".divx", ".mp4", ".asf", ".flv", ".mov", ".m4v", ".vob", ".ogv", ".webm", ".ts", ".m2ts", ".mts", ".avs", ".mxf" };
2016-02-01 20:31:53 +01:00
public static string GetVideoFileFilter(bool includeAudioFiles)
{
var sb = new StringBuilder();
sb.Append(Configuration.Settings.Language.General.VideoFiles + "|");
int i = 0;
foreach (string extension in VideoFileExtensions)
2016-02-01 20:31:53 +01:00
{
if (i > 0)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
sb.Append(';');
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
sb.Append('*');
sb.Append(extension);
i++;
}
if (includeAudioFiles)
{
sb.Append('|');
sb.Append(Configuration.Settings.Language.General.AudioFiles);
2017-11-23 18:56:45 +01:00
sb.Append("|*.mp3;*.wav;*.wma;*.ogg;*.mpa;*.m4a;*.ape;*.aiff;*.flac;*.aac;*.ac3;*.mka");
2016-02-01 20:31:53 +01:00
}
sb.Append('|');
sb.Append(Configuration.Settings.Language.General.AllFiles);
sb.Append("|*.*");
return sb.ToString();
}
public static bool IsInteger(string s)
{
return int.TryParse(s, out _);
2016-02-01 20:31:53 +01:00
}
public static bool IsHex(string s)
{
foreach (var ch in s)
{
if (!CharUtils.IsHexadecimal(ch))
{
return false;
}
}
return true;
}
2016-02-01 20:31:53 +01:00
public static SubtitleFormat GetSubtitleFormatByFriendlyName(string friendlyName)
{
foreach (var format in SubtitleFormat.AllSubtitleFormats)
2016-02-01 20:31:53 +01:00
{
if (format.FriendlyName == friendlyName || format.Name == friendlyName)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return format;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
return null;
}
public static string FormatBytesToDisplayFileSize(long fileSize)
{
if (fileSize <= 1024)
2019-01-19 14:40:37 +01:00
{
return $"{fileSize} bytes";
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (fileSize <= 1024 * 1024)
2019-01-19 14:40:37 +01:00
{
return $"{fileSize / 1024} kb";
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (fileSize <= 1024 * 1024 * 1024)
2019-01-19 14:40:37 +01:00
{
return $"{(float)fileSize / (1024 * 1024):0.0} mb";
2019-01-19 14:40:37 +01:00
}
return $"{(float)fileSize / (1024 * 1024 * 1024):0.0} gb";
2016-02-01 20:31:53 +01:00
}
public static long DisplayFileSizeToBytes(string displayFileSize)
{
if (displayFileSize.Contains("bytes"))
{
if (double.TryParse(displayFileSize.Replace("bytes", string.Empty).Trim(), NumberStyles.AllowDecimalPoint, CultureInfo.CurrentCulture, out var n))
{
return (int)Math.Round(n);
}
}
if (displayFileSize.Contains("kb"))
{
if (double.TryParse(displayFileSize.Replace("kb", string.Empty).Trim(), NumberStyles.AllowDecimalPoint, CultureInfo.CurrentCulture, out var n))
{
return (int)Math.Round(n * 1024);
}
}
if (displayFileSize.Contains("mb"))
{
if (double.TryParse(displayFileSize.Replace("mb", string.Empty).Trim(), NumberStyles.AllowDecimalPoint, CultureInfo.CurrentCulture, out var n))
{
return (int)Math.Round(n * 1024 * 1024);
}
}
if (displayFileSize.Contains("gb"))
{
if (double.TryParse(displayFileSize.Replace("gb", string.Empty).Trim(), NumberStyles.AllowDecimalPoint, CultureInfo.CurrentCulture, out var n))
{
return (int)Math.Round(n * 1024 * 1024 * 1024);
}
}
return 0;
}
2016-02-01 20:31:53 +01:00
/// <summary>
/// Downloads the requested resource as a <see cref="String"/> using the configured <see cref="WebProxy"/>.
/// </summary>
/// <param name="address">A <see cref="String"/> containing the URI to download.</param>
/// <param name="encoding">Encoding for source text</param>
/// <returns>A <see cref="String"/> containing the requested resource.</returns>
public static string DownloadString(string address, Encoding encoding = null)
{
using (var wc = new WebClient())
{
wc.Proxy = GetProxy();
if (encoding != null)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
wc.Encoding = encoding;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
return wc.DownloadString(address).Trim();
}
}
2018-02-24 21:34:23 +01:00
public static void SetSecurityProtocol()
{
// Github requires TLS 1.2
2018-12-18 17:49:14 +01:00
try
{
var tls12Protocol = (SslProtocols)0x00000C00; //TODO: Remove this when it's standard in .net framework - 4.6+
2020-02-07 08:13:56 +01:00
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | (SecurityProtocolType)tls12Protocol;
2018-12-18 17:49:14 +01:00
}
catch (Exception)
{
// This will crash on .net framework versions < 4.5!
// .NET 4.5 required for TLS 1.2 - TLS 1.2 is not default so use this: ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12
// NET 4.6 and above. You dont need to do any additional work to support TLS 1.2, its supported by default.
}
2018-02-24 21:34:23 +01:00
}
2016-02-01 20:31:53 +01:00
public static WebProxy GetProxy()
{
if (!string.IsNullOrEmpty(Configuration.Settings.Proxy.ProxyAddress))
{
var proxy = new WebProxy(Configuration.Settings.Proxy.ProxyAddress);
if (!string.IsNullOrEmpty(Configuration.Settings.Proxy.UserName))
{
if (string.IsNullOrEmpty(Configuration.Settings.Proxy.Domain))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
proxy.Credentials = new NetworkCredential(Configuration.Settings.Proxy.UserName, Configuration.Settings.Proxy.DecodePassword());
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
else
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
proxy.Credentials = new NetworkCredential(Configuration.Settings.Proxy.UserName, Configuration.Settings.Proxy.DecodePassword(), Configuration.Settings.Proxy.Domain);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
else
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
proxy.UseDefaultCredentials = true;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
return proxy;
}
return null;
}
public static bool IsBetweenNumbers(string s, int position)
{
if (string.IsNullOrEmpty(s) || position < 1 || position + 2 > s.Length)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return false;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
return char.IsDigit(s[position - 1]) && char.IsDigit(s[position + 1]);
}
public static string AutoBreakLine(string text, string language)
{
return AutoBreakLine(text, Configuration.Settings.General.SubtitleLineMaximumLength, Configuration.Settings.General.MergeLinesShorterThan, language);
2016-02-01 20:31:53 +01:00
}
public static string AutoBreakLine(string text)
{
return AutoBreakLine(text, string.Empty); // no language
}
2018-12-12 20:22:29 +01:00
internal static bool CanBreak(string s, int index, string language)
2016-02-01 20:31:53 +01:00
{
char nextChar;
if (index >= 0 && index < s.Length)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
nextChar = s[index];
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
else
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return false;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (!"\r\n\t ".Contains(nextChar))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return false;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
// Some words we don't like breaking after
string s2 = s.Substring(0, index);
if (Configuration.Settings.Tools.UseNoLineBreakAfter)
{
foreach (NoBreakAfterItem ending in NoBreakAfterList(language))
{
if (ending.IsMatch(s2))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return false;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
}
else
{
if (s2.EndsWith(" mr.", StringComparison.OrdinalIgnoreCase) ||
s2.EndsWith(" dr.", StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
2016-02-01 20:31:53 +01:00
if (s2.EndsWith("? -", StringComparison.Ordinal) || s2.EndsWith("! -", StringComparison.Ordinal) || s2.EndsWith(". -", StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return false;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
return true;
}
public static void ResetNoBreakAfterList()
{
_lastNoBreakAfterListLanguage = null;
}
2016-02-01 20:31:53 +01:00
private static string _lastNoBreakAfterListLanguage;
private static List<NoBreakAfterItem> _lastNoBreakAfterList = new List<NoBreakAfterItem>();
internal static IEnumerable<NoBreakAfterItem> NoBreakAfterList(string languageName)
2016-02-01 20:31:53 +01:00
{
if (string.IsNullOrEmpty(languageName))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return new List<NoBreakAfterItem>();
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (languageName == _lastNoBreakAfterListLanguage)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return _lastNoBreakAfterList;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
_lastNoBreakAfterList = new List<NoBreakAfterItem>();
//load words via xml
string noBreakAfterFileName = DictionaryFolder + languageName + "_NoBreakAfterList.xml";
var doc = new XmlDocument();
if (File.Exists(noBreakAfterFileName))
{
doc.Load(noBreakAfterFileName);
2020-01-29 13:51:52 +01:00
foreach (XmlNode node in doc.DocumentElement.SelectNodes("Item"))
2016-02-01 20:31:53 +01:00
{
if (!string.IsNullOrEmpty(node.InnerText))
{
if (node.Attributes?["RegEx"] != null && node.Attributes["RegEx"].InnerText.Equals("true", StringComparison.OrdinalIgnoreCase))
2016-02-01 20:31:53 +01:00
{
var r = new Regex(node.InnerText, RegexOptions.Compiled);
_lastNoBreakAfterList.Add(new NoBreakAfterItem(r, node.InnerText));
}
else
{
_lastNoBreakAfterList.Add(new NoBreakAfterItem(node.InnerText.TrimStart()));
2016-02-01 20:31:53 +01:00
}
}
}
}
_lastNoBreakAfterListLanguage = languageName;
return _lastNoBreakAfterList;
}
public static string AutoBreakLineMoreThanTwoLines(string text, int maximumLength, int mergeLinesShorterThan, string language)
2016-02-01 20:31:53 +01:00
{
if (text == null || text.Length < 3 || !(text.Contains(" ") || text.Contains("\n")))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return text;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
string s = AutoBreakLinePrivate(text, maximumLength, mergeLinesShorterThan, language, Configuration.Settings.Tools.AutoBreakLineEndingEarly);
2016-02-01 20:31:53 +01:00
var arr = HtmlUtil.RemoveHtmlTags(s, true).SplitToLines();
if (arr.Count == 1 && arr[0].Length <= maximumLength ||
arr.Count == 2 && arr[0].Length <= maximumLength && arr[1].Length <= maximumLength)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return s;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
s = RemoveLineBreaks(text);
2016-02-01 20:31:53 +01:00
var htmlTags = new Dictionary<int, string>();
var sb = new StringBuilder(s.Length);
int six = 0;
while (six < s.Length)
{
var letter = s[six];
var tagFound = letter == '<' &&
(s.Substring(six).StartsWith("<font", StringComparison.OrdinalIgnoreCase)
|| s.Substring(six).StartsWith("</font", StringComparison.OrdinalIgnoreCase)
|| s.Substring(six).StartsWith("<u", StringComparison.OrdinalIgnoreCase)
|| s.Substring(six).StartsWith("</u", StringComparison.OrdinalIgnoreCase)
|| s.Substring(six).StartsWith("<b", StringComparison.OrdinalIgnoreCase)
|| s.Substring(six).StartsWith("</b", StringComparison.OrdinalIgnoreCase)
|| s.Substring(six).StartsWith("<i", StringComparison.OrdinalIgnoreCase)
|| s.Substring(six).StartsWith("</i", StringComparison.OrdinalIgnoreCase));
2016-02-01 20:31:53 +01:00
int endIndex = -1;
if (tagFound)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
endIndex = s.IndexOf('>', six + 1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (tagFound && endIndex > 0)
{
string tag = s.Substring(six, endIndex - six + 1);
s = s.Remove(six, tag.Length);
if (htmlTags.ContainsKey(six))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
htmlTags[six] = htmlTags[six] + tag;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
else
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
htmlTags.Add(six, tag);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
else
{
sb.Append(letter);
six++;
}
}
s = sb.ToString();
// check 3 lines
var pti = new PlainTextImporter(false, false, 1, ".?!", maximumLength, language);
var three = pti.SplitToThree(sb.ToString());
if (three.Count == 3 &&
three[0].Length < maximumLength &&
three[1].Length < maximumLength &&
three[2].Length < maximumLength)
{
return ReInsertHtmlTagsAndCleanUp(string.Join(" " + Environment.NewLine, three), htmlTags);
}
// check 4 lines
var four = pti.SplitToFour(sb.ToString());
if (four.Count == 4 &&
four[0].Length < maximumLength &&
four[1].Length < maximumLength &&
four[2].Length < maximumLength &&
four[3].Length < maximumLength)
{
return ReInsertHtmlTagsAndCleanUp(string.Join(" " + Environment.NewLine, four), htmlTags);
}
2016-02-01 20:31:53 +01:00
var words = s.Split(' ');
for (int numberOfLines = 3; numberOfLines < 9999; numberOfLines++)
{
int average = s.Length / numberOfLines + 1;
for (int len = average; len < maximumLength; len++)
2016-02-01 20:31:53 +01:00
{
List<int> list = SplitToX(words, numberOfLines, len);
bool allOk = true;
foreach (var lineLength in list)
{
if (lineLength > maximumLength)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
allOk = false;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
if (allOk)
{
int index = 0;
foreach (var item in list)
{
index += item;
htmlTags.Add(index, Environment.NewLine);
}
return ReInsertHtmlTagsAndCleanUp(s, htmlTags);
2016-02-01 20:31:53 +01:00
}
}
}
return text;
}
private static string ReInsertHtmlTagsAndCleanUp(string input, Dictionary<int, string> htmlTags)
{
var s = ReInsertHtmlTags(input, htmlTags);
s = s.Replace(" " + Environment.NewLine, Environment.NewLine);
s = s.Replace(Environment.NewLine + " ", Environment.NewLine);
s = s.Replace(Environment.NewLine + "</i>", "</i>" + Environment.NewLine);
s = s.Replace(Environment.NewLine + "</b>", "</b>" + Environment.NewLine);
s = s.Replace(Environment.NewLine + "</u>", "</u>" + Environment.NewLine);
s = s.Replace(Environment.NewLine + "</font>", "</font>" + Environment.NewLine);
return s.TrimEnd();
}
2016-02-01 20:31:53 +01:00
private static List<int> SplitToX(string[] words, int count, int average)
{
var list = new List<int>();
int currentIdx = 0;
int currentCount = 0;
foreach (string word in words)
{
if (currentCount + word.Length + 3 > average && currentIdx < count)
{
list.Add(currentCount);
currentIdx++;
currentCount = 0;
}
currentCount += word.Length + 1;
}
if (currentIdx < count)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
list.Add(currentCount);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
else
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
list[list.Count - 1] += currentCount;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
return list;
}
public static string AutoBreakLine(string text, int maximumLength, int mergeLinesShorterThan, string language)
{
if (Configuration.Settings.General.MaxNumberOfLines <= 2)
{
return AutoBreakLinePrivate(text, maximumLength, mergeLinesShorterThan, language, Configuration.Settings.Tools.AutoBreakLineEndingEarly);
}
return AutoBreakLineMoreThanTwoLines(text, maximumLength, mergeLinesShorterThan, language);
}
public static string AutoBreakLine(string text, string language, bool autoBreakLineEndingEarly)
{
if (Configuration.Settings.General.MaxNumberOfLines <= 2)
{
return AutoBreakLinePrivate(text, Configuration.Settings.General.SubtitleLineMaximumLength, Configuration.Settings.General.MergeLinesShorterThan, language, autoBreakLineEndingEarly);
}
return AutoBreakLineMoreThanTwoLines(text, Configuration.Settings.General.SubtitleLineMaximumLength, Configuration.Settings.General.MergeLinesShorterThan, language);
}
public static string AutoBreakLinePrivate(string text, int maximumLength, int mergeLinesShorterThan, string language, bool autoBreakLineEndingEarly)
{
if (text == null || text.Length < 3 || !(text.Contains(" ") || text.Contains("\n")))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return text;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
2019-11-05 19:09:02 +01:00
// do not auto break dialogs or music symbol
if (text.Contains(Environment.NewLine) && (text.Contains('-') || text.Contains('♪')))
2016-02-01 20:31:53 +01:00
{
var noTagLines = HtmlUtil.RemoveHtmlTags(text, true).SplitToLines();
if (noTagLines.Count == 2)
2016-02-01 20:31:53 +01:00
{
var arr0 = noTagLines[0].Trim().TrimEnd('"', '\'').TrimEnd();
if (language == "ar")
{
if (arr0.EndsWith('-') && noTagLines[1].TrimStart().EndsWith('-') && arr0.Length > 1 && (".?!)]♪؟".Contains(arr0[0]) || arr0.StartsWith("--", StringComparison.Ordinal) || arr0.StartsWith('')))
2019-01-19 14:40:37 +01:00
{
return text;
2019-01-19 14:40:37 +01:00
}
}
else
{
if (arr0.StartsWith('-') && noTagLines[1].TrimStart().StartsWith('-') && arr0.Length > 1 && (".?!)]♪؟".Contains(arr0[arr0.Length - 1]) || arr0.EndsWith("--", StringComparison.Ordinal) || arr0.EndsWith('')))
2019-01-19 14:40:37 +01:00
{
return text;
2019-01-19 14:40:37 +01:00
}
}
if (noTagLines[0].StartsWith('♪') && noTagLines[0].EndsWith('♪') || noTagLines[1].StartsWith('♪') && noTagLines[0].EndsWith('♪'))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return text;
2019-01-19 14:40:37 +01:00
}
if (noTagLines[0].StartsWith('[') && noTagLines[0].Length > 1 && (".?!)]♪؟".Contains(arr0[arr0.Length - 1]) && (noTagLines[1].StartsWith('-') || noTagLines[1].StartsWith('['))))
2020-02-25 11:52:58 +01:00
{
return text;
}
if (noTagLines[0].StartsWith('-') && noTagLines[0].Length > 1 && (".?!)]♪؟".Contains(arr0[arr0.Length - 1]) && (noTagLines[1].StartsWith('-') || noTagLines[1].StartsWith('['))))
2020-02-25 11:52:58 +01:00
{
return text;
}
2016-02-01 20:31:53 +01:00
}
}
string s = RemoveLineBreaks(text);
2016-11-12 18:12:22 +01:00
if (HtmlUtil.RemoveHtmlTags(s, true).Length < mergeLinesShorterThan)
2016-02-01 20:31:53 +01:00
{
return s;
}
var htmlTags = new Dictionary<int, string>();
var sb = new StringBuilder();
int six = 0;
while (six < s.Length)
{
var letter = s[six];
2016-11-12 18:23:46 +01:00
bool tagFound = false;
if (letter == '<')
{
string tagString = s.Substring(six);
tagFound = tagString.StartsWith("<font", StringComparison.OrdinalIgnoreCase)
|| tagString.StartsWith("</font", StringComparison.OrdinalIgnoreCase)
|| tagString.StartsWith("<u", StringComparison.OrdinalIgnoreCase)
|| tagString.StartsWith("</u", StringComparison.OrdinalIgnoreCase)
|| tagString.StartsWith("<b", StringComparison.OrdinalIgnoreCase)
|| tagString.StartsWith("</b", StringComparison.OrdinalIgnoreCase)
|| tagString.StartsWith("<i", StringComparison.OrdinalIgnoreCase)
|| tagString.StartsWith("</i", StringComparison.OrdinalIgnoreCase);
}
else if (letter == '{' && s.Substring(six).StartsWith("{\\"))
{
string tagString = s.Substring(six);
var endIndexAssTag = tagString.IndexOf('}') + 1;
if (endIndexAssTag > 0)
{
tagString = tagString.Substring(0, endIndexAssTag);
if (htmlTags.ContainsKey(six))
2019-01-19 14:40:37 +01:00
{
htmlTags[six] = htmlTags[six] + tagString;
2019-01-19 14:40:37 +01:00
}
else
2019-01-19 14:40:37 +01:00
{
htmlTags.Add(six, tagString);
2019-01-19 14:40:37 +01:00
}
s = s.Remove(six, endIndexAssTag);
continue;
}
}
2016-11-12 18:23:46 +01:00
2016-02-01 20:31:53 +01:00
int endIndex = -1;
if (tagFound)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
endIndex = s.IndexOf('>', six + 1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (tagFound && endIndex > 0)
{
string tag = s.Substring(six, endIndex - six + 1);
s = s.Remove(six, tag.Length);
if (htmlTags.ContainsKey(six))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
htmlTags[six] = htmlTags[six] + tag;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
else
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
htmlTags.Add(six, tag);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
else
{
sb.Append(letter);
six++;
}
}
s = sb.ToString();
var textSplit = new TextSplit(s, maximumLength, language);
var split = textSplit.AutoBreak(Configuration.Settings.Tools.AutoBreakDashEarly, autoBreakLineEndingEarly, Configuration.Settings.Tools.AutoBreakCommaBreakEarly, Configuration.Settings.Tools.AutoBreakUsePixelWidth);
2019-11-08 13:09:48 +01:00
if (split != null)
2016-02-01 20:31:53 +01:00
{
2019-11-08 13:09:48 +01:00
s = split;
2016-02-01 20:31:53 +01:00
}
2019-11-08 13:09:48 +01:00
s = ReInsertHtmlTags(s.Replace(Environment.NewLine, " " + Environment.NewLine), htmlTags);
2016-02-01 20:31:53 +01:00
var idx = s.IndexOf(Environment.NewLine + "</", StringComparison.Ordinal);
if (idx > 2)
{
var endIdx = s.IndexOf('>', idx + 2);
if (endIdx > idx)
{
var tag = s.Substring(idx + Environment.NewLine.Length, endIdx - (idx + Environment.NewLine.Length) + 1);
s = s.Insert(idx, tag);
s = s.Remove(idx + tag.Length + Environment.NewLine.Length, tag.Length);
}
}
s = s.Replace(" " + Environment.NewLine, Environment.NewLine);
s = s.Replace(Environment.NewLine + " ", Environment.NewLine);
return s.TrimEnd();
}
public static string RemoveLineBreaks(string input)
2016-02-01 20:31:53 +01:00
{
var s = HtmlUtil.FixUpperTags(input);
2016-02-01 20:31:53 +01:00
s = s.Replace(Environment.NewLine + "</i>", "</i>" + Environment.NewLine);
s = s.Replace(Environment.NewLine + "</b>", "</b>" + Environment.NewLine);
s = s.Replace(Environment.NewLine + "</u>", "</u>" + Environment.NewLine);
s = s.Replace(Environment.NewLine + "</font>", "</font>" + Environment.NewLine);
s = s.Replace("</i> " + Environment.NewLine + "<i>", " ");
s = s.Replace("</i>" + Environment.NewLine + " <i>", " ");
s = s.Replace("</i>" + Environment.NewLine + "<i>", " ");
s = s.Replace(Environment.NewLine, " ");
s = s.Replace(" </i>", "</i> ");
s = s.Replace(" </b>", "</b> ");
s = s.Replace(" </u>", "</u> ");
s = s.Replace(" </font>", "</font> ");
s = s.FixExtraSpaces();
return s.Trim();
}
2019-11-08 13:09:48 +01:00
/// <summary>
/// Note: Requires a space before the NewLine
/// </summary>
2016-02-01 20:31:53 +01:00
private static string ReInsertHtmlTags(string s, Dictionary<int, string> htmlTags)
{
if (htmlTags.Count > 0)
{
var sb = new StringBuilder(s.Length);
int six = 0;
foreach (var letter in s)
{
if (Environment.NewLine.Contains(letter))
{
sb.Append(letter);
}
else
{
if (htmlTags.ContainsKey(six))
{
sb.Append(htmlTags[six]);
}
sb.Append(letter);
six++;
}
}
for (int i = 0; i < 15; i++)
2016-02-01 20:31:53 +01:00
{
if (htmlTags.ContainsKey(six + i))
{
sb.Append(htmlTags[six + i]);
}
2016-02-01 20:31:53 +01:00
}
2016-02-01 20:31:53 +01:00
return sb.ToString();
}
return s;
}
public static string UnbreakLine(string text)
{
var lines = text.SplitToLines();
if (lines.Count == 1)
2019-01-19 14:40:37 +01:00
{
2018-12-09 15:16:36 +01:00
return lines[0];
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
var singleLine = string.Join(" ", lines);
2016-02-01 20:31:53 +01:00
while (singleLine.Contains(" "))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
singleLine = singleLine.Replace(" ", " ");
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (singleLine.Contains("</")) // Fix tag
{
singleLine = singleLine.Replace("</i> <i>", " ");
singleLine = singleLine.Replace("</i><i>", " ");
singleLine = singleLine.Replace("</b> <b>", " ");
singleLine = singleLine.Replace("</b><b>", " ");
singleLine = singleLine.Replace("</u> <u>", " ");
singleLine = singleLine.Replace("</u><u>", " ");
}
return singleLine;
}
public static string RemoveSsaTags(string input)
2016-02-01 20:31:53 +01:00
{
var s = input;
int k = s.IndexOf("{\\", StringComparison.Ordinal);
2016-02-01 20:31:53 +01:00
while (k >= 0)
{
int l = s.IndexOf('}', k + 1);
2019-01-19 14:40:37 +01:00
if (l < k)
{
break;
}
s = s.Remove(k, l - k + 1);
k = s.IndexOf('{', k);
2016-02-01 20:31:53 +01:00
}
return s;
}
2018-03-16 09:31:53 +01:00
public static string DictionaryFolder => Configuration.DictionariesDirectory;
2016-02-01 20:31:53 +01:00
public static List<string> GetDictionaryLanguages()
{
var list = new List<string>();
if (Directory.Exists(DictionaryFolder))
{
foreach (string dic in Directory.GetFiles(DictionaryFolder, "*.dic"))
{
string name = Path.GetFileNameWithoutExtension(dic);
2018-03-16 09:31:53 +01:00
if (!name.StartsWith("hyph", StringComparison.Ordinal))
2016-02-01 20:31:53 +01:00
{
try
{
var ci = CultureInfo.GetCultureInfo(name.Replace('_', '-'));
name = ci.DisplayName + " [" + name + "]";
}
catch (Exception exception)
{
System.Diagnostics.Debug.WriteLine(exception.Message);
name = "[" + name + "]";
}
list.Add(name);
}
}
}
return list;
}
public static List<string> GetDictionaryLanguagesCultureNeutral()
{
var list = new List<string>();
if (Directory.Exists(DictionaryFolder))
{
foreach (string dic in Directory.GetFiles(DictionaryFolder, "*.dic"))
{
string name = Path.GetFileNameWithoutExtension(dic);
2018-03-09 23:26:24 +01:00
if (!name.StartsWith("hyph", StringComparison.Ordinal))
{
try
{
var ci = CultureInfo.GetCultureInfo(name.Replace('_', '-'));
var displayName = ci.DisplayName;
if (displayName.Contains("("))
{
displayName = displayName.Remove(displayName.IndexOf('(')).TrimEnd();
}
name = displayName + " [" + ci.TwoLetterISOLanguageName + "]";
}
catch (Exception exception)
{
System.Diagnostics.Debug.WriteLine(exception.Message);
name = "[" + name + "]";
}
if (!list.Contains(name))
{
list.Add(name);
}
}
}
}
return list;
}
public static IEnumerable<CultureInfo> GetSubtitleLanguageCultures()
{
var prospects = new List<CultureInfo>();
var excludes = new HashSet<string>();
foreach (var ci in CultureInfo.GetCultures(CultureTypes.NeutralCultures))
{
if (ci.Name.Length < 4 && ci.Name == ci.IetfLanguageTag)
{
excludes.Add(ci.Parent.Name);
prospects.Add(ci);
}
}
return prospects.Where(ci => !excludes.Contains(ci.Name));
}
2016-02-01 20:31:53 +01:00
public static double GetOptimalDisplayMilliseconds(string text)
{
return GetOptimalDisplayMilliseconds(text, Configuration.Settings.General.SubtitleOptimalCharactersPerSeconds);
}
public static double GetOptimalDisplayMilliseconds(string text, double optimalCharactersPerSecond)
{
if (optimalCharactersPerSecond < 2 || optimalCharactersPerSecond > 100)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
optimalCharactersPerSecond = 14.7;
2019-01-19 14:40:37 +01:00
}
var duration = text.CountCharacters(Configuration.Settings.General.CharactersPerSecondsIgnoreWhiteSpace) / optimalCharactersPerSecond * TimeCode.BaseUnit;
if (duration < 1400)
{
duration *= 1.2;
}
else if (duration < 1400 * 1.2)
{
duration = 1400 * 1.2;
}
else if (duration > 2900)
{
duration = Math.Max(2900, duration * 0.96);
}
2016-02-01 20:31:53 +01:00
if (duration < Configuration.Settings.General.SubtitleMinimumDisplayMilliseconds)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
duration = Configuration.Settings.General.SubtitleMinimumDisplayMilliseconds;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (duration > Configuration.Settings.General.SubtitleMaximumDisplayMilliseconds)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
duration = Configuration.Settings.General.SubtitleMaximumDisplayMilliseconds;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
return duration;
}
public static string ColorToHex(Color c)
{
return $"#{c.R:x2}{c.G:x2}{c.B:x2}";
2016-02-01 20:31:53 +01:00
}
public static int GetMaxLineLength(string text)
{
int maxLength = 0;
2019-02-08 16:59:49 +01:00
foreach (string line in HtmlUtil.RemoveHtmlTags(text, true).SplitToLines())
2016-02-01 20:31:53 +01:00
{
if (line.Length > maxLength)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
maxLength = line.Length;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
return maxLength;
}
public static double GetCharactersPerSecond(Paragraph paragraph)
{
2018-01-23 11:21:14 +01:00
var duration = paragraph.Duration;
if (duration.TotalMilliseconds < 1)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return 999;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
2018-01-23 11:21:14 +01:00
return paragraph.Text.CountCharacters(Configuration.Settings.General.CharactersPerSecondsIgnoreWhiteSpace) / duration.TotalSeconds;
2016-02-01 20:31:53 +01:00
}
2019-01-29 21:33:20 +01:00
public static double GetCharactersPerSecond(Paragraph paragraph, int numberOfCharacters)
{
var duration = paragraph.Duration;
if (duration.TotalMilliseconds < 1)
{
return 999;
}
return numberOfCharacters / duration.TotalSeconds;
}
2016-02-01 20:31:53 +01:00
public static bool IsRunningOnMono()
{
return Type.GetType("Mono.Runtime") != null;
}
public static void ShowHelp(string parameter)
{
string helpFile = Configuration.Settings.Language.General.HelpFile;
if (string.IsNullOrEmpty(helpFile))
2019-01-19 14:40:37 +01:00
{
2018-09-20 00:05:55 +02:00
helpFile = "https://www.nikse.dk/SubtitleEdit/Help";
2019-01-19 14:40:37 +01:00
}
try
{
if (Configuration.IsRunningOnWindows || Configuration.IsRunningOnMac)
{
System.Diagnostics.Process.Start(helpFile + parameter);
}
else if (Configuration.IsRunningOnLinux)
{
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.EnableRaisingEvents = false;
process.StartInfo.FileName = "xdg-open";
process.StartInfo.Arguments = helpFile + parameter;
process.Start();
}
}
catch
{
//Don't do anything
}
2016-02-01 20:31:53 +01:00
}
public static string AssemblyVersion => Assembly.GetEntryAssembly().GetName().Version.ToString();
2016-02-01 20:31:53 +01:00
public static string AssemblyDescription
{
get
{
var assembly = Assembly.GetEntryAssembly();
if (Attribute.IsDefined(assembly, typeof(AssemblyDescriptionAttribute)))
{
var descriptionAttribute = (AssemblyDescriptionAttribute)Attribute.GetCustomAttribute(assembly, typeof(AssemblyDescriptionAttribute));
if (descriptionAttribute != null)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return descriptionAttribute.Description;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
return null;
}
}
public static void RemoveFromUserDictionary(string word, string languageName)
{
word = word.Trim();
if (word.Length > 0)
{
string userWordsXmlFileName = DictionaryFolder + languageName + "_user.xml";
var userWords = new XmlDocument();
if (File.Exists(userWordsXmlFileName))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
userWords.Load(userWordsXmlFileName);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
else
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
userWords.LoadXml("<words />");
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
var words = new List<string>();
2018-03-16 09:31:53 +01:00
var nodes = userWords.DocumentElement?.SelectNodes("word");
if (nodes != null)
2016-02-01 20:31:53 +01:00
{
2018-03-16 09:31:53 +01:00
foreach (XmlNode node in nodes)
{
string w = node.InnerText.Trim();
if (w.Length > 0 && w != word)
2019-01-19 14:40:37 +01:00
{
2018-03-16 09:31:53 +01:00
words.Add(w);
2019-01-19 14:40:37 +01:00
}
2018-03-16 09:31:53 +01:00
}
2016-02-01 20:31:53 +01:00
}
2018-03-16 09:31:53 +01:00
2016-02-01 20:31:53 +01:00
words.Sort();
2018-03-16 09:31:53 +01:00
if (userWords.DocumentElement != null)
2016-02-01 20:31:53 +01:00
{
2018-03-16 09:31:53 +01:00
userWords.DocumentElement.RemoveAll();
foreach (string w in words)
{
XmlNode node = userWords.CreateElement("word");
node.InnerText = w;
userWords.DocumentElement.AppendChild(node);
}
2016-02-01 20:31:53 +01:00
}
2018-03-16 09:31:53 +01:00
2016-02-01 20:31:53 +01:00
userWords.Save(userWordsXmlFileName);
}
}
public static void AddToUserDictionary(string word, string languageName)
{
word = word.Trim();
if (word.Length > 0)
{
string userWordsXmlFileName = DictionaryFolder + languageName + "_user.xml";
var userWords = new XmlDocument();
if (File.Exists(userWordsXmlFileName))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
userWords.Load(userWordsXmlFileName);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
else
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
userWords.LoadXml("<words />");
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
var words = new List<string>();
2018-03-16 09:31:53 +01:00
if (userWords.DocumentElement != null)
2016-02-01 20:31:53 +01:00
{
2018-03-16 09:31:53 +01:00
var nodes = userWords.DocumentElement.SelectNodes("word");
if (nodes != null)
{
foreach (XmlNode node in nodes)
{
string w = node.InnerText.Trim();
if (w.Length > 0)
2019-01-19 14:40:37 +01:00
{
2018-03-16 09:31:53 +01:00
words.Add(w);
2019-01-19 14:40:37 +01:00
}
2018-03-16 09:31:53 +01:00
}
}
2016-02-01 20:31:53 +01:00
2018-03-16 09:31:53 +01:00
if (!words.Contains(word))
2019-01-19 14:40:37 +01:00
{
2018-03-16 09:31:53 +01:00
words.Add(word);
2019-01-19 14:40:37 +01:00
}
2018-03-16 09:31:53 +01:00
words.Sort();
userWords.DocumentElement.RemoveAll();
foreach (string w in words)
{
XmlNode node = userWords.CreateElement("word");
node.InnerText = w;
userWords.DocumentElement.AppendChild(node);
}
2016-02-01 20:31:53 +01:00
}
2018-03-16 09:31:53 +01:00
2016-02-01 20:31:53 +01:00
userWords.Save(userWordsXmlFileName);
}
}
public static string LoadUserWordList(List<string> userWordList, string languageName)
{
userWordList.Clear();
var userWordDictionary = new XmlDocument();
string userWordListXmlFileName = DictionaryFolder + languageName + "_user.xml";
if (File.Exists(userWordListXmlFileName))
{
userWordDictionary.Load(userWordListXmlFileName);
foreach (XmlNode node in userWordDictionary.DocumentElement.SelectNodes("word"))
{
string s = node.InnerText.ToLowerInvariant();
2016-02-01 20:31:53 +01:00
if (!userWordList.Contains(s))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
userWordList.Add(s);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
}
return userWordListXmlFileName;
}
public static string LoadUserWordList(HashSet<string> userWordList, string languageName)
{
userWordList.Clear();
var userWordDictionary = new XmlDocument();
string userWordListXmlFileName = DictionaryFolder + languageName + "_user.xml";
if (File.Exists(userWordListXmlFileName))
{
userWordDictionary.Load(userWordListXmlFileName);
2018-03-16 09:31:53 +01:00
var nodes = userWordDictionary.DocumentElement?.SelectNodes("word");
if (nodes != null)
2016-02-01 20:31:53 +01:00
{
2018-03-16 09:31:53 +01:00
foreach (XmlNode node in nodes)
{
string s = node.InnerText.ToLowerInvariant();
2018-03-16 09:31:53 +01:00
if (!userWordList.Contains(s))
2019-01-19 14:40:37 +01:00
{
2018-03-16 09:31:53 +01:00
userWordList.Add(s);
2019-01-19 14:40:37 +01:00
}
2018-03-16 09:31:53 +01:00
}
2016-02-01 20:31:53 +01:00
}
}
return userWordListXmlFileName;
}
public static readonly string UppercaseLetters = Configuration.Settings.General.UppercaseLetters.ToUpperInvariant();
public static readonly string LowercaseLetters = Configuration.Settings.General.UppercaseLetters.ToLowerInvariant();
2016-03-22 20:33:53 +01:00
public static readonly string LowercaseLettersWithNumbers = LowercaseLetters + "0123456789";
public static readonly string AllLetters = UppercaseLetters + LowercaseLetters;
public static readonly string AllLettersAndNumbers = UppercaseLetters + LowercaseLettersWithNumbers;
2016-02-01 20:31:53 +01:00
public static Color GetColorFromUserName(string userName)
{
if (string.IsNullOrEmpty(userName))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return Color.Pink;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
byte[] buffer = Encoding.UTF8.GetBytes(userName);
long number = 0;
foreach (byte b in buffer)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
number += b;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
switch (number % 20)
{
case 0: return Color.Red;
case 1: return Color.Blue;
case 2: return Color.Green;
case 3: return Color.DarkCyan;
case 4: return Color.DarkGreen;
case 5: return Color.DarkBlue;
case 6: return Color.DarkTurquoise;
case 7: return Color.DarkViolet;
case 8: return Color.DeepPink;
case 9: return Color.DodgerBlue;
case 10: return Color.ForestGreen;
case 11: return Color.Fuchsia;
case 12: return Color.DarkOrange;
case 13: return Color.GreenYellow;
case 14: return Color.IndianRed;
case 15: return Color.Indigo;
case 16: return Color.LawnGreen;
case 17: return Color.LightBlue;
case 18: return Color.DarkGoldenrod;
case 19: return Color.Magenta;
default:
return Color.Black;
}
}
public static int GetNumber0To7FromUserName(string userName)
{
if (string.IsNullOrEmpty(userName))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return 0;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
byte[] buffer = Encoding.UTF8.GetBytes(userName);
long number = 0;
foreach (byte b in buffer)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
number += b;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
return (int)(number % 8);
}
public static string LowercaseVowels => "aeiouyæøåéóáôèòæøåäöïɤəɛʊʉɨ";
2016-02-01 20:31:53 +01:00
public static int CountTagInText(string text, string tag)
{
int count = 0;
int index = text.IndexOf(tag, StringComparison.Ordinal);
while (index >= 0)
{
count++;
index = index + tag.Length;
if (index >= text.Length)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return count;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
index = text.IndexOf(tag, index, StringComparison.Ordinal);
}
return count;
}
public static int CountTagInText(string text, char tag)
{
int count = 0;
int index = text.IndexOf(tag);
while (index >= 0)
{
count++;
if ((index + 1) == text.Length)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return count;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
index = text.IndexOf(tag, index + 1);
}
return count;
}
public static bool StartsAndEndsWithTag(string text, string startTag, string endTag)
{
if (string.IsNullOrWhiteSpace(text))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return false;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (!text.Contains(startTag) || !text.Contains(endTag))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return false;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
while (text.Contains(" "))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace(" ", " ");
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
var s1 = "- " + startTag;
var s2 = "-" + startTag;
var s3 = "- ..." + startTag;
var s4 = "- " + startTag + "..."; // - <i>...
var e1 = endTag + ".";
var e2 = endTag + "!";
var e3 = endTag + "?";
var e4 = endTag + "...";
var e5 = endTag + "-";
bool isStart = false;
bool isEnd = false;
if (text.StartsWith(startTag, StringComparison.Ordinal) || text.StartsWith(s1, StringComparison.Ordinal) || text.StartsWith(s2, StringComparison.Ordinal) || text.StartsWith(s3, StringComparison.Ordinal) || text.StartsWith(s4, StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
isStart = true;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.EndsWith(endTag, StringComparison.Ordinal) || text.EndsWith(e1, StringComparison.Ordinal) || text.EndsWith(e2, StringComparison.Ordinal) || text.EndsWith(e3, StringComparison.Ordinal) || text.EndsWith(e4, StringComparison.Ordinal) || text.EndsWith(e5, StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
isEnd = true;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
return isStart && isEnd;
}
public static Paragraph GetOriginalParagraph(int index, Paragraph paragraph, List<Paragraph> originalParagraphs)
{
if (index < 0)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return null;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (index < originalParagraphs.Count && Math.Abs(originalParagraphs[index].StartTime.TotalMilliseconds - paragraph.StartTime.TotalMilliseconds) < 50)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return originalParagraphs[index];
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (paragraph.StartTime.IsMaxTime && index < originalParagraphs.Count && originalParagraphs[index].StartTime.IsMaxTime)
2016-02-01 20:31:53 +01:00
{
return originalParagraphs[index];
}
foreach (var p in originalParagraphs)
{
if (!p.StartTime.IsMaxTime && Math.Abs(p.StartTime.TotalMilliseconds - paragraph.StartTime.TotalMilliseconds) < 0.01)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return p;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
foreach (var p in originalParagraphs)
2016-02-01 20:31:53 +01:00
{
if (!p.StartTime.IsMaxTime &&
p.StartTime.TotalMilliseconds > paragraph.StartTime.TotalMilliseconds - 200 &&
2016-02-01 20:31:53 +01:00
p.StartTime.TotalMilliseconds < paragraph.StartTime.TotalMilliseconds + TimeCode.BaseUnit)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return p;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
2016-02-01 20:31:53 +01:00
return null;
}
/// <summary>
/// UrlEncodes a string without the requirement for System.Web
/// </summary>
public static string UrlEncode(string text)
{
return Uri.EscapeDataString(text);
}
/// <summary>
/// UrlDecodes a string without requiring System.Web
/// </summary>
public static string UrlDecode(string text)
{
// pre-process for + sign space formatting since System.Uri doesn't handle it
// plus literals are encoded as %2b normally so this should be safe
text = text.Replace('+', ' ');
return Uri.UnescapeDataString(text);
}
private static readonly Regex TwoOrMoreDigitsNumber = new Regex(@"\d\d+", RegexOptions.Compiled);
private const string PrePostStringsToReverse = @"-— !?.…""،,():;[]+~*/<>&^%$#\\|'";
2016-03-27 14:41:32 +02:00
public static string ReverseStartAndEndingForRightToLeft(string s)
{
var newLines = new StringBuilder();
var pre = new StringBuilder();
var post = new StringBuilder();
var lines = s.SplitToLines();
2016-03-27 14:41:32 +02:00
foreach (var line in lines)
{
string s2 = line;
2019-01-27 18:47:55 +01:00
var preTags = new StringBuilder();
2017-12-11 08:09:36 +01:00
while (s2.StartsWith("{\\", StringComparison.Ordinal) && s2.IndexOf('}') > 0)
{
int end = s2.IndexOf('}') + 1;
2019-01-27 18:47:55 +01:00
preTags.Append(s2.Substring(0, end));
s2 = s2.Remove(0, end);
}
string postTags = string.Empty;
for (int k = 0; k < 10; k++)
{
if (s2.StartsWith("♪ ", StringComparison.Ordinal) ||
s2.StartsWith("♫ ", StringComparison.Ordinal))
{
preTags.Append(s2.Substring(0, 2));
s2 = s2.Remove(0, 2);
}
if (s2.StartsWith("♪", StringComparison.Ordinal) ||
s2.StartsWith("♫", StringComparison.Ordinal))
{
preTags.Append(s2.Substring(0, 1));
s2 = s2.Remove(0, 1);
}
if (s2.StartsWith("<i>", StringComparison.Ordinal) ||
s2.StartsWith("<b>", StringComparison.Ordinal) ||
s2.StartsWith("<u>", StringComparison.Ordinal))
{
2019-01-27 18:47:55 +01:00
preTags.Append(s2.Substring(0, 3));
s2 = s2.Remove(0, 3);
}
if (s2.StartsWith("<font ", StringComparison.Ordinal) && s2.IndexOf('>') > 0)
{
int idx = s2.IndexOf('>');
idx++;
2019-01-27 18:47:55 +01:00
preTags.Append(s2.Substring(0, idx));
s2 = s2.Remove(0, idx);
}
if (s2.EndsWith(" ♪", StringComparison.Ordinal) ||
s2.EndsWith(" ♫", StringComparison.Ordinal))
{
postTags = s2.Substring(s2.Length - 2) + postTags;
s2 = s2.Remove(s2.Length - 2);
}
if (s2.EndsWith("♪", StringComparison.Ordinal) ||
s2.EndsWith("♫", StringComparison.Ordinal))
{
postTags = s2.Substring(s2.Length - 1) + postTags;
s2 = s2.Remove(s2.Length - 1);
}
if (s2.EndsWith("</i>", StringComparison.Ordinal) ||
s2.EndsWith("</b>", StringComparison.Ordinal) ||
s2.EndsWith("</u>", StringComparison.Ordinal))
{
2019-01-27 18:47:55 +01:00
postTags = s2.Substring(s2.Length - 4) + postTags;
s2 = s2.Remove(s2.Length - 4);
}
if (s2.EndsWith("</font>", StringComparison.Ordinal))
{
postTags = s2.Substring(s2.Length - 7) + postTags;
s2 = s2.Remove(s2.Length - 7);
}
2016-03-27 14:41:32 +02:00
}
pre.Clear();
post.Clear();
2016-03-27 14:41:32 +02:00
int i = 0;
while (i < s2.Length && PrePostStringsToReverse.Contains(s2[i]) && s2[i] != '{' &&
!s2.Substring(i).StartsWith("<i>", StringComparison.OrdinalIgnoreCase) &&
!s2.Substring(i).StartsWith("<b>", StringComparison.OrdinalIgnoreCase) &&
!s2.Substring(i).StartsWith("<font ", StringComparison.OrdinalIgnoreCase))
2016-03-27 14:41:32 +02:00
{
pre.Append(s2[i]);
i++;
}
int j = s2.Length - 1;
while (j > i && PrePostStringsToReverse.Contains(s2[j]) && s2[j] != '}' &&
!s2.Substring(0, j + 1).EndsWith("</i>", StringComparison.OrdinalIgnoreCase) &&
!s2.Substring(0, j + 1).EndsWith("</b>", StringComparison.OrdinalIgnoreCase) &&
!s2.Substring(0, j + 1).EndsWith("</font>", StringComparison.OrdinalIgnoreCase))
2016-03-27 14:41:32 +02:00
{
post.Append(s2[j]);
j--;
}
newLines.Append(preTags);
2016-03-27 14:41:32 +02:00
newLines.Append(ReverseParenthesis(post.ToString()));
newLines.Append(s2.Substring(pre.Length, s2.Length - (pre.Length + post.Length)));
newLines.Append(ReverseParenthesis(ReverseString(pre.ToString())));
newLines.Append(postTags);
2016-03-27 14:41:32 +02:00
newLines.AppendLine();
}
return newLines.ToString().Trim();
}
public static string ReverseNumbers(string s)
{
return TwoOrMoreDigitsNumber.Replace(s, m => ReverseString(m.Value));
}
2017-12-20 20:13:57 +01:00
internal static string ReverseString(string s)
2016-03-27 14:41:32 +02:00
{
2016-11-24 19:05:28 +01:00
int len = s.Length;
if (len <= 1)
{
return s;
}
var chars = new char[len];
2016-11-24 19:05:28 +01:00
for (int i = 0; i < len; i++)
{
chars[i] = s[len - i - 1];
}
return new string(chars);
2016-03-27 14:41:32 +02:00
}
private static string ReverseParenthesis(string s)
{
if (string.IsNullOrEmpty(s))
{
return s;
}
int len = s.Length;
var chars = new char[len];
for (int i = 0; i < len; i++)
{
char ch = s[i];
switch (ch)
{
case '(':
ch = ')';
break;
case ')':
ch = '(';
break;
case '[':
ch = ']';
break;
case ']':
ch = '[';
break;
}
chars[i] = ch;
}
return new string(chars);
2016-03-27 14:41:32 +02:00
}
2016-02-01 20:31:53 +01:00
public static string FixEnglishTextInRightToLeftLanguage(string text, string reverseChars)
{
var sb = new StringBuilder();
var lines = text.SplitToLines();
foreach (string line in lines)
{
string s = ReverseParenthesis(line.Trim());
2016-02-01 20:31:53 +01:00
bool numbersOn = false;
string numbers = string.Empty;
for (int i = 0; i < s.Length; i++)
{
if (numbersOn && reverseChars.Contains(s[i]))
{
numbers = s[i] + numbers;
}
else if (numbersOn)
{
numbersOn = false;
s = s.Remove(i - numbers.Length, numbers.Length).Insert(i - numbers.Length, numbers);
numbers = string.Empty;
}
else if (reverseChars.Contains(s[i]))
{
numbers = s[i] + numbers;
numbersOn = true;
}
}
if (numbersOn)
{
int i = s.Length;
s = s.Remove(i - numbers.Length, numbers.Length).Insert(i - numbers.Length, numbers);
}
sb.AppendLine(s);
}
return sb.ToString().Trim();
}
public static string ToSuperscript(string text)
{
var sb = new StringBuilder();
var superscript = new List<char>{
'⁰',
'¹',
'²',
'³',
'⁴',
'⁵',
'⁶',
'⁷',
'⁸',
'⁹',
'⁺',
'⁻',
'⁼',
'⁽',
'⁾',
'ᵃ',
'ᵇ',
'ᶜ',
'ᵈ',
'ᵉ',
'ᶠ',
'ᵍ',
'ʰ',
'ⁱ',
'ʲ',
'ᵏ',
'ˡ',
'ᵐ',
'ⁿ',
'ᵒ',
'ᵖ',
'ʳ',
'ˢ',
'ᵗ',
'ᵘ',
'ᵛ',
'ʷ',
'ˣ',
'ʸ',
'ᶻ',
'ᴬ',
'ᴮ',
'ᴰ',
'ᴱ',
'ᴳ',
'ᴴ',
'ᴵ',
'ᴶ',
'ᴷ',
'ᴸ',
'ᴹ',
'ᴺ',
'ᴼ',
'ᴾ',
'ᴿ',
'ᵀ',
'ᵁ',
'ᵂ'
};
var normal = new List<char>{
'0', // "⁰"
'1', // "¹"
'2', // "²"
'3', // "³"
'4', // "⁴"
'5', // "⁵"
'6', // "⁶"
'7', // "⁷"
'8', // "⁸"
'9', // "⁹"
'+', // "⁺"
'-', // "⁻"
'=', // "⁼"
'(', // "⁽"
')', // "⁾"
'a', // "ᵃ"
'b', // "ᵇ"
'c', // "ᶜ"
'd', // "ᵈ"
'e', // "ᵉ"
'f', // "ᶠ"
'g', // "ᵍ"
'h', // "ʰ"
'i', // "ⁱ"
'j', // "ʲ"
'k', // "ᵏ"
'l', // "ˡ"
'm', // "ᵐ"
'n', // "ⁿ"
'o', // "ᵒ"
'p', // "ᵖ"
'r', // "ʳ"
's', // "ˢ"
't', // "ᵗ"
'u', // "ᵘ"
'v', // "ᵛ"
'w', // "ʷ"
'x', // "ˣ"
'y', // "ʸ"
'z', // "ᶻ"
'A', // "ᴬ"
'B', // "ᴮ"
'D', // "ᴰ"
'E', // "ᴱ"
'G', // "ᴳ"
'H', // "ᴴ"
'I', // "ᴵ"
'J', // "ᴶ"
'K', // "ᴷ"
'L', // "ᴸ"
'M', // "ᴹ"
'N', // "ᴺ"
'O', // "ᴼ"
'P', // "ᴾ"
'R', // "ᴿ"
'T', // "ᵀ"
'U', // "ᵁ"
'W', // "ᵂ"
};
for (int i = 0; i < text.Length; i++)
{
char s = text[i];
int index = normal.IndexOf(s);
if (index >= 0)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
sb.Append(superscript[index]);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
else
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
sb.Append(s);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
return sb.ToString();
}
public static string ToSubscript(string text)
{
var sb = new StringBuilder();
var subcript = new List<char>{
'₀',
'₁',
'₂',
'₃',
'₄',
'₅',
'₆',
'₇',
'₈',
'₉',
'₊',
'₋',
'₌',
'₍',
'₎',
'ₐ',
'ₑ',
'ᵢ',
'ₒ',
'ᵣ',
'ᵤ',
'ᵥ',
'ₓ',
};
var normal = new List<char>
{
'0', // "₀"
'1', // "₁"
'2', // "₂"
'3', // "₃"
'4', // "₄"
'5', // "₅"
'6', // "₆"
'7', // "₇"
'8', // "₈"
'9', // "₉"
'+', // "₊"
'-', // "₋"
'=', // "₌"
'(', // "₍"
')', // "₎"
'a', // "ₐ"
'e', // "ₑ"
'i', // "ᵢ"
'o', // "ₒ"
'r', // "ᵣ"
'u', // "ᵤ"
'v', // "ᵥ"
'x', // "ₓ"
};
for (int i = 0; i < text.Length; i++)
{
char s = text[i];
int index = normal.IndexOf(s);
if (index >= 0)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
sb.Append(subcript[index]);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
else
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
sb.Append(s);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
return sb.ToString();
}
public static string FixQuotes(string text)
{
if (string.IsNullOrEmpty(text))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return text;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.StartsWith('"') && text.Length > 1)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Substring(1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.EndsWith('"') && text.Length >= 1)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Substring(0, text.Length - 1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
return text.Replace("\"\"", "\"");
}
public static Color GetColorFromFontString(string text, Color defaultColor)
{
string s = text.TrimEnd();
int 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=", StringComparison.OrdinalIgnoreCase))
2016-02-01 20:31:53 +01:00
{
int colorStart = f.IndexOf(" color=", StringComparison.OrdinalIgnoreCase);
if (s.IndexOf('"', colorStart + " color=".Length + 1) > 0)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
end = s.IndexOf('"', colorStart + " color=".Length + 1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
s = s.Substring(colorStart, end - colorStart);
s = s.Replace(" color=", string.Empty);
s = s.Trim('\'').Trim('"').Trim('\'');
try
{
if (s.StartsWith("rgb(", StringComparison.OrdinalIgnoreCase))
2016-02-01 20:31:53 +01:00
{
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 ColorTranslator.FromHtml(s);
}
catch
{
return defaultColor;
}
}
}
}
return defaultColor;
}
public static string[] SplitForChangedCalc(string s, bool ignoreLineBreaks, bool ignoreFormatting, bool breakToLetters)
2016-02-01 20:31:53 +01:00
{
const string endChars = "!?.…:;,#%$£";
2016-02-01 20:31:53 +01:00
var list = new List<string>();
if (ignoreFormatting)
2019-01-19 14:40:37 +01:00
{
s = HtmlUtil.RemoveHtmlTags(s, true);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (breakToLetters)
{
foreach (char ch in s)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
list.Add(ch.ToString());
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
else
{
var word = new StringBuilder();
int i = 0;
while (i < s.Length)
{
2016-02-05 02:43:04 +01:00
if (s.Substring(i).StartsWith(Environment.NewLine, StringComparison.Ordinal))
2016-02-01 20:31:53 +01:00
{
if (word.Length > 0)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
list.Add(word.ToString());
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
word.Clear();
2016-02-05 02:43:04 +01:00
if (!ignoreLineBreaks)
2019-01-19 14:40:37 +01:00
{
2016-02-05 02:43:04 +01:00
list.Add(Environment.NewLine);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
i += Environment.NewLine.Length;
}
else if (s[i] == ' ')
{
if (word.Length > 0)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
list.Add(word.ToString());
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
word.Clear();
i++;
}
else if (endChars.Contains(s[i]) && (word.Length == 0 || endChars.Contains(word[0])))
{
word.Append(s[i]);
i++;
}
else if (endChars.Contains(s[i]))
{
if (word.Length > 0)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
list.Add(word.ToString());
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
word.Clear();
word.Append(s[i]);
i++;
}
else
{
word.Append(s[i]);
i++;
}
}
if (word.Length > 0)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
list.Add(word.ToString());
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
return list.ToArray();
}
public static void GetTotalAndChangedWords(string s1, string s2, ref int total, ref int change, bool ignoreLineBreaks, bool ignoreFormatting, bool breakToLetters)
2016-02-01 20:31:53 +01:00
{
var parts1 = SplitForChangedCalc(s1, ignoreLineBreaks, ignoreFormatting, breakToLetters);
var parts2 = SplitForChangedCalc(s2, ignoreLineBreaks, ignoreFormatting, breakToLetters);
2016-02-01 20:31:53 +01:00
total += Math.Max(parts1.Length, parts2.Length);
change += GetChangesAdvanced(parts1, parts2);
}
private static int GetChangesAdvanced(string[] parts1, string[] parts2)
{
int i1 = 0;
int i2 = 0;
int i = 0;
int c = 0;
2016-02-05 02:34:46 +01:00
var max = Math.Max(parts1.Length, parts2.Length);
while (i < max && i1 < parts1.Length && i2 < parts2.Length)
2016-02-01 20:31:53 +01:00
{
if (parts1[i1] == parts2[i2])
{
i1++;
i2++;
}
else
{
int i1Next = FindNext(parts2[i2], parts1, i1);
int i2Next = FindNext(parts1[i1], parts2, i2);
if (i1Next < i2Next)
{
c += i1Next - i1;
i1 = i1Next + 1;
i2++;
}
else if (i2Next < i1Next)
{
c += i2Next - i2;
i1++;
i2 = i2Next + 1;
}
else
{
i1++;
i2++;
c++;
}
}
i++;
}
if (i1 == parts1.Length && i2 == parts2.Length)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return c;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
return c + Math.Abs(parts1.Length - parts2.Length);
}
private static int FindNext(string s, string[] parts, int startIndex)
{
for (; startIndex < parts.Length; startIndex++)
{
if (s == parts[startIndex])
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return startIndex;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
return int.MaxValue;
}
public static string RemoveNonNumbers(string p)
{
if (string.IsNullOrEmpty(p))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return p;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
var sb = new StringBuilder();
foreach (var c in p)
{
if (char.IsDigit(c))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
sb.Append(c);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
return sb.ToString();
}
private static readonly Regex RemoveSpaceBetweenNumbersRegex = new Regex(@"(?<=\b\d+) \d(?!/\d)", RegexOptions.Compiled);
public static string RemoveSpaceBetweenNumbers(string text)
{
if (!string.IsNullOrEmpty(text))
{
var match = RemoveSpaceBetweenNumbersRegex.Match(text);
while (match.Success)
{
text = text.Remove(match.Index, 1);
match = RemoveSpaceBetweenNumbersRegex.Match(text, match.Index);
}
}
return text;
}
/// <summary>
/// Remove unneeded spaces
/// </summary>
/// <param name="input">text string to remove unneeded spaces from</param>
2016-02-01 20:31:53 +01:00
/// <param name="language">two letter language id string</param>
/// <returns>text with unneeded spaces removed</returns>
public static string RemoveUnneededSpaces(string input, string language)
2016-02-01 20:31:53 +01:00
{
const char zeroWidthSpace = '\u200B';
const char zeroWidthNoBreakSpace = '\uFEFF';
const char noBreakSpace = '\u00A0';
const char operatingSystemCommand = '\u009D';
2016-02-01 20:31:53 +01:00
var text = input.Trim();
int len = text.Length;
int count = 0;
char[] textChars = new char[len];
for (int i = 0; i < len; i++)
{
char ch = text[i];
switch (ch)
{
// Ignore: \u200B, \uFEFF and \u009D.
case zeroWidthSpace:
case zeroWidthNoBreakSpace:
case operatingSystemCommand:
break;
// Replace: \t or \u00A0 with white-space.
case '\t':
case noBreakSpace:
textChars[count++] = ' ';
break;
default:
textChars[count++] = ch;
break;
}
}
// Construct new string from textChars.
text = new string(textChars, 0, count);
2016-02-01 20:31:53 +01:00
text = text.FixExtraSpaces();
if (text.EndsWith(' '))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Substring(0, text.Length - 1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
const string ellipses = "...";
text = text.Replace(". . ..", ellipses);
text = text.Replace(". ...", ellipses);
text = text.Replace(". .. .", ellipses);
text = text.Replace(". . .", ellipses);
text = text.Replace(". ..", ellipses);
text = text.Replace(".. .", ellipses);
// Fix recursive: ...
while (text.Contains("...."))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace("....", ellipses);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
text = text.Replace(" ..." + Environment.NewLine, "..." + Environment.NewLine);
text = text.Replace(Environment.NewLine + "... ", Environment.NewLine + "...");
text = text.Replace(Environment.NewLine + "<i>... ", Environment.NewLine + "<i>...");
text = text.Replace(Environment.NewLine + "- ... ", Environment.NewLine + "- ...");
text = text.Replace(Environment.NewLine + "<i>- ... ", Environment.NewLine + "<i>- ...");
text = text.Replace(Environment.NewLine + "- ... ", Environment.NewLine + "- ...");
if (text.StartsWith("... ", StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Remove(3, 1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.EndsWith(" ...", StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Remove(text.Length - 4, 1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.EndsWith(" ...</i>", StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Remove(text.Length - 8, 1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.StartsWith("- ... ", StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Remove(5, 1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.StartsWith("<i>... ", StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Remove(6, 1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (language != "fr") // special rules for French
{
text = text.Replace("... ?", "...?");
text = text.Replace("... !", "...!");
text = text.Replace(" :", ":");
text = text.Replace(" :", ":");
}
if (!text.Contains("- ..."))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace(" ... ", "... ");
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
while (text.Contains(" ,"))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace(" ,", ",");
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.EndsWith(" .", StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Remove(text.Length - 2, 1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.EndsWith(" \"", StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Remove(text.Length - 2, 1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.Contains(" \"" + Environment.NewLine))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace(" \"" + Environment.NewLine, "\"" + Environment.NewLine);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.Contains(" ." + Environment.NewLine))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace(" ." + Environment.NewLine, "." + Environment.NewLine);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (language != "fr") // special rules for French
{
if (text.Contains(" !"))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace(" !", "!");
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.Contains(" ?"))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace(" ?", "?");
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
if (language == "ar") // special rules for Arabic
{
while (text.Contains(" ؟"))
{
text = text.Replace(" ؟", "؟");
}
while (text.Contains(" \u060C")) // Arabic comma
{
text = text.Replace(" \u060C", "\u060C");
}
while (text.Contains(" و "))
{
text = text.Replace(" و ", " و");
}
if (text.Length > 3 && text[0] == 'و' && text[1] == ' ')
{
text = text.Remove(1, 1);
}
while (text.Contains(" و\""))
{
text = text.Replace(" و\"", "و\"");
}
while (text.Contains("ـ "))
{
text = text.Replace("ـ ", "ـ");
}
}
2016-02-01 20:31:53 +01:00
if (text.Contains(" . "))
{
var regex = new Regex(@"[a-z] \. [A-Z]");
var match = regex.Match(text);
while (match.Success)
{
text = text.Remove(match.Index + 1, 1);
match = regex.Match(text);
}
}
while (text.Contains("¿ "))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace("¿ ", "¿");
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
while (text.Contains("¡ "))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace("¡ ", "¡");
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
// Italic
if (text.Contains("<i>", StringComparison.OrdinalIgnoreCase) && text.Contains("</i>", StringComparison.OrdinalIgnoreCase))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = RemoveSpaceBeforeAfterTag(text, "<i>");
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
// Bold
if (text.Contains("<b>", StringComparison.OrdinalIgnoreCase) && text.Contains("</b>", StringComparison.OrdinalIgnoreCase))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = RemoveSpaceBeforeAfterTag(text, "<b>");
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
// Underline
if (text.Contains("<u>", StringComparison.OrdinalIgnoreCase) && text.Contains("</u>", StringComparison.OrdinalIgnoreCase))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = RemoveSpaceBeforeAfterTag(text, "<u>");
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
// Font
if (text.Contains("<font ", StringComparison.OrdinalIgnoreCase))
{
var idx = text.IndexOf("<font ", StringComparison.OrdinalIgnoreCase);
var endIdx = text.IndexOf('>', idx + 6);
if (endIdx > idx && endIdx < text.Length - 8)
{
var color = text.Substring(idx, (endIdx - idx) + 1).ToLowerInvariant();
2016-02-01 20:31:53 +01:00
text = RemoveSpaceBeforeAfterTag(text, color);
}
}
text = text.Trim();
text = text.Replace(Environment.NewLine + " ", Environment.NewLine);
2020-03-07 13:23:38 +01:00
if (text.Contains("-") && text.Length > 2 && !text.StartsWith("--", StringComparison.Ordinal))
2016-02-01 20:31:53 +01:00
{
var dialogHelper = new DialogSplitMerge { DialogStyle = Configuration.Settings.General.DialogStyle };
text = dialogHelper.RemoveSpaces(text);
2016-02-01 20:31:53 +01:00
int idx = text.IndexOf("- ", 2, StringComparison.Ordinal);
if (text.StartsWith("<i>", StringComparison.OrdinalIgnoreCase))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
idx = text.IndexOf("- ", 5, StringComparison.Ordinal);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
while (idx > 0)
{
if (idx > 0 && idx < text.Length - 2)
{
string before = string.Empty;
int k = idx - 1;
while (k >= 0 && char.IsLetterOrDigit(text[k]))
2016-02-01 20:31:53 +01:00
{
before = text[k--] + before;
2016-02-01 20:31:53 +01:00
}
string after = string.Empty;
k = idx + 2;
while (k < text.Length && char.IsLetter(text[k]))
2016-02-01 20:31:53 +01:00
{
after = after + text[k++];
2016-02-01 20:31:53 +01:00
}
if (after.Length > 0 && after.Equals(before, StringComparison.OrdinalIgnoreCase))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Remove(idx + 1, 1);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
else if (before.Length > 0)
{
if ((language != "en" ||
!after.Equals("and", StringComparison.OrdinalIgnoreCase) &&
!after.Equals("or", StringComparison.OrdinalIgnoreCase)) &&
(language != "es" ||
!after.Equals("y", StringComparison.OrdinalIgnoreCase) &&
!after.Equals("o", StringComparison.OrdinalIgnoreCase)) &&
(language != "da" ||
!after.Equals("og", StringComparison.OrdinalIgnoreCase) &&
!after.Equals("eller", StringComparison.OrdinalIgnoreCase)) &&
(language != "de" ||
!after.Equals("und", StringComparison.OrdinalIgnoreCase) &&
!after.Equals("oder", StringComparison.OrdinalIgnoreCase)) &&
(language != "fi" ||
!after.Equals("ja", StringComparison.OrdinalIgnoreCase) &&
!after.Equals("tai", StringComparison.OrdinalIgnoreCase)) &&
(language != "fr" ||
!after.Equals("et", StringComparison.OrdinalIgnoreCase) &&
!after.Equals("ou", StringComparison.OrdinalIgnoreCase)) &&
(language != "it" ||
!after.Equals("e", StringComparison.OrdinalIgnoreCase) &&
!after.Equals("o", StringComparison.OrdinalIgnoreCase)) &&
(language != "nl" ||
!after.Equals("en", StringComparison.OrdinalIgnoreCase) &&
!after.Equals("of", StringComparison.OrdinalIgnoreCase)) &&
(language != "pl" ||
!after.Equals("i", StringComparison.OrdinalIgnoreCase) &&
!after.Equals("czy", StringComparison.OrdinalIgnoreCase)) &&
(language != "pt" ||
!after.Equals("e", StringComparison.OrdinalIgnoreCase) &&
!after.Equals("ou", StringComparison.OrdinalIgnoreCase)))
2016-02-01 20:31:53 +01:00
{
text = text.Remove(idx + 1, 1);
}
}
}
if (idx + 1 < text.Length && idx != -1)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
idx = text.IndexOf("- ", idx + 1, StringComparison.Ordinal);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
else
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
break;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
}
if (CountTagInText(text, '"') == 2 && text.Contains(" \" "))
{
int idx = text.IndexOf(" \" ", StringComparison.Ordinal);
int idxp = text.IndexOf('"');
//"Foo " bar.
if ((idxp >= 0 && idxp < idx) && char.IsLetterOrDigit(text[idx - 1]) && !" \r\n".Contains(text[idxp + 1]))
2016-02-01 20:31:53 +01:00
{
text = text.Remove(idx, 1);
}
//" Foo " bar.
idx = text.IndexOf(" \" ", StringComparison.Ordinal);
idxp = text.IndexOf('"');
if (idxp >= 0 && idx > idxp)
{
if (text[idxp + 1] == ' ' && char.IsLetterOrDigit(text[idxp + 2]))
2016-02-01 20:31:53 +01:00
{
text = text.Remove(idxp + 1, 1);
idx--;
}
text = text.Remove(idx, 1);
}
}
// Fix spaces after quotes
// e.g: Foobar. " Foobar" => Foobar. "Foobar"
string preText = string.Empty;
if (text.LineStartsWithHtmlTag(true, true))
{
int endIdx = text.IndexOf('>') + 1;
preText = text.Substring(0, endIdx);
text = text.Substring(endIdx);
}
if (text.StartsWith('"'))
{
text = '"' + text.Substring(1).TrimStart();
}
text = preText + text;
// Fix spaces before quotes at line ending
string postText = string.Empty;
if (text.LineEndsWithHtmlTag(true, true))
{
int endIdx = text.LastIndexOf('<');
postText = text.Substring(endIdx);
text = text.Substring(0, endIdx);
}
if (text.EndsWith(" \""))
{
text = text.Remove(text.Length - 2, 1);
}
text = text + postText;
text = text.Replace(". \" ", ". \"");
text = text.Replace("? \" ", "? \"");
text = text.Replace("! \" ", "! \"");
text = text.Replace(") \" ", ") \"");
text = text.Replace("> \" ", "> \"");
while (text.Contains(" . "))
2019-01-19 14:40:37 +01:00
{
text = text.Replace(" . ", ". ");
2019-01-19 14:40:37 +01:00
}
var numberSeparatorNumberMatch = NumberSeparatorNumberRegEx.Match(text);
while (numberSeparatorNumberMatch.Success)
{
var spaceIdx = text.IndexOf(' ', numberSeparatorNumberMatch.Index);
text = text.Remove(spaceIdx, 1);
numberSeparatorNumberMatch = NumberSeparatorNumberRegEx.Match(text);
}
2016-02-01 20:31:53 +01:00
return text;
}
2019-02-08 16:59:49 +01:00
public static string RemoveSpaceBeforeAfterTag(string input, string openTag)
2016-02-01 20:31:53 +01:00
{
2019-02-08 16:59:49 +01:00
var text = HtmlUtil.FixUpperTags(input);
2016-02-01 20:31:53 +01:00
var closeTag = string.Empty;
switch (openTag)
{
case "<i>":
closeTag = "</i>";
break;
case "<b>":
closeTag = "</b>";
break;
case "<u>":
closeTag = "</u>";
break;
}
if (closeTag.Length == 0 && openTag.Contains("<font ", StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
closeTag = "</font>";
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
// Open tags
var open1 = openTag + " ";
var open2 = Environment.NewLine + openTag + " ";
var open3 = openTag + Environment.NewLine;
// Closing tags
var close1 = "! " + closeTag + Environment.NewLine;
var close2 = "? " + closeTag + Environment.NewLine;
var close3 = " " + closeTag;
var close4 = " " + closeTag + Environment.NewLine;
var close5 = Environment.NewLine + closeTag;
if (text.Contains(close1, StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace(close1, "!" + closeTag + Environment.NewLine);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.Contains(close2, StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace(close2, "?" + closeTag + Environment.NewLine);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.EndsWith(close3, StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Substring(0, text.Length - close3.Length) + closeTag;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.Contains(close4))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace(close4, closeTag + Environment.NewLine);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
// e.g: ! </i><br>Foobar
if (text.StartsWith(open1, StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = openTag + text.Substring(open1.Length);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
// e.g.: <i>\r\n
if (text.StartsWith(open3, StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Remove(openTag.Length, Environment.NewLine.Length);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
// e.g.: \r\n</i>
if (text.EndsWith(close5, StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Remove(text.Length - openTag.Length - Environment.NewLine.Length - 1, Environment.NewLine.Length);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
if (text.Contains(open2, StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = text.Replace(open2, Environment.NewLine + openTag);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
// Hi <i> bad</i> man! -> Hi <i>bad</i> man!
text = text.Replace(" " + openTag + " ", " " + openTag);
text = text.Replace(Environment.NewLine + openTag + " ", Environment.NewLine + openTag);
// Hi <i>bad </i> man! -> Hi <i>bad</i> man!
text = text.Replace(" " + closeTag + " ", closeTag + " ");
text = text.Replace(" " + closeTag + Environment.NewLine, closeTag + Environment.NewLine);
text = text.Trim();
if (text.StartsWith(open1, StringComparison.Ordinal))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
text = openTag + text.Substring(open1.Length);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
return text;
}
/// <summary>
/// Creates a task that will complete after a time delay.
/// </summary>
/// <param name="millisecondsDelay">The number of milliseconds to wait before completing the returned task.</param>
/// <returns>A task that represents the time delay.</returns>
public static Task TaskDelay(int millisecondsDelay)
{
var tcs = new TaskCompletionSource<object>();
var t = new System.Threading.Timer(_ => tcs.SetResult(null));
t.Change(millisecondsDelay, -1);
return tcs.Task;
}
public static SubtitleFormat LoadMatroskaTextSubtitle(MatroskaTrackInfo matroskaSubtitleInfo, MatroskaFile matroska, List<MatroskaSubtitle> sub, Subtitle subtitle)
{
if (subtitle == null)
2019-01-19 14:40:37 +01:00
{
2017-03-28 18:52:18 +02:00
throw new ArgumentNullException(nameof(subtitle));
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
subtitle.Paragraphs.Clear();
var isSsa = false;
SubtitleFormat format = new SubRip();
var codecPrivate = matroskaSubtitleInfo.GetCodecPrivate();
if (codecPrivate.Contains("[script info]", StringComparison.OrdinalIgnoreCase))
2016-02-01 20:31:53 +01:00
{
if (codecPrivate.Contains("[V4 Styles]", StringComparison.OrdinalIgnoreCase))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
format = new SubStationAlpha();
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
else
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
format = new AdvancedSubStationAlpha();
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
isSsa = true;
}
if (isSsa)
{
foreach (var p in LoadMatroskaSSA(matroskaSubtitleInfo, matroska.Path, format, sub).Paragraphs)
{
subtitle.Paragraphs.Add(p);
}
if (!string.IsNullOrEmpty(codecPrivate))
2016-02-01 20:31:53 +01:00
{
bool eventsStarted = false;
bool fontsStarted = false;
bool graphicsStarted = false;
var header = new StringBuilder();
foreach (string line in codecPrivate.Replace(Environment.NewLine, "\n").Split('\n'))
2016-02-01 20:31:53 +01:00
{
if (!eventsStarted && !fontsStarted && !graphicsStarted)
{
header.AppendLine(line);
}
if (line.TrimStart().StartsWith("dialog:", StringComparison.OrdinalIgnoreCase))
{
eventsStarted = true;
fontsStarted = false;
graphicsStarted = false;
}
else if (line.Trim().Equals("[events]", StringComparison.OrdinalIgnoreCase))
{
eventsStarted = true;
fontsStarted = false;
graphicsStarted = false;
}
else if (line.Trim().Equals("[fonts]", StringComparison.OrdinalIgnoreCase))
{
eventsStarted = false;
fontsStarted = true;
graphicsStarted = false;
}
else if (line.Trim().Equals("[graphics]", StringComparison.OrdinalIgnoreCase))
{
eventsStarted = false;
fontsStarted = false;
graphicsStarted = true;
}
}
subtitle.Header = header.ToString().TrimEnd();
if (!subtitle.Header.Contains("[events]", StringComparison.OrdinalIgnoreCase))
{
subtitle.Header += Environment.NewLine + Environment.NewLine + "[Events]" + Environment.NewLine;
}
}
}
else
{
foreach (var p in sub)
{
subtitle.Paragraphs.Add(new Paragraph(p.GetText(matroskaSubtitleInfo), p.Start, p.End));
2016-02-01 20:31:53 +01:00
}
}
subtitle.Renumber();
return format;
}
public static Subtitle LoadMatroskaSSA(MatroskaTrackInfo matroskaSubtitleInfo, string fileName, SubtitleFormat format, List<MatroskaSubtitle> sub)
{
var codecPrivate = matroskaSubtitleInfo.GetCodecPrivate();
var subtitle = new Subtitle { Header = codecPrivate };
var lines = subtitle.Header.Trim().SplitToLines();
2016-02-01 20:31:53 +01:00
var footer = new StringBuilder();
var comments = new Subtitle();
if (!string.IsNullOrEmpty(codecPrivate))
2016-02-01 20:31:53 +01:00
{
bool footerOn = false;
2016-02-14 20:23:11 +01:00
char[] splitChars = { ':', '.' };
2016-02-01 20:31:53 +01:00
foreach (string line in lines)
{
if (footerOn)
{
footer.AppendLine(line);
}
else if (line.Trim() == "[Events]")
{
}
else if (line.Trim() == "[Fonts]" || line.Trim() == "[Graphics]")
{
footerOn = true;
footer.AppendLine();
footer.AppendLine();
footer.AppendLine(line);
}
else if (line.StartsWith("Comment:", StringComparison.Ordinal))
{
var arr = line.Split(',');
if (arr.Length > 3)
{
2016-02-14 20:23:11 +01:00
arr = arr[1].Split(splitChars);
2016-02-01 20:31:53 +01:00
if (arr.Length == 4)
{
if (int.TryParse(arr[0], out var hour) && int.TryParse(arr[1], out var min) &&
int.TryParse(arr[2], out var sec) && int.TryParse(arr[3], out var ms))
2016-02-01 20:31:53 +01:00
{
comments.Paragraphs.Add(new Paragraph(new TimeCode(hour, min, sec, ms * 10), new TimeCode(), line));
2016-02-01 20:31:53 +01:00
}
}
}
}
}
}
const string headerFormat = "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text";
if (!subtitle.Header.Contains("[Events]"))
{
subtitle.Header = subtitle.Header.Trim() + Environment.NewLine +
Environment.NewLine +
"[Events]" + Environment.NewLine +
headerFormat + Environment.NewLine;
}
else if (subtitle.Header.LastIndexOf("Format:", StringComparison.Ordinal) < subtitle.Header.IndexOf("[Events]", StringComparison.Ordinal))
2016-02-01 20:31:53 +01:00
{
subtitle.Header = subtitle.Header.Remove(subtitle.Header.IndexOf("[Events]", StringComparison.Ordinal));
subtitle.Header = subtitle.Header.Trim() + Environment.NewLine +
Environment.NewLine +
"[Events]" + Environment.NewLine +
headerFormat + Environment.NewLine;
}
else
{
subtitle.Header = subtitle.Header.Trim() + Environment.NewLine;
}
2016-02-01 20:31:53 +01:00
lines = new List<string>();
foreach (string l in subtitle.Header.Trim().SplitToLines())
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
lines.Add(l);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
const string timeCodeFormat = "{0}:{1:00}:{2:00}.{3:00}"; // h:mm:ss.cc
foreach (var mp in sub)
{
var p = new Paragraph(string.Empty, mp.Start, mp.End);
string start = string.Format(timeCodeFormat, p.StartTime.Hours, p.StartTime.Minutes, p.StartTime.Seconds, p.StartTime.Milliseconds / 10);
string end = string.Format(timeCodeFormat, p.EndTime.Hours, p.EndTime.Minutes, p.EndTime.Seconds, p.EndTime.Milliseconds / 10);
//MKS contains this: ReadOrder, Layer, Style, Name, MarginL, MarginR, MarginV, Effect, Text
for (int commentIndex = 0; commentIndex < comments.Paragraphs.Count; commentIndex++)
{
var cp = comments.Paragraphs[commentIndex];
if (cp.StartTime.TotalMilliseconds <= p.StartTime.TotalMilliseconds)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
lines.Add(cp.Text);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
for (int commentIndex = comments.Paragraphs.Count - 1; commentIndex >= 0; commentIndex--)
{
var cp = comments.Paragraphs[commentIndex];
if (cp.StartTime.TotalMilliseconds <= p.StartTime.TotalMilliseconds)
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
comments.Paragraphs.RemoveAt(commentIndex);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
}
2018-02-03 23:02:30 +01:00
string text = mp.GetText(matroskaSubtitleInfo).Replace(Environment.NewLine, "\\N");
2016-02-01 20:31:53 +01:00
int idx = text.IndexOf(',') + 1;
if (idx > 0 && idx < text.Length)
{
text = text.Remove(0, idx); // remove ReadOrder
idx = text.IndexOf(',');
text = text.Insert(idx, "," + start + "," + end);
lines.Add("Dialogue: " + text);
}
}
for (int commentIndex = 0; commentIndex < comments.Paragraphs.Count; commentIndex++)
{
var cp = comments.Paragraphs[commentIndex];
lines.Add(cp.Text);
}
foreach (string l in footer.ToString().SplitToLines())
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
lines.Add(l);
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
format.LoadSubtitle(subtitle, lines, fileName);
return subtitle;
}
public static int GetNumberOfLines(string text)
{
if (string.IsNullOrEmpty(text))
2019-01-19 14:40:37 +01:00
{
2016-02-01 20:31:53 +01:00
return 0;
2019-01-19 14:40:37 +01:00
}
2016-02-01 20:31:53 +01:00
int lines = 1;
int idx = text.IndexOf('\n');
while (idx >= 0)
{
lines++;
idx = text.IndexOf('\n', idx + 1);
}
return lines;
}
public static bool QualifiesForMerge(Paragraph p, Paragraph next, double maximumMillisecondsBetweenLines, int maximumTotalLength, bool onlyContinuationLines)
{
if (p?.Text != null && next?.Text != null)
{
var s = HtmlUtil.RemoveHtmlTags(p.Text.Trim(), true);
var nextText = HtmlUtil.RemoveHtmlTags(next.Text.Trim(), true);
if (s.Length + nextText.Length < maximumTotalLength && next.StartTime.TotalMilliseconds - p.EndTime.TotalMilliseconds < maximumMillisecondsBetweenLines)
{
if (string.IsNullOrEmpty(s))
2019-01-19 14:40:37 +01:00
{
return true;
2019-01-19 14:40:37 +01:00
}
bool isLineContinuation = s.EndsWith(',') ||
s.EndsWith('-') ||
s.EndsWith("...", StringComparison.Ordinal) ||
s.EndsWith("…", StringComparison.Ordinal) || // Unicode Character 'HORIZONTAL ELLIPSIS' (U+2026)
AllLettersAndNumbers.Contains(s.Substring(s.Length - 1));
if (!onlyContinuationLines)
2019-01-19 14:40:37 +01:00
{
return true;
2019-01-19 14:40:37 +01:00
}
return isLineContinuation;
}
}
return false;
}
public static string GetPathAndFileNameWithoutExtension(string fileName)
{
if (string.IsNullOrEmpty(fileName))
2019-01-19 14:40:37 +01:00
{
return fileName;
2019-01-19 14:40:37 +01:00
}
var indexOfPeriod = fileName.LastIndexOf('.');
if (indexOfPeriod > 0 && fileName.LastIndexOf(Path.DirectorySeparatorChar) < indexOfPeriod)
2019-01-19 14:40:37 +01:00
{
return fileName.Substring(0, indexOfPeriod);
2019-01-19 14:40:37 +01:00
}
return fileName;
}
public static string GetFileNameWithoutExtension(string fileName)
{
if (string.IsNullOrEmpty(fileName))
{
return fileName;
}
var indexOfDirectorySeparatorChar = fileName.LastIndexOf(Path.DirectorySeparatorChar);
if (indexOfDirectorySeparatorChar >= 0)
{
fileName = fileName.Remove(0, indexOfDirectorySeparatorChar).TrimStart(Path.DirectorySeparatorChar);
}
var indexOfPeriod = fileName.LastIndexOf('.');
if (indexOfPeriod > 0)
{
return fileName.Substring(0, indexOfPeriod);
}
return fileName;
}
public static string ReSplit(string text, int selectionStart)
{
if (string.IsNullOrWhiteSpace(text) || !text.Contains(" ") || selectionStart == 0)
{
return text;
}
var sb = new StringBuilder();
var isFixed = false;
for (int i = 0; i < text.Length; i++)
{
var ch = text[i];
if (!isFixed && ch == ' ' && (i > 0 && i + 1 == selectionStart || i >= selectionStart && ch == ' '))
{
sb.Append(Environment.NewLine);
isFixed = true;
}
sb.Append(ch == '\r' || ch == '\n' ? ' ' : ch);
}
if (!isFixed)
{
return text;
}
return sb.ToString().Replace(" ", " ").Replace(Environment.NewLine + " ", Environment.NewLine);
}
2019-03-13 17:53:09 +01:00
public static string FixRtlViaUnicodeChars(string input)
{
string rtl = "\u202B";
var text = input.Replace(rtl, string.Empty);
text = rtl + text.Replace(Environment.NewLine, Environment.NewLine + rtl);
return text;
}
2016-02-01 20:31:53 +01:00
}
}