using Nikse.SubtitleEdit.Core.ContainerFormats; using Nikse.SubtitleEdit.Core.ContainerFormats.Matroska; using Nikse.SubtitleEdit.Core.ContainerFormats.Mp4; using Nikse.SubtitleEdit.Core.SubtitleFormats; using System; using System.Collections.Generic; using System.Drawing; using System.Globalization; using System.IO; using System.Net; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows.Forms; using System.Xml; namespace Nikse.SubtitleEdit.Core { public static class Utilities { public const string WinXP2KUnicodeFontName = "Times New Roman"; /// /// Cached environment new line characters for faster lookup. /// public static readonly char[] NewLineChars = Environment.NewLine.ToCharArray(); public static VideoInfo TryReadVideoInfoViaMatroskaHeader(string fileName) { var info = new VideoInfo { Success = false }; MatroskaFile matroska = null; try { matroska = new MatroskaFile(fileName); if (matroska.IsValid) { double frameRate; int width; int height; double milliseconds; string videoCodec; matroska.GetInfo(out frameRate, out width, out height, out milliseconds, out videoCodec); info.Width = width; info.Height = height; info.FramesPerSecond = frameRate; info.Success = true; info.TotalMilliseconds = milliseconds; info.TotalSeconds = milliseconds / TimeCode.BaseUnit; info.TotalFrames = info.TotalSeconds * frameRate; info.VideoCodec = videoCodec; } } catch (Exception ex) { MessageBox.Show(ex.Message); } finally { if (matroska != null) { matroska.Dispose(); } } return info; } public static VideoInfo TryReadVideoInfoViaAviHeader(string fileName) { var info = new VideoInfo { Success = false }; try { using (var rp = new RiffParser()) { var dh = new RiffDecodeHeader(rp); rp.OpenFile(fileName); info.FileType = RiffParser.FromFourCC(rp.FileType); if (RiffParser.ckidAVI == rp.FileType) { dh.ProcessMainAVI(); info.Width = dh.Width; info.Height = dh.Height; info.FramesPerSecond = dh.FrameRate; info.TotalFrames = dh.TotalFrames; info.TotalMilliseconds = dh.TotalMilliseconds; info.TotalSeconds = info.TotalMilliseconds / TimeCode.BaseUnit; info.VideoCodec = dh.VideoHandler; info.Success = true; } } } catch { } return info; } public static VideoInfo TryReadVideoInfoViaMp4(string fileName) { var info = new VideoInfo { Success = false }; try { var mp4Parser = new MP4Parser(fileName); if (mp4Parser.Moov != null && mp4Parser.VideoResolution.X > 0) { info.Width = mp4Parser.VideoResolution.X; info.Height = mp4Parser.VideoResolution.Y; info.TotalMilliseconds = mp4Parser.Duration.TotalSeconds; info.VideoCodec = "MP4"; info.FramesPerSecond = mp4Parser.FrameRate; info.Success = true; } } catch { } return info; } public static List GetMovieFileExtensions() { return new List { ".avi", ".mkv", ".wmv", ".mpg", ".mpeg", ".divx", ".mp4", ".asf", ".flv", ".mov", ".m4v", ".vob", ".ogv", ".webm", ".ts", ".m2ts", ".avs", ".mxf" }; } public static string GetVideoFileFilter(bool includeAudioFiles) { var sb = new StringBuilder(); sb.Append(Configuration.Settings.Language.General.VideoFiles + "|"); int i = 0; foreach (string extension in GetMovieFileExtensions()) { if (i > 0) sb.Append(';'); sb.Append('*'); sb.Append(extension); i++; } if (includeAudioFiles) { sb.Append('|'); sb.Append(Configuration.Settings.Language.General.AudioFiles); sb.Append("|*.mp3;*.wav;*.wma;*.ogg;*.mpa;*.m4a;*.ape;*.aiff;*.flac;*.aac;*.mka"); } sb.Append('|'); sb.Append(Configuration.Settings.Language.General.AllFiles); sb.Append("|*.*"); return sb.ToString(); } public static bool IsInteger(string s) { int i; return int.TryParse(s, out i); } public static SubtitleFormat GetSubtitleFormatByFriendlyName(string friendlyName) { foreach (SubtitleFormat format in SubtitleFormat.AllSubtitleFormats) { if (format.FriendlyName == friendlyName || format.Name == friendlyName) return format; } return null; } public static string FormatBytesToDisplayFileSize(long fileSize) { if (fileSize <= 1024) return string.Format("{0} bytes", fileSize); if (fileSize <= 1024 * 1024) return string.Format("{0} kb", fileSize / 1024); if (fileSize <= 1024 * 1024 * 1024) return string.Format("{0:0.0} mb", (float)fileSize / (1024 * 1024)); return string.Format("{0:0.0} gb", (float)fileSize / (1024 * 1024 * 1024)); } /// /// Downloads the requested resource as a using the configured . /// /// A containing the URI to download. /// Encoding for source text /// A containing the requested resource. public static string DownloadString(string address, Encoding encoding = null) { using (var wc = new WebClient()) { wc.Proxy = GetProxy(); if (encoding != null) wc.Encoding = encoding; return wc.DownloadString(address).Trim(); } } 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)) proxy.Credentials = new NetworkCredential(Configuration.Settings.Proxy.UserName, Configuration.Settings.Proxy.DecodePassword()); else proxy.Credentials = new NetworkCredential(Configuration.Settings.Proxy.UserName, Configuration.Settings.Proxy.DecodePassword(), Configuration.Settings.Proxy.Domain); } else proxy.UseDefaultCredentials = true; return proxy; } return null; } private static bool IsPartOfNumber(string s, int position) { if (string.IsNullOrWhiteSpace(s) || position + 1 >= s.Length) return false; if (position > 0 && @",.".Contains(s[position])) { return char.IsDigit(s[position - 1]) && char.IsDigit(s[position + 1]); } return false; } public static bool IsBetweenNumbers(string s, int position) { if (string.IsNullOrEmpty(s) || position < 1 || position + 2 > s.Length) return false; 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.Tools.MergeLinesShorterThan, language); } public static string AutoBreakLine(string text) { return AutoBreakLine(text, string.Empty); // no language } private static bool CanBreak(string s, int index, string language) { char nextChar = ' '; if (index >= 0 && index < s.Length) nextChar = s[index]; else return false; if (!"\r\n\t ".Contains(nextChar)) return false; // 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)) return false; } } if (s2.EndsWith("? -", StringComparison.Ordinal) || s2.EndsWith("! -", StringComparison.Ordinal) || s2.EndsWith(". -", StringComparison.Ordinal)) return false; return true; } private static string _lastNoBreakAfterListLanguage; private static List _lastNoBreakAfterList = new List(); private static IEnumerable NoBreakAfterList(string languageName) { if (string.IsNullOrEmpty(languageName)) return new List(); if (languageName == _lastNoBreakAfterListLanguage) return _lastNoBreakAfterList; _lastNoBreakAfterList = new List(); //load words via xml string noBreakAfterFileName = DictionaryFolder + languageName + "_NoBreakAfterList.xml"; var doc = new XmlDocument(); if (File.Exists(noBreakAfterFileName)) { doc.Load(noBreakAfterFileName); foreach (XmlNode node in doc.DocumentElement) { if (!string.IsNullOrEmpty(node.InnerText)) { if (node.Attributes["RegEx"] != null && node.Attributes["RegEx"].InnerText.Equals("true", StringComparison.OrdinalIgnoreCase)) { Regex r = new Regex(node.InnerText, RegexOptions.Compiled); _lastNoBreakAfterList.Add(new NoBreakAfterItem(r, node.InnerText)); } else { _lastNoBreakAfterList.Add(new NoBreakAfterItem(node.InnerText)); } } } } _lastNoBreakAfterListLanguage = languageName; return _lastNoBreakAfterList; } public static string AutoBreakLineMoreThanTwoLines(string text, int maximumLineLength, string language) { if (text == null || text.Length < 3) return text; string s = AutoBreakLine(text, 0, 0, language); var arr = s.SplitToLines(); if ((arr.Length < 2 && arr[0].Length <= maximumLineLength) || (arr[0].Length <= maximumLineLength && arr[1].Length <= maximumLineLength)) return s; s = RemoveLineBreaks(s); var htmlTags = new Dictionary(); var sb = new StringBuilder(s.Length); int six = 0; while (six < s.Length) { var letter = s[six]; var tagFound = letter == '<' && (s.Substring(six).StartsWith("', six + 1); if (tagFound && endIndex > 0) { string tag = s.Substring(six, endIndex - six + 1); s = s.Remove(six, tag.Length); if (htmlTags.ContainsKey(six)) htmlTags[six] = htmlTags[six] + tag; else htmlTags.Add(six, tag); } else { sb.Append(letter); six++; } } s = sb.ToString(); var words = s.Split(' '); for (int numberOfLines = 3; numberOfLines < 9999; numberOfLines++) { int average = s.Length / numberOfLines + 1; for (int len = average; len < maximumLineLength; len++) { List list = SplitToX(words, numberOfLines, len); bool allOk = true; foreach (var lineLength in list) { if (lineLength > maximumLineLength) allOk = false; } if (allOk) { int index = 0; foreach (var item in list) { index += item; htmlTags.Add(index, Environment.NewLine); } s = ReInsertHtmlTags(s, htmlTags); s = s.Replace(" " + Environment.NewLine, Environment.NewLine); s = s.Replace(Environment.NewLine + " ", Environment.NewLine); s = s.Replace(Environment.NewLine + "", "" + Environment.NewLine); s = s.Replace(Environment.NewLine + "", "" + Environment.NewLine); s = s.Replace(Environment.NewLine + "", "" + Environment.NewLine); s = s.Replace(Environment.NewLine + "", "" + Environment.NewLine); return s.TrimEnd(); } } } return text; } private static List SplitToX(string[] words, int count, int average) { var list = new List(); 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) list.Add(currentCount); else list[list.Count - 1] += currentCount; return list; } public static string AutoBreakLine(string text, int maximumLength, int mergeLinesShorterThan, string language) { if (text == null || text.Length < 3) return text; // do not autobreak dialogs if (text.Contains('-') && text.Contains(Environment.NewLine)) { var noTagLines = HtmlUtil.RemoveHtmlTags(text, true).SplitToLines(); if (noTagLines.Length == 2) { var arr0 = noTagLines[0].Trim().TrimEnd('"', '\'').TrimEnd(); if (arr0.StartsWith('-') && noTagLines[1].TrimStart().StartsWith('-') && arr0.Length > 1 && ".?!)]".Contains(arr0[arr0.Length - 1]) || arr0.EndsWith("--", StringComparison.Ordinal) || arr0.EndsWith('–')) return text; } } string s = RemoveLineBreaks(text); string noTagText = HtmlUtil.RemoveHtmlTags(s, true); if (noTagText.Length < mergeLinesShorterThan) { var noTagLines = noTagText.SplitToLines(); if (noTagLines.Length > 1) { bool isDialog = true; foreach (string line in noTagLines) { var noTagLine = line.TrimStart(); isDialog = isDialog && (noTagLine.StartsWith('-') || noTagLine.StartsWith('—')); } if (isDialog) { return text; } } return s; } var htmlTags = new Dictionary(); var sb = new StringBuilder(); int six = 0; while (six < s.Length) { var letter = s[six]; var tagFound = letter == '<' && (s.Substring(six).StartsWith("', six + 1); if (tagFound && endIndex > 0) { string tag = s.Substring(six, endIndex - six + 1); s = s.Remove(six, tag.Length); if (htmlTags.ContainsKey(six)) htmlTags[six] = htmlTags[six] + tag; else htmlTags.Add(six, tag); } else { sb.Append(letter); six++; } } s = sb.ToString(); int splitPos = -1; int mid = s.Length / 2; // try to find " - " with uppercase letter after (dialog) if (s.Contains(" - ")) { for (int j = 0; j <= (maximumLength / 2) + 5; j++) { if (mid + j + 4 < s.Length) { if (s[mid + j] == '-' && s[mid + j + 1] == ' ' && s[mid + j - 1] == ' ') { string rest = s.Substring(mid + j + 1).TrimStart(); if (rest.Length > 0 && char.IsUpper(rest[0])) { splitPos = mid + j; break; } } } if (mid - (j + 1) > 4) { if (s[mid - j] == '-' && s[mid - j + 1] == ' ' && s[mid - j - 1] == ' ') { string rest = s.Substring(mid - j + 1).TrimStart(); if (rest.Length > 0 && char.IsUpper(rest[0])) { if (mid - j > 5 && s[mid - j - 1] == ' ' && @"!?.".Contains(s[mid - j - 2])) { splitPos = mid - j; break; } } } } } } if (splitPos == maximumLength + 1 && s[maximumLength] != ' ') // only allow space for last char (as it does not count) splitPos = -1; if (splitPos < 0) { const string expectedChars1 = ".!?0123456789"; const string expectedChars2 = ".!?"; for (int j = 0; j < 15; j++) { if (mid + j + 1 < s.Length && mid + j > 0) { if (expectedChars2.Contains(s[mid + j]) && !IsPartOfNumber(s, mid + j) && CanBreak(s, mid + j + 1, language)) { splitPos = mid + j + 1; if (expectedChars1.Contains(s[splitPos])) { // do not break double/tripple end lines like "!!!" or "..." splitPos++; if (expectedChars1.Contains(s[mid + j + 1])) splitPos++; } break; } if (expectedChars2.Contains(s[mid - j]) && !IsPartOfNumber(s, mid - j) && CanBreak(s, mid - j, language)) { splitPos = mid - j; splitPos++; break; } } } } if (splitPos > maximumLength) // too long first line { if (splitPos != maximumLength + 1 || s[maximumLength] != ' ') // allow for maxlength+1 char to be space (does not count) splitPos = -1; } else if (splitPos >= 0 && s.Length - splitPos > maximumLength) // too long second line { splitPos = -1; } if (splitPos < 0) { const string expectedChars1 = ".!?, "; const string expectedChars2 = " .!?"; const string expectedChars3 = ".!?"; for (int j = 0; j < 25; j++) { if (mid + j + 1 < s.Length && mid + j > 0) { if (expectedChars1.Contains(s[mid + j]) && !IsPartOfNumber(s, mid + j) && s.Length > mid + j + 2 && CanBreak(s, mid + j, language)) { splitPos = mid + j; if (expectedChars2.Contains(s[mid + j + 1])) { splitPos++; if (expectedChars2.Contains(s[mid + j + 2])) splitPos++; } break; } if (expectedChars1.Contains(s[mid - j]) && !IsPartOfNumber(s, mid - j) && s.Length > mid + j + 2 && CanBreak(s, mid - j, language)) { splitPos = mid - j; if (expectedChars3.Contains(s[splitPos])) splitPos--; if (expectedChars3.Contains(s[splitPos])) splitPos--; if (expectedChars3.Contains(s[splitPos])) splitPos--; break; } } } } if (splitPos < 0) { splitPos = mid; s = s.Insert(mid - 1, Environment.NewLine); s = ReInsertHtmlTags(s, htmlTags); htmlTags = new Dictionary(); s = s.Replace(Environment.NewLine, "-"); } if (splitPos < s.Length - 2) s = s.Substring(0, splitPos) + Environment.NewLine + s.Substring(splitPos); s = ReInsertHtmlTags(s, htmlTags); s = s.Replace(" " + Environment.NewLine, Environment.NewLine); s = s.Replace(Environment.NewLine + " ", Environment.NewLine); return s.TrimEnd(); } public static string RemoveLineBreaks(string s) { s = HtmlUtil.FixUpperTags(s); s = s.Replace(Environment.NewLine + "", "" + Environment.NewLine); s = s.Replace(Environment.NewLine + "", "" + Environment.NewLine); s = s.Replace(Environment.NewLine + "", "" + Environment.NewLine); s = s.Replace(Environment.NewLine + "", "" + Environment.NewLine); s = s.Replace(" " + Environment.NewLine + "", " "); s = s.Replace("" + Environment.NewLine + " ", " "); s = s.Replace("" + Environment.NewLine + "", " "); s = s.Replace(Environment.NewLine, " "); s = s.Replace(" ", " "); s = s.Replace(" ", " "); s = s.Replace(" ", " "); s = s.Replace(" ", " "); s = s.FixExtraSpaces(); return s.Trim(); } private static string ReInsertHtmlTags(string s, Dictionary 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++; } } if (htmlTags.ContainsKey(six)) { sb.Append(htmlTags[six]); } return sb.ToString(); } return s; } public static string UnbreakLine(string text) { if (!text.Contains(Environment.NewLine)) return text; var singleLine = text.Replace(Environment.NewLine, " "); while (singleLine.Contains(" ")) singleLine = singleLine.Replace(" ", " "); if (singleLine.Contains(" ", " "); singleLine = singleLine.Replace("", " "); singleLine = singleLine.Replace(" ", " "); singleLine = singleLine.Replace("", " "); singleLine = singleLine.Replace(" ", " "); singleLine = singleLine.Replace("", " "); } return singleLine; } public static void InitializeSubtitleFont(Control control) { var gs = Configuration.Settings.General; if (string.IsNullOrEmpty(gs.SubtitleFontName)) gs.SubtitleFontName = "Tahoma"; try { if (gs.SubtitleFontBold) control.Font = new Font(gs.SubtitleFontName, gs.SubtitleFontSize, FontStyle.Bold); else control.Font = new Font(gs.SubtitleFontName, gs.SubtitleFontSize); control.BackColor = gs.SubtitleBackgroundColor; control.ForeColor = gs.SubtitleFontColor; } catch { } } public static string RemoveSsaTags(string s) { int k = s.IndexOf('{'); while (k >= 0) { int l = s.IndexOf('}', k); if (l > k) { s = s.Remove(k, l - k + 1); if (s.Length > 1 && s.Length > k) k = s.IndexOf('{', k); else break; } else { break; } } return s; } public static Encoding GetEncodingFromFile(string fileName) { Encoding encoding = Encoding.Default; try { foreach (EncodingInfo ei in Encoding.GetEncodings()) { if (ei.CodePage + ": " + ei.DisplayName == Configuration.Settings.General.DefaultEncoding && ei.Name != Encoding.UTF8.BodyName && ei.Name != Encoding.Unicode.BodyName) { encoding = ei.GetEncoding(); break; } } using (var file = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { var bom = new byte[12]; // Get the byte-order mark, if there is one file.Position = 0; file.Read(bom, 0, 12); if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) encoding = Encoding.UTF8; else if (bom[0] == 0xff && bom[1] == 0xfe) encoding = Encoding.Unicode; else if (bom[0] == 0xfe && bom[1] == 0xff) // utf-16 and ucs-2 encoding = Encoding.BigEndianUnicode; else if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) // ucs-4 encoding = Encoding.UTF32; else if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76 && (bom[3] == 0x38 || bom[3] == 0x39 || bom[3] == 0x2b || bom[3] == 0x2f)) // utf-7 encoding = Encoding.UTF7; else if (file.Length > 12) { long length = file.Length; if (length > 500000) length = 500000; file.Position = 0; var buffer = new byte[length]; file.Read(buffer, 0, (int)length); bool couldBeUtf8; if (IsUtf8(buffer, out couldBeUtf8)) { encoding = Encoding.UTF8; } else if (couldBeUtf8 && Configuration.Settings.General.DefaultEncoding == Encoding.UTF8.BodyName) { // keep utf-8 encoding if it's default encoding = Encoding.UTF8; } else if (couldBeUtf8 && fileName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase) && Encoding.Default.GetString(buffer).ToLower().Replace('\'', '"').Contains("encoding=\"utf-8\"")) { // keep utf-8 encoding for xml files with utf-8 in header (without any utf-8 encoded characters, but with only allowed utf-8 characters) encoding = Encoding.UTF8; } else if (Configuration.Settings.General.AutoGuessAnsiEncoding) { encoding = DetectAnsiEncoding(buffer); Encoding greekEncoding = Encoding.GetEncoding(1253); // Greek if (GetCount(greekEncoding.GetString(buffer), AutoDetectWordsGreek) > 5) return greekEncoding; Encoding russianEncoding = Encoding.GetEncoding(1251); // Cyrillic if (GetCount(russianEncoding.GetString(buffer), "что", "быть", "весь", "этот", "один", "такой") > 5) // Russian return russianEncoding; if (GetCount(russianEncoding.GetString(buffer), "Какво", "тук", "може", "Как", "Ваше", "какво") > 5) // Bulgarian return russianEncoding; russianEncoding = Encoding.GetEncoding(28595); // Russian if (GetCount(russianEncoding.GetString(buffer), "что", "быть", "весь", "этот", "один", "такой") > 5) return russianEncoding; Encoding thaiEncoding = Encoding.GetEncoding(874); // Thai if (GetCount(thaiEncoding.GetString(buffer), "โอ", "โรเบิร์ต", "วิตตอเรีย", "ดร", "คุณตำรวจ", "ราเชล") + GetCount(thaiEncoding.GetString(buffer), "ไม่", "เลดดิส", "พระเจ้า", "เท็ดดี้", "หัวหน้า", "แอนดรูว์") > 5) return thaiEncoding; Encoding arabicEncoding = Encoding.GetEncoding(28596); // Arabic Encoding hewbrewEncoding = Encoding.GetEncoding(28598); // Hebrew if (GetCount(arabicEncoding.GetString(buffer), "من", "هل", "لا", "فى", "لقد", "ما") > 5) { if (GetCount(hewbrewEncoding.GetString(buffer), "אולי", "אולי", "אולי", "אולי", "טוב", "טוב") > 10) return hewbrewEncoding; return arabicEncoding; } if (GetCount(hewbrewEncoding.GetString(buffer), AutoDetectWordsHebrew) > 5) return hewbrewEncoding; Encoding romanianEncoding = Encoding.GetEncoding(1250); // Romanian if (GetCount(romanianEncoding.GetString(buffer), "să", "şi", "văzut", "regulă", "găsit", "viaţă") > 99) return romanianEncoding; Encoding koreanEncoding = Encoding.GetEncoding(949); // Korean if (GetCount(koreanEncoding.GetString(buffer), "그리고", "아니야", "하지만", "말이야", "그들은", "우리가") > 5) return koreanEncoding; } } } } catch { } return encoding; } /// /// Will try to determine if buffer is utf-8 encoded or not. /// If any non-utf8 sequences are found then false is returned, if no utf8 multibytes sequences are found then false is returned. /// private static bool IsUtf8(byte[] buffer, out bool couldBeUtf8) { couldBeUtf8 = false; int utf8Count = 0; int i = 0; while (i < buffer.Length - 3) { byte b = buffer[i]; if (b > 127) { if (b >= 194 && b <= 223 && buffer[i + 1] >= 128 && buffer[i + 1] <= 191) { // 2-byte sequence utf8Count++; i++; } else if (b >= 224 && b <= 239 && buffer[i + 1] >= 128 && buffer[i + 1] <= 191 && buffer[i + 2] >= 128 && buffer[i + 2] <= 191) { // 3-byte sequence utf8Count++; i += 2; } else if (b >= 240 && b <= 244 && buffer[i + 1] >= 128 && buffer[i + 1] <= 191 && buffer[i + 2] >= 128 && buffer[i + 2] <= 191 && buffer[i + 3] >= 128 && buffer[i + 3] <= 191) { // 4-byte sequence utf8Count++; i += 3; } else { return false; } } i++; } couldBeUtf8 = true; if (utf8Count == 0) return false; // not utf-8 (no characters utf-8 encoded...) return true; } public static Encoding DetectAnsiEncoding(byte[] buffer) { if (IsRunningOnMono()) return Encoding.Default; try { Encoding encoding = DetectEncoding.EncodingTools.DetectInputCodepage(buffer); Encoding greekEncoding = Encoding.GetEncoding(1253); // Greek if (GetCount(greekEncoding.GetString(buffer), AutoDetectWordsGreek) > 5) return greekEncoding; Encoding russianEncoding = Encoding.GetEncoding(1251); // Cyrillic if (GetCount(russianEncoding.GetString(buffer), "что", "быть", "весь", "этот", "один", "такой") > 5) // Russian return russianEncoding; if (GetCount(russianEncoding.GetString(buffer), "Какво", "тук", "може", "Как", "Ваше", "какво") > 5) // Bulgarian return russianEncoding; russianEncoding = Encoding.GetEncoding(28595); // Russian if (GetCount(russianEncoding.GetString(buffer), "что", "быть", "весь", "этот", "один", "такой") > 5) // Russian return russianEncoding; Encoding thaiEncoding = Encoding.GetEncoding(874); // Thai if (GetCount(thaiEncoding.GetString(buffer), "โอ", "โรเบิร์ต", "วิตตอเรีย", "ดร", "คุณตำรวจ", "ราเชล") + GetCount(thaiEncoding.GetString(buffer), "ไม่", "เลดดิส", "พระเจ้า", "เท็ดดี้", "หัวหน้า", "แอนดรูว์") > 5) return thaiEncoding; Encoding arabicEncoding = Encoding.GetEncoding(28596); // Arabic Encoding hewbrewEncoding = Encoding.GetEncoding(28598); // Hebrew if (GetCount(arabicEncoding.GetString(buffer), "من", "هل", "لا", "فى", "لقد", "ما") > 5) { if (GetCount(hewbrewEncoding.GetString(buffer), "אולי", "אולי", "אולי", "אולי", "טוב", "טוב") > 10) return hewbrewEncoding; return arabicEncoding; } if (GetCount(hewbrewEncoding.GetString(buffer), AutoDetectWordsHebrew) > 5) return hewbrewEncoding; return encoding; } catch { return Encoding.Default; } } public static string DictionaryFolder { get { return Configuration.DictionariesFolder; } } public static List GetDictionaryLanguages() { var list = new List(); if (Directory.Exists(DictionaryFolder)) { foreach (string dic in Directory.GetFiles(DictionaryFolder, "*.dic")) { string name = Path.GetFileNameWithoutExtension(dic); if (!name.StartsWith("hyph", StringComparison.Ordinal)) { 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 double GetOptimalDisplayMilliseconds(string text) { return GetOptimalDisplayMilliseconds(text, Configuration.Settings.General.SubtitleOptimalCharactersPerSeconds); } public static double GetOptimalDisplayMilliseconds(string text, double optimalCharactersPerSecond) { if (optimalCharactersPerSecond < 2 || optimalCharactersPerSecond > 100) optimalCharactersPerSecond = 14.7; double duration = (HtmlUtil.RemoveHtmlTags(text, true).Length / optimalCharactersPerSecond) * TimeCode.BaseUnit; if (duration < Configuration.Settings.General.SubtitleMinimumDisplayMilliseconds) duration = Configuration.Settings.General.SubtitleMinimumDisplayMilliseconds; if (duration > Configuration.Settings.General.SubtitleMaximumDisplayMilliseconds) duration = Configuration.Settings.General.SubtitleMaximumDisplayMilliseconds; return duration; } private static int GetCount(string text, params string[] words) { int count = 0; for (int i = 0; i < words.Length; i++) { count += Regex.Matches(text, "\\b" + words[i] + "\\b", (RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture)).Count; } return count; } private static int GetCountContains(string text, params string[] words) { int count = 0; for (int i = 0; i < words.Length; i++) { var regEx = new Regex(words[i]); count += regEx.Matches(text).Count; } return count; } public static string AutoDetectGoogleLanguage(Encoding encoding) { switch (encoding.CodePage) { case 860: return "pt"; // Portuguese case 28599: case 1254: return "tr"; // Turkish case 28598: case 1255: return "he"; // Hebrew case 28596: case 1256: return "ar"; // Arabic case 1258: return "vi"; // Vietnamese case 949: case 1361: case 20949: case 51949: case 50225: return "ko"; // Korean case 1253: case 28597: return "el"; // Greek case 50220: case 50221: case 50222: case 51932: case 20932: case 10001: return "ja"; // Japanese case 20000: case 20002: case 20936: case 950: case 52936: case 54936: case 51936: return "zh"; // Chinese default: return null; } } public static readonly string[] AutoDetectWordsEnglish = { "we", "are", "and", "you", "your", "what" }; public static readonly string[] AutoDetectWordsDanish = { "vi", "han", "og", "jeg", "var", "men", "gider", "bliver", "virkelig", "kommer", "tilbage", "Hej" }; public static readonly string[] AutoDetectWordsNorwegian = { "vi", "er", "og", "jeg", "var", "men" }; public static readonly string[] AutoDetectWordsSwedish = { "vi", "är", "och", "Jag", "inte", "för" }; public static readonly string[] AutoDetectWordsSpanish = { "el", "bien", "Vamos", "Hola", "casa", "con" }; public static readonly string[] AutoDetectWordsFrench = { "un", "vous", "avec", "pas", "ce", "une" }; public static readonly string[] AutoDetectWordsGerman = { "und", "auch", "sich", "bin", "hast", "möchte" }; public static readonly string[] AutoDetectWordsDutch = { "van", "een", "[Hh]et", "m(ij|ij)", "z(ij|ij)n" }; public static readonly string[] AutoDetectWordsPolish = { "Czy", "ale", "ty", "siê", "jest", "mnie" }; public static readonly string[] AutoDetectWordsItalian = { "Cosa", "sono", "Grazie", "Buongiorno", "bene", "questo", "ragazzi", "propriamente", "numero", "hanno", "giorno", "faccio", "davvero", "negativo", "essere", "vuole", "sensitivo", "venire" }; public static readonly string[] AutoDetectWordsPortuguese = { "não", "Não", "Estás", "Então", "isso", "com" }; public static readonly string[] AutoDetectWordsGreek = { "μου", "είναι", "Είναι", "αυτό", "Τόμπυ", "καλά", "Ενταξει", "Ενταξει", "πρεπει", "Λοιπον", "τιποτα", "ξερεις" }; public static readonly string[] AutoDetectWordsRussian = { "все", "это", "как", "Воробей", "сюда", "Давай" }; public static readonly string[] AutoDetectWordsBulgarian = { "Какво", "тук", "може", "Как", "Ваше", "какво" }; public static readonly string[] AutoDetectWordsRomanian = { "Какво", "тук", "може", "Как", "Ваше", "какво" }; public static readonly string[] AutoDetectWordsArabic = { "Какво", "тук", "може", "Как", "Ваше", "какво" }; public static readonly string[] AutoDetectWordsHebrew = { "אתה", "אולי", "הוא", "בסדר", "יודע", "טוב" }; public static readonly string[] AutoDetectWordsVietnamese = { "không", "tôi", "anh", "đó", "Tôi", "ông" }; public static readonly string[] AutoDetectWordsHungarian = { "hogy", "lesz", "tudom", "vagy", "mondtam", "még" }; public static readonly string[] AutoDetectWordsTurkish = { "için", "Tamam", "Hayır", "benim", "daha", "deðil", "önce", "lazým", "benim", "çalýþýyor", "burada", "efendim" }; public static readonly string[] AutoDetectWordsCroatianAndSerbian = { "sam", "ali", "nije", "samo", "ovo", "kako", "dobro", "sve", "tako", "će", "mogu", "ću", "zašto", "nešto", "za" }; public static readonly string[] AutoDetectWordsCroatian = { "što", "ovdje", "gdje", "kamo", "tko", "prije", "uvijek", "vrijeme", "vidjeti", "netko", "vidio", "nitko", "bok", "lijepo", "oprosti", "htio", "mjesto", "oprostite", "čovjek", "dolje", "čovječe", "dvije", "dijete", "dio", "poslije", "događa", "vjerovati", "vjerojatno", "vjerujem", "točno", "razumijem", "vidjela", "cijeli", "svijet", "obitelj", "volio", "sretan", "dovraga", "svijetu", "htjela", "vidjeli", "negdje", "želio", "ponovno", "djevojka", "umrijeti", "čovjeka", "mjesta", "djeca", "osjećam", "uopće", "djecu", "naprijed", "obitelji", "doista", "mjestu", "lijepa", "također", "riječ", "tijelo" }; public static readonly string[] AutoDetectWordsSerbian = { "šta", "ovde", "gde", "ko", "pre", "uvek", "vreme", "videti", "neko", "video", "niko", "ćao", "lepo", "izvini", "hteo", "mesto", "izvinite", "čovek", "dole", "čoveče", "dve", "dete", "deo", "posle", "dešava", "verovati", "verovatno", "verujem", "tačno", "razumem", "videla", "ceo", "svet", "porodica", "voleo", "srećan", "dođavola", "svetu", "htela", "videli", "negde", "želeo", "ponovo", "devojka", "umreti", "čoveka", "mesta", "deca", "osećam", "uopšte", "decu", "napred", "porodicu", "zaista", "mestu", "lepa", "takođe", "reč", "telo" }; public static string AutoDetectGoogleLanguage(string text, int bestCount) { int count = GetCount(text, AutoDetectWordsEnglish); if (count > bestCount) return "en"; count = GetCount(text, AutoDetectWordsDanish); if (count > bestCount) { int norwegianCount = GetCount(text, "ut", "deg", "meg", "merkelig", "mye", "spørre"); int dutchCount = GetCount(text, "van", "een", "[Hh]et", "m(ij|ij)", "z(ij|ij)n"); if (norwegianCount < 2 && dutchCount < count) return "da"; } count = GetCount(text, AutoDetectWordsNorwegian); if (count > bestCount) { int danishCount = GetCount(text, "siger", "dig", "mig", "mærkelig", "tilbage", "spørge"); int dutchCount = GetCount(text, "van", "een", "[Hh]et", "m(ij|ij)", "z(ij|ij)n"); if (danishCount < 2 && dutchCount < count) return "no"; } count = GetCount(text, AutoDetectWordsSwedish); if (count > bestCount) return "sv"; count = GetCount(text, AutoDetectWordsSpanish); if (count > bestCount) { int frenchCount = GetCount(text, "[Cc]'est", "pas", "vous", "pour", "suis", "Pourquoi", "maison", "souviens", "quelque"); // not spanish words if (frenchCount < 2) return "es"; } count = GetCount(text, AutoDetectWordsFrench); if (count > bestCount) { int spanishCount = GetCount(text, "Hola", "nada", "Vamos", "pasa", "los", "como"); // not french words int italianCount = GetCount(text, AutoDetectWordsItalian); int romanianCount = GetCount(text, "sînt", "aici", "Sînt", "domnule", "pentru", "Vreau"); if (spanishCount < 2 && italianCount < 2 && romanianCount < 5) return "fr"; } count = GetCount(text, AutoDetectWordsGerman); if (count > bestCount) return "de"; count = GetCount(text, AutoDetectWordsDutch); if (count > bestCount) return "nl"; count = GetCount(text, AutoDetectWordsPolish); if (count > bestCount) return "pl"; count = GetCount(text, AutoDetectWordsItalian); if (count > bestCount) { int frenchCount = GetCount(text, "[Cc]'est", "pas", "vous", "pour", "suis", "Pourquoi", "maison", "souviens", "quelque"); // not spanish words int spanishCount = GetCount(text, "Hola", "nada", "Vamos", "pasa", "los", "como"); // not french words if (frenchCount < 2 && spanishCount < 2) return "it"; } count = GetCount(text, AutoDetectWordsPortuguese); if (count > bestCount) return "pt"; // Portuguese count = GetCount(text, AutoDetectWordsGreek); if (count > bestCount) return "el"; // Greek count = GetCount(text, AutoDetectWordsRussian); if (count > bestCount) return "ru"; // Russian count = GetCount(text, AutoDetectWordsBulgarian); if (count > bestCount) return "bg"; // Bulgarian count = GetCount(text, AutoDetectWordsArabic); if (count > bestCount) { if (GetCount(text, "אולי", "אולי", "אולי", "אולי", "טוב", "טוב") > 10) return "he"; int romanianCount = GetCount(text, "sînt", "aici", "Sînt", "domnule", "pentru", "Vreau", "trãiascã", "niciodatã", "înseamnã", "vorbesti", "oamenii", "Asteaptã", "fãcut", "Fãrã", "spune", "decât", "pentru", "vreau"); if (romanianCount > count) return "ro"; // Romanian romanianCount = GetCount(text, "daca", "pentru", "acum", "soare", "trebuie", "Trebuie", "nevoie", "decat", "echilibrul", "vorbesti", "oamenii", "zeului", "vrea", "atunci", "Poate", "Acum", "memoria", "soarele"); if (romanianCount > count) return "ro"; // Romanian return "ar"; // Arabic } count = GetCount(text, AutoDetectWordsHebrew); if (count > bestCount) return "he"; // Hebrew count = GetCount(text, AutoDetectWordsCroatianAndSerbian); if (count > bestCount) { int croatianCount = GetCount(text, AutoDetectWordsCroatian); int serbianCount = GetCount(text, AutoDetectWordsSerbian); if (croatianCount > serbianCount) return "hr"; // Croatian return "sr"; // Serbian } count = GetCount(text, AutoDetectWordsVietnamese); if (count > bestCount) return "vi"; // Vietnamese count = GetCount(text, AutoDetectWordsHungarian); if (count > bestCount) return "hu"; // Hungarian count = GetCount(text, AutoDetectWordsTurkish); if (count > bestCount) return "tr"; // Turkish count = GetCount(text, "yang", "tahu", "bisa", "akan", "tahun", "tapi", "dengan", "untuk", "rumah", "dalam", "sudah", "bertemu"); if (count > bestCount) return "id"; // Indonesian count = GetCount(text, "โอ", "โรเบิร์ต", "วิตตอเรีย", "ดร", "คุณตำรวจ", "ราเชล", "ไม่", "เลดดิส", "พระเจ้า", "เท็ดดี้", "หัวหน้า", "แอนดรูว์"); if (count > 10 || count > bestCount) return "th"; // Thai count = GetCount(text, "그리고", "아니야", "하지만", "말이야", "그들은", "우리가"); if (count > 10 || count > bestCount) return "ko"; // Korean count = GetCount(text, "että", "kuin", "minä", "mitään", "Mutta", "siitä", "täällä", "poika", "Kiitos", "enää", "vielä", "tässä"); if (count > bestCount) return "fi"; // Finnish count = GetCount(text, "sînt", "aici", "Sînt", "domnule", "pentru", "Vreau", "trãiascã", "niciodatã", "înseamnã", "vorbesti", "oamenii", "Asteaptã", "fãcut", "Fãrã", "spune", "decât", "pentru", "vreau"); if (count > bestCount) return "ro"; // Romanian count = GetCount(text, "daca", "pentru", "acum", "soare", "trebuie", "Trebuie", "nevoie", "decat", "echilibrul", "vorbesti", "oamenii", "zeului", "vrea", "atunci", "Poate", "Acum", "memoria", "soarele"); if (count > bestCount) return "ro"; // Romanian count = GetCountContains(text, "シ", "ュ", "シン", "シ", "ン", "ユ"); count += GetCountContains(text, "イ", "ン", "チ", "ェ", "ク", "ハ"); count += GetCountContains(text, "シ", "ュ", "う", "シ", "ン", "サ"); count += GetCountContains(text, "シ", "ュ", "シ", "ン", "だ", "う"); if (count > bestCount * 2) return "ja"; // Japanese - not tested... count = GetCountContains(text, "是", "是早", "吧", "的", "爱", "上好"); count += GetCountContains(text, "的", "啊", "好", "好", "亲", "的"); count += GetCountContains(text, "谢", "走", "吧", "晚", "上", "好"); count += GetCountContains(text, "来", "卡", "拉", "吐", "滚", "他"); if (count > bestCount * 2) return "zh"; // Chinese (simplified) - not tested... return string.Empty; } public static string AutoDetectGoogleLanguage(Subtitle subtitle) { string languageId = AutoDetectGoogleLanguageOrNull(subtitle); if (languageId == null) languageId = "en"; return languageId; } public static string AutoDetectGoogleLanguageOrNull(Subtitle subtitle) { var sb = new StringBuilder(); foreach (Paragraph p in subtitle.Paragraphs) sb.AppendLine(p.Text); string languageId = AutoDetectGoogleLanguage(sb.ToString(), subtitle.Paragraphs.Count / 14); if (string.IsNullOrEmpty(languageId)) languageId = null; return languageId; } public static string AutoDetectLanguageName(string languageName, Subtitle subtitle) { if (string.IsNullOrEmpty(languageName)) languageName = "en_US"; int bestCount = subtitle.Paragraphs.Count / 14; var sb = new StringBuilder(); foreach (Paragraph p in subtitle.Paragraphs) sb.AppendLine(p.Text); string text = sb.ToString(); List dictionaryNames = GetDictionaryLanguages(); bool containsEnGb = false; bool containsEnUs = false; bool containsHrHr = false; bool containsSrLatn = false; foreach (string name in dictionaryNames) { if (name.Contains("[en_GB]")) containsEnGb = true; if (name.Contains("[en_US]")) containsEnUs = true; if (name.Contains("[hr_HR]")) containsHrHr = true; if (name.Contains("[sr-Latn]")) containsSrLatn = true; } foreach (string name in dictionaryNames) { string shortName = string.Empty; int start = name.IndexOf('['); int end = name.IndexOf(']'); if (start > 0 && end > start) { start++; shortName = name.Substring(start, end - start); } int count; switch (shortName) { case "da_DK": count = GetCount(text, "vi", "hun", "og", "jeg", "var", "men", "bliver", "meget", "spørger", "Hej", "utrolig", "dejligt"); if (count > bestCount) { int norweigianCount = GetCount(text, "ut", "deg", "meg", "merkelig", "mye", "spørre"); if (norweigianCount < 2) languageName = shortName; } break; case "nb_NO": count = GetCount(text, AutoDetectWordsNorwegian); if (count > bestCount) { int danishCount = GetCount(text, "siger", "dig", "mig", "mærkelig", "tilbage", "spørge"); int dutchCount = GetCount(text, "van", "een", "[Hh]et", "m(ij|ij)", "z(ij|ij)n"); if (danishCount < 2 && dutchCount < count) languageName = shortName; } break; case "en_US": count = GetCount(text, AutoDetectWordsEnglish); if (count > bestCount) { languageName = shortName; if (containsEnGb) { int usCount = GetCount(text, "color", "flavor", "honor", "humor", "neighbor", "honor"); int gbCount = GetCount(text, "colour", "flavour", "honour", "humour", "neighbour", "honour"); if (gbCount > usCount) languageName = "en_GB"; } } break; case "en_GB": count = GetCount(text, "we", "are", "and", "you", "your", "what"); if (count > bestCount) { languageName = shortName; if (containsEnUs) { int usCount = GetCount(text, "color", "flavor", "honor", "humor", "neighbor", "honor"); int gbCount = GetCount(text, "colour", "flavour", "honour", "humour", "neighbour", "honour"); if (gbCount < usCount) languageName = "en_US"; } } break; case "sv_SE": count = GetCount(text, "vi", "är", "och", "Jag", "inte", "för"); if (count > bestCount) languageName = shortName; break; case "es_ES": count = GetCount(text, AutoDetectWordsSpanish); if (count > bestCount) { int frenchWords = GetCount(text, "[Cc]'est", "pas", "vous", "pour", "suis", "Pourquoi", "maison", "souviens", "quelque"); // not spanish words if (frenchWords < 2) languageName = shortName; } break; case "fr_FR": count = GetCount(text, AutoDetectWordsFrench); if (count > bestCount) { int spanishWords = GetCount(text, "Hola", "nada", "Vamos", "pasa", "los", "como"); // not french words int italianWords = GetCount(text, AutoDetectWordsItalian); // not italian words if (spanishWords < 2 && italianWords < 2) languageName = shortName; } break; case "it_IT": count = GetCount(text, AutoDetectWordsItalian); if (count > bestCount) { int frenchWords = GetCount(text, "[Cc]'est", "pas", "vous", "pour", "suis", "Pourquoi", "maison", "souviens", "quelque"); // not spanish words int spanishWords = GetCount(text, "Hola", "nada", "Vamos", "pasa", "los", "como"); // not french words if (frenchWords < 2 && spanishWords < 2) languageName = shortName; } break; case "de_DE": count = GetCount(text, "und", "auch", "sich", "bin", "hast", "möchte"); if (count > bestCount) languageName = shortName; break; case "nl_NL": count = GetCount(text, "van", "een", "[Hh]et", "m(ij|ij)", "z(ij|ij)n"); if (count > bestCount) languageName = shortName; break; case "pl_PL": count = GetCount(text, "Czy", "ale", "ty", "siê", "jest", "mnie"); if (count > bestCount) languageName = shortName; break; case "el_GR": count = GetCount(text, AutoDetectWordsGreek); if (count > bestCount) languageName = shortName; break; case "ru_RU": count = GetCount(text, AutoDetectWordsRussian); if (count > bestCount) languageName = shortName; break; case "ro_RO": count = GetCount(text, "sînt", "aici", "Sînt", "domnule", "pentru", "Vreau", "trãiascã", "niciodatã", "înseamnã", "vorbesti", "oamenii", "Asteaptã", "fãcut", "Fãrã", "spune", "decât", "pentru", "vreau"); if (count > bestCount) { languageName = shortName; } else { count = GetCount(text, "daca", "pentru", "acum", "soare", "trebuie", "Trebuie", "nevoie", "decat", "echilibrul", "vorbesti", "oamenii", "zeului", "vrea", "atunci", "Poate", "Acum", "memoria", "soarele"); if (count > bestCount) languageName = shortName; } break; case "hr_HR": // Croatian count = GetCount(text, AutoDetectWordsCroatianAndSerbian); if (count > bestCount) { languageName = shortName; if (containsSrLatn) { int croatianCount = GetCount(text, AutoDetectWordsCroatian); int serbianCount = GetCount(text, AutoDetectWordsSerbian); if (serbianCount > croatianCount) languageName = "sr-Latn"; } } break; case "sr-Latn": // Serbian (Latin) count = GetCount(text, AutoDetectWordsCroatianAndSerbian); if (count > bestCount) { languageName = shortName; if (containsHrHr) { int croatianCount = GetCount(text, AutoDetectWordsCroatian); int serbianCount = GetCount(text, AutoDetectWordsSerbian); if (serbianCount < croatianCount) languageName = "hr_HR"; } } break; case "pt_PT": // Portuguese count = GetCount(text, AutoDetectWordsPortuguese); if (count > bestCount) languageName = shortName; break; case "pt_BR": // Portuguese (Brasil) count = GetCount(text, AutoDetectWordsPortuguese); if (count > bestCount) languageName = shortName; break; case "hu_HU": // Hungarian count = GetCount(text, AutoDetectWordsHungarian); if (count > bestCount) languageName = shortName; break; } } return languageName; } public static string ColorToHex(Color c) { return string.Format("#{0:x2}{1:x2}{2:x2}", c.R, c.G, c.B); } public static int GetMaxLineLength(string text) { int maxLength = 0; text = HtmlUtil.RemoveHtmlTags(text, true); foreach (string line in text.SplitToLines()) { if (line.Length > maxLength) maxLength = line.Length; } return maxLength; } public static double GetCharactersPerSecond(Paragraph paragraph) { if (paragraph.Duration.TotalMilliseconds < 1) return 999; const string zeroWidthSpace = "\u200B"; const string zeroWidthNoBreakSpace = "\uFEFF"; string s = HtmlUtil.RemoveHtmlTags(paragraph.Text, true).Replace(Environment.NewLine, string.Empty).Replace(zeroWidthSpace, string.Empty).Replace(zeroWidthNoBreakSpace, string.Empty); return s.Length / paragraph.Duration.TotalSeconds; } 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)) helpFile = "http://www.nikse.dk/SubtitleEdit/Help"; System.Diagnostics.Process.Start(helpFile + parameter); } public static string AssemblyVersion { get { return Assembly.GetEntryAssembly().GetName().Version.ToString(); } } public static string AssemblyDescription { get { var assembly = Assembly.GetEntryAssembly(); string assemblyName = assembly.GetName().Name; if (Attribute.IsDefined(assembly, typeof(AssemblyDescriptionAttribute))) { Console.WriteLine(assemblyName); var descriptionAttribute = (AssemblyDescriptionAttribute)Attribute.GetCustomAttribute(assembly, typeof(AssemblyDescriptionAttribute)); if (descriptionAttribute != null) return descriptionAttribute.Description; } return null; } } private static void AddExtension(StringBuilder sb, string extension) { if (!sb.ToString().Contains("*" + extension + ";", StringComparison.OrdinalIgnoreCase)) { sb.Append('*'); sb.Append(extension.TrimStart('*')); sb.Append(';'); } } public static string GetOpenDialogFilter() { var sb = new StringBuilder(); sb.Append(Configuration.Settings.Language.General.SubtitleFiles + "|"); foreach (SubtitleFormat s in SubtitleFormat.AllSubtitleFormats) { AddExtension(sb, s.Extension); foreach (string ext in s.AlternateExtensions) AddExtension(sb, ext); } AddExtension(sb, new Pac().Extension); AddExtension(sb, new Cavena890().Extension); AddExtension(sb, new Spt().Extension); AddExtension(sb, new Wsb().Extension); AddExtension(sb, new CheetahCaption().Extension); AddExtension(sb, ".chk"); AddExtension(sb, new CaptionsInc().Extension); AddExtension(sb, new Ultech130().Extension); AddExtension(sb, new ELRStudioClosedCaption().Extension); AddExtension(sb, ".uld"); // Ultech drop frame AddExtension(sb, new SonicScenaristBitmaps().Extension); AddExtension(sb, ".mks"); AddExtension(sb, ".mxf"); AddExtension(sb, ".sup"); AddExtension(sb, ".dost"); AddExtension(sb, new Ayato().Extension); AddExtension(sb, new PacUnicode().Extension); if (!string.IsNullOrEmpty(Configuration.Settings.General.OpenSubtitleExtraExtensions)) { var extraExtensions = Configuration.Settings.General.OpenSubtitleExtraExtensions.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (string ext in extraExtensions) { if (ext.StartsWith("*.", StringComparison.Ordinal) && !sb.ToString().Contains(ext, StringComparison.OrdinalIgnoreCase)) AddExtension(sb, ext); } } AddExtension(sb, ".son"); sb.Append('|'); sb.Append(Configuration.Settings.Language.General.AllFiles); sb.Append("|*.*"); return sb.ToString(); } public static void SetSaveDialogFilter(SaveFileDialog saveFileDialog, SubtitleFormat currentFormat) { var sb = new StringBuilder(); int index = 0; foreach (SubtitleFormat format in SubtitleFormat.AllSubtitleFormats) { sb.Append(format.Name + "|*" + format.Extension + "|"); if (currentFormat.Name == format.Name) saveFileDialog.FilterIndex = index + 1; index++; } saveFileDialog.Filter = sb.ToString().TrimEnd('|'); } public static Color ColorDarkOrange = Color.FromArgb(220, 90, 10); public static void GetLineLengths(Label label, string text) { label.ForeColor = Color.Black; var lines = HtmlUtil.RemoveHtmlTags(text, true).SplitToLines(); const int max = 3; var sb = new StringBuilder(); for (int i = 0; i < lines.Length; i++) { string line = lines[i]; if (i > 0) { sb.Append('/'); } if (i > max) { label.ForeColor = Color.Red; sb.Append("..."); label.Text = sb.ToString(); return; } sb.Append(line.Length); if (line.Length > Configuration.Settings.General.SubtitleLineMaximumLength) label.ForeColor = Color.Red; else if (line.Length > Configuration.Settings.General.SubtitleLineMaximumLength - 5) label.ForeColor = ColorDarkOrange; } label.Text = sb.ToString(); } public static bool IsValidRegex(string testPattern) { if (string.IsNullOrEmpty(testPattern)) { return false; } try { Regex.Match(string.Empty, testPattern); } catch (ArgumentException) { // BAD PATTERN: Syntax error return false; } return true; } public static Regex MakeWordSearchRegex(string word) { string s = word.Replace("\\", "\\\\"); s = s.Replace("*", "\\*"); s = s.Replace(".", "\\."); s = s.Replace("?", "\\?"); return new Regex(@"\b" + s + @"\b", RegexOptions.Compiled); } public static Regex MakeWordSearchRegexWithNumbers(string word) { string s = word.Replace("\\", "\\\\"); s = s.Replace("*", "\\*"); s = s.Replace(".", "\\."); s = s.Replace("?", "\\?"); return new Regex(@"[\b ,\.\?\!]" + s + @"[\b !\.,\r\n\?]", RegexOptions.Compiled); } 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)) userWords.Load(userWordsXmlFileName); else userWords.LoadXml(""); var words = new List(); foreach (XmlNode node in userWords.DocumentElement.SelectNodes("word")) { string w = node.InnerText.Trim(); if (w.Length > 0 && w != word) words.Add(w); } words.Sort(); userWords.DocumentElement.RemoveAll(); foreach (string w in words) { XmlNode node = userWords.CreateElement("word"); node.InnerText = w; userWords.DocumentElement.AppendChild(node); } 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)) userWords.Load(userWordsXmlFileName); else userWords.LoadXml(""); var words = new List(); foreach (XmlNode node in userWords.DocumentElement.SelectNodes("word")) { string w = node.InnerText.Trim(); if (w.Length > 0) words.Add(w); } words.Add(word); words.Sort(); userWords.DocumentElement.RemoveAll(); foreach (string w in words) { XmlNode node = userWords.CreateElement("word"); node.InnerText = w; userWords.DocumentElement.AppendChild(node); } userWords.Save(userWordsXmlFileName); } } public static string LoadUserWordList(List 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.ToLower(); if (!userWordList.Contains(s)) userWordList.Add(s); } } return userWordListXmlFileName; } public static string LoadUserWordList(HashSet 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.ToLower(); if (!userWordList.Contains(s)) userWordList.Add(s); } } return userWordListXmlFileName; } public static readonly string UppercaseLetters = GetLetters(true, false, false); public static readonly string LowercaseLetters = GetLetters(false, true, false); public static readonly string LowercaseLettersWithNumbers = GetLetters(false, true, true); public static readonly string AllLetters = GetLetters(true, true, false); public static readonly string AllLettersAndNumbers = GetLetters(true, true, true); private static string GetLetters(bool uppercase, bool lowercase, bool numbers) { var sb = new StringBuilder(); if (uppercase) sb.Append(Configuration.Settings.General.UppercaseLetters.ToUpper()); if (lowercase) sb.Append(Configuration.Settings.General.UppercaseLetters.ToLower()); if (numbers) sb.Append("0123456789"); return sb.ToString(); } public static Color GetColorFromUserName(string userName) { if (string.IsNullOrEmpty(userName)) return Color.Pink; byte[] buffer = Encoding.UTF8.GetBytes(userName); long number = 0; foreach (byte b in buffer) number += b; 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)) return 0; byte[] buffer = Encoding.UTF8.GetBytes(userName); long number = 0; foreach (byte b in buffer) number += b; return (int)(number % 8); } public static string GetRegExGroup(string regEx) { var start = regEx.IndexOf("(?<", StringComparison.Ordinal); if (start < 0) return null; start += 3; var end = regEx.IndexOf('>', start); if (end <= start) return null; return regEx.Substring(start, end - start); } public static string LowercaseVowels { get { return "aeiouyæøåéóáôèòæøåäöïɤəɛʊʉɨ"; } } public static void SetButtonHeight(Control control, int newHeight, int level) { if (level > 6) return; if (control.HasChildren) { foreach (Control subControl in control.Controls) { if (subControl.HasChildren) SetButtonHeight(subControl, newHeight, level + 1); else if (subControl is Button) subControl.Height = newHeight; } } else if (control is Button) control.Height = newHeight; } 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) return count; 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) return count; index = text.IndexOf(tag, index + 1); } return count; } public static bool StartsAndEndsWithTag(string text, string startTag, string endTag) { if (string.IsNullOrWhiteSpace(text)) return false; if (!text.Contains(startTag) || !text.Contains(endTag)) return false; while (text.Contains(" ")) text = text.Replace(" ", " "); var s1 = "- " + startTag; var s2 = "-" + startTag; var s3 = "- ..." + startTag; var s4 = "- " + startTag + "..."; // - ... 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)) isStart = true; 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)) isEnd = true; return isStart && isEnd; } public static Paragraph GetOriginalParagraph(int index, Paragraph paragraph, List originalParagraphs) { if (index < 0) return null; if (index < originalParagraphs.Count && Math.Abs(originalParagraphs[index].StartTime.TotalMilliseconds - paragraph.StartTime.TotalMilliseconds) < 50) return originalParagraphs[index]; foreach (Paragraph p in originalParagraphs) { if (p.StartTime.TotalMilliseconds == paragraph.StartTime.TotalMilliseconds) return p; } foreach (Paragraph p in originalParagraphs) { if (p.StartTime.TotalMilliseconds > paragraph.StartTime.TotalMilliseconds - 200 && p.StartTime.TotalMilliseconds < paragraph.StartTime.TotalMilliseconds + TimeCode.BaseUnit) return p; } return null; } /// /// UrlEncodes a string without the requirement for System.Web /// public static string UrlEncode(string text) { return Uri.EscapeDataString(text); } /// /// UrlDecodes a string without requiring System.Web /// 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); } public static void CheckAutoWrap(TextBox textBox, KeyEventArgs e, int numberOfNewLines) { int length = HtmlUtil.RemoveHtmlTags(textBox.Text).Length; if (e.Modifiers == Keys.None && e.KeyCode != Keys.Enter && numberOfNewLines < 1 && length > Configuration.Settings.General.SubtitleLineMaximumLength) { if (Configuration.Settings.General.AutoWrapLineWhileTyping) // only if auto-break-setting is true { string newText; if (length > Configuration.Settings.General.SubtitleLineMaximumLength + 30) { newText = AutoBreakLine(textBox.Text); } else { int lastSpace = textBox.Text.LastIndexOf(' '); if (lastSpace > 0) newText = textBox.Text.Remove(lastSpace, 1).Insert(lastSpace, Environment.NewLine); else newText = textBox.Text; } int autobreakIndex = newText.IndexOf(Environment.NewLine, StringComparison.Ordinal); if (autobreakIndex > 0) { int selectionStart = textBox.SelectionStart; textBox.Text = newText; if (selectionStart > autobreakIndex) selectionStart += Environment.NewLine.Length - 1; if (selectionStart >= 0) textBox.SelectionStart = selectionStart; } } } } private static readonly Dictionary AllKeys = new Dictionary(); public static Keys GetKeys(string keysInString) { if (string.IsNullOrEmpty(keysInString)) return Keys.None; if (AllKeys.Count == 0) { foreach (Keys val in Enum.GetValues(typeof(Keys))) { string k = val.ToString().ToLower(); if (!AllKeys.ContainsKey(k)) AllKeys.Add(k, val); } if (!AllKeys.ContainsKey("pagedown")) AllKeys.Add("pagedown", Keys.RButton | Keys.Space); if (!AllKeys.ContainsKey("home")) AllKeys.Add("home", Keys.MButton | Keys.Space); if (!AllKeys.ContainsKey("capslock")) AllKeys.Add("capslock", Keys.CapsLock); } string[] parts = keysInString.ToLower().Split(new[] { '+' }, StringSplitOptions.RemoveEmptyEntries); var resultKeys = Keys.None; foreach (string k in parts) { if (AllKeys.ContainsKey(k)) resultKeys = resultKeys | AllKeys[k]; } return resultKeys; } public static string FixEnglishTextInRightToLeftLanguage(string text, string reverseChars) { var sb = new StringBuilder(); var lines = text.SplitToLines(); foreach (string line in lines) { string s = line.Trim(); for (int i = 0; i < s.Length; i++) { if (s[i] == ')') s = s.Remove(i, 1).Insert(i, "("); else if (s[i] == '(') s = s.Remove(i, 1).Insert(i, ")"); } 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{ '⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹', '⁺', '⁻', '⁼', '⁽', '⁾', 'ᵃ', 'ᵇ', 'ᶜ', 'ᵈ', 'ᵉ', 'ᶠ', 'ᵍ', 'ʰ', 'ⁱ', 'ʲ', 'ᵏ', 'ˡ', 'ᵐ', 'ⁿ', 'ᵒ', 'ᵖ', 'ʳ', 'ˢ', 'ᵗ', 'ᵘ', 'ᵛ', 'ʷ', 'ˣ', 'ʸ', 'ᶻ', 'ᴬ', 'ᴮ', 'ᴰ', 'ᴱ', 'ᴳ', 'ᴴ', 'ᴵ', 'ᴶ', 'ᴷ', 'ᴸ', 'ᴹ', 'ᴺ', 'ᴼ', 'ᴾ', 'ᴿ', 'ᵀ', 'ᵁ', 'ᵂ' }; var normal = new List{ '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) sb.Append(superscript[index]); else sb.Append(s); } return sb.ToString(); } public static string ToSubscript(string text) { var sb = new StringBuilder(); var subcript = new List{ '₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉', '₊', '₋', '₌', '₍', '₎', 'ₐ', 'ₑ', 'ᵢ', 'ₒ', 'ᵣ', 'ᵤ', 'ᵥ', 'ₓ', }; var normal = new List { '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) sb.Append(subcript[index]); else sb.Append(s); } return sb.ToString(); } public static string FixQuotes(string text) { if (string.IsNullOrEmpty(text)) return text; if (text.StartsWith('"') && text.Length > 1) text = text.Substring(1); if (text.EndsWith('"') && text.Length >= 1) text = text.Substring(0, text.Length - 1); return text.Replace("\"\"", "\""); } public static Color GetColorFromFontString(string text, Color defaultColor) { string s = text.TrimEnd(); int start = s.IndexOf("= 0 && s.EndsWith("", 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.Ordinal)) { 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 breakToLetters) { const string endChars = "!?.:;,#%$£"; var list = new List(); if (breakToLetters) { foreach (char ch in s) list.Add(ch.ToString()); } else { var word = new StringBuilder(); int i = 0; while (i < s.Length) { if (ignoreLineBreaks && s.Substring(i).StartsWith(Environment.NewLine, StringComparison.Ordinal)) { if (word.Length > 0) list.Add(word.ToString()); word.Clear(); i += Environment.NewLine.Length; } else if (s.Substring(i).StartsWith(Environment.NewLine, StringComparison.Ordinal)) { if (word.Length > 0) list.Add(word.ToString()); word.Clear(); list.Add(Environment.NewLine); i += Environment.NewLine.Length; } else if (s[i] == ' ') { if (word.Length > 0) list.Add(word.ToString()); 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) list.Add(word.ToString()); word.Clear(); word.Append(s[i]); i++; } else { word.Append(s[i]); i++; } } if (word.Length > 0) list.Add(word.ToString()); } return list.ToArray(); } public static void GetTotalAndChangedWords(string s1, string s2, ref int total, ref int change, bool ignoreLineBreaks, bool breakToLetters) { var parts1 = SplitForChangedCalc(s1, ignoreLineBreaks, breakToLetters); var parts2 = SplitForChangedCalc(s2, ignoreLineBreaks, breakToLetters); 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; while (i < Math.Max(parts1.Length, parts2.Length) && i1 < parts1.Length && i2 < parts2.Length) { 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) return c; return c + Math.Abs(parts1.Length - parts2.Length); } private static int FindNext(string s, string[] parts, int startIndex) { for (int i = startIndex; i < parts.Length; i++) { if (s == parts[i]) return i; } return int.MaxValue; } public static string RemoveNonNumbers(string p) { if (string.IsNullOrEmpty(p)) return p; var sb = new StringBuilder(); foreach (var c in p) { if (char.IsDigit(c)) sb.Append(c); } return sb.ToString(); } /// /// Remove unneeded spaces /// /// text string to remove unneeded spaces from /// two letter language id string /// text with unneeded spaces removed public static string RemoveUnneededSpaces(string text, string language) { const string zeroWidthSpace = "\u200B"; const string zeroWidthNoBreakSpace = "\uFEFF"; const string noBreakSpace = "\u00A0"; const string operatingSystemCommand = "\u009D"; text = text.Trim(); text = text.Replace(zeroWidthSpace, string.Empty); text = text.Replace(zeroWidthNoBreakSpace, string.Empty); text = text.Replace(noBreakSpace, " "); text = text.Replace(operatingSystemCommand, string.Empty); text = text.Replace('\t', ' '); text = text.FixExtraSpaces(); if (text.EndsWith(' ')) text = text.Substring(0, text.Length - 1); 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("....")) text = text.Replace("....", ellipses); text = text.Replace(" ..." + Environment.NewLine, "..." + Environment.NewLine); text = text.Replace(Environment.NewLine + "... ", Environment.NewLine + "..."); text = text.Replace(Environment.NewLine + "... ", Environment.NewLine + "..."); text = text.Replace(Environment.NewLine + "- ... ", Environment.NewLine + "- ..."); text = text.Replace(Environment.NewLine + "- ... ", Environment.NewLine + "- ..."); text = text.Replace(Environment.NewLine + "- ... ", Environment.NewLine + "- ..."); if (text.StartsWith("... ", StringComparison.Ordinal)) text = text.Remove(3, 1); if (text.EndsWith(" ...", StringComparison.Ordinal)) text = text.Remove(text.Length - 4, 1); if (text.EndsWith(" ...", StringComparison.Ordinal)) text = text.Remove(text.Length - 8, 1); if (text.StartsWith("- ... ", StringComparison.Ordinal)) text = text.Remove(5, 1); if (text.StartsWith("... ", StringComparison.Ordinal)) text = text.Remove(6, 1); if (language != "fr") // special rules for French { text = text.Replace("... ?", "...?"); text = text.Replace("... !", "...!"); text = text.Replace(" :", ":"); text = text.Replace(" :", ":"); } if (!text.Contains("- ...")) text = text.Replace(" ... ", "... "); while (text.Contains(" ,")) text = text.Replace(" ,", ","); if (text.EndsWith(" .", StringComparison.Ordinal)) text = text.Remove(text.Length - 2, 1); if (text.EndsWith(" \"", StringComparison.Ordinal)) text = text.Remove(text.Length - 2, 1); if (text.Contains(" \"" + Environment.NewLine)) text = text.Replace(" \"" + Environment.NewLine, "\"" + Environment.NewLine); if (text.Contains(" ." + Environment.NewLine)) text = text.Replace(" ." + Environment.NewLine, "." + Environment.NewLine); if (language != "fr") // special rules for French { if (text.Contains(" !")) text = text.Replace(" !", "!"); if (text.Contains(" ?")) text = text.Replace(" ?", "?"); } while (text.Contains("¿ ")) text = text.Replace("¿ ", "¿"); while (text.Contains("¡ ")) text = text.Replace("¡ ", "¡"); // Italic if (text.Contains("", StringComparison.OrdinalIgnoreCase) && text.Contains("", StringComparison.OrdinalIgnoreCase)) text = RemoveSpaceBeforeAfterTag(text, ""); // Bold if (text.Contains("", StringComparison.OrdinalIgnoreCase) && text.Contains("", StringComparison.OrdinalIgnoreCase)) text = RemoveSpaceBeforeAfterTag(text, ""); // Underline if (text.Contains("", StringComparison.OrdinalIgnoreCase) && text.Contains("", StringComparison.OrdinalIgnoreCase)) text = RemoveSpaceBeforeAfterTag(text, ""); // Font if (text.Contains("', idx + 6); if (endIdx > idx && endIdx < text.Length - 8) { var color = text.Substring(idx, (endIdx - idx) + 1).ToLower(); text = RemoveSpaceBeforeAfterTag(text, color); } } text = text.Trim(); text = text.Replace(Environment.NewLine + " ", Environment.NewLine); if (text.Contains("- ") && text.Length > 5) { int idx = text.IndexOf("- ", 2, StringComparison.Ordinal); if (text.StartsWith("", StringComparison.OrdinalIgnoreCase)) idx = text.IndexOf("- ", 5, StringComparison.Ordinal); while (idx > 0) { if (idx > 0 && idx < text.Length - 2) { string before = string.Empty; int k = idx - 1; while (k >= 0 && AllLettersAndNumbers.Contains(text[k])) { before = text[k] + before; k--; } string after = string.Empty; k = idx + 2; while (k < text.Length && AllLetters.Contains(text[k])) { after = after + text[k]; k++; } if (after.Length > 0 && after.Equals(before, StringComparison.OrdinalIgnoreCase)) text = text.Remove(idx + 1, 1); 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)))) { } else { text = text.Remove(idx + 1, 1); } } } if (idx + 1 < text.Length && idx != -1) idx = text.IndexOf("- ", idx + 1, StringComparison.Ordinal); else break; } } if (CountTagInText(text, '"') == 2 && text.Contains(" \" ")) { int idx = text.IndexOf(" \" ", StringComparison.Ordinal); int idxp = text.IndexOf('"'); //"Foo " bar. if ((idxp >= 0 && idxp < idx) && AllLettersAndNumbers.Contains(text[idx - 1]) && !" \r\n".Contains(text[idxp + 1])) { text = text.Remove(idx, 1); } //" Foo " bar. idx = text.IndexOf(" \" ", StringComparison.Ordinal); idxp = text.IndexOf('"'); if (idxp >= 0 && idx > idxp) { if (text[idxp + 1] == ' ' && AllLettersAndNumbers.Contains(text[idxp + 2])) { text = text.Remove(idxp + 1, 1); idx--; } text = text.Remove(idx, 1); } } return text; } public static string RemoveSpaceBeforeAfterTag(string text, string openTag) { text = HtmlUtil.FixUpperTags(text); var closeTag = string.Empty; switch (openTag) { case "": closeTag = ""; break; case "": closeTag = ""; break; case "": closeTag = ""; break; } if (closeTag.Length == 0 && openTag.Contains("
Foobar if (text.StartsWith(open1, StringComparison.Ordinal)) text = openTag + text.Substring(open1.Length); if (text.Contains(open2, StringComparison.Ordinal)) text = text.Replace(open2, Environment.NewLine + openTag); // Hi bad man! -> Hi bad man! text = text.Replace(" " + openTag + " ", " " + openTag); text = text.Replace(Environment.NewLine + openTag + " ", Environment.NewLine + openTag); // Hi bad man! -> Hi bad man! text = text.Replace(" " + closeTag + " ", closeTag + " "); text = text.Replace(" " + closeTag + Environment.NewLine, closeTag + Environment.NewLine); text = text.Trim(); if (text.StartsWith(open1, StringComparison.Ordinal)) text = openTag + text.Substring(open1.Length); return text; } /// /// Creates a task that will complete after a time delay. /// /// The number of milliseconds to wait before completing the returned task. /// A task that represents the time delay. public static Task TaskDelay(int millisecondsDelay) { var tcs = new TaskCompletionSource(); 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 sub, Subtitle subtitle) { if (subtitle == null) throw new ArgumentNullException("subtitle"); subtitle.Paragraphs.Clear(); var isSsa = false; SubtitleFormat format = new SubRip(); if (matroskaSubtitleInfo.CodecPrivate.Contains("[script info]", StringComparison.OrdinalIgnoreCase)) { if (matroskaSubtitleInfo.CodecPrivate.Contains("[V4 Styles]", StringComparison.OrdinalIgnoreCase)) format = new SubStationAlpha(); else format = new AdvancedSubStationAlpha(); isSsa = true; } if (isSsa) { foreach (var p in LoadMatroskaSSA(matroskaSubtitleInfo, matroska.Path, format, sub).Paragraphs) { subtitle.Paragraphs.Add(p); } if (!string.IsNullOrEmpty(matroskaSubtitleInfo.CodecPrivate)) { bool eventsStarted = false; bool fontsStarted = false; bool graphicsStarted = false; var header = new StringBuilder(); foreach (string line in matroskaSubtitleInfo.CodecPrivate.Replace(Environment.NewLine, "\n").Split('\n')) { 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.Text, p.Start, p.End)); } } subtitle.Renumber(); return format; } public static Subtitle LoadMatroskaSSA(MatroskaTrackInfo matroskaSubtitleInfo, string fileName, SubtitleFormat format, List sub) { var subtitle = new Subtitle { Header = matroskaSubtitleInfo.CodecPrivate }; var lines = new List(); foreach (string l in subtitle.Header.Trim().SplitToLines()) lines.Add(l); var footer = new StringBuilder(); var comments = new Subtitle(); if (!string.IsNullOrEmpty(matroskaSubtitleInfo.CodecPrivate)) { bool footerOn = false; 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) { arr = arr[1].Split(new[] { ':', '.' }); if (arr.Length == 4) { int hour; int min; int sec; int ms; if (int.TryParse(arr[0], out hour) && int.TryParse(arr[1], out min) && int.TryParse(arr[2], out sec) && int.TryParse(arr[3], out ms)) { comments.Paragraphs.Add(new Paragraph(new TimeCode(hour, min, sec, ms * 10), new TimeCode(0, 0, 0, 0), line)); } } } } } } 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 { 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; } lines = new List(); foreach (string l in subtitle.Header.Trim().SplitToLines()) lines.Add(l); 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) lines.Add(cp.Text); } for (int commentIndex = comments.Paragraphs.Count - 1; commentIndex >= 0; commentIndex--) { var cp = comments.Paragraphs[commentIndex]; if (cp.StartTime.TotalMilliseconds <= p.StartTime.TotalMilliseconds) comments.Paragraphs.RemoveAt(commentIndex); } string text = mp.Text.Replace(Environment.NewLine, "\\N"); 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()) lines.Add(l); format.LoadSubtitle(subtitle, lines, fileName); return subtitle; } public static int GetNumberOfLines(string text) { if (string.IsNullOrEmpty(text)) return 0; int lines = 1; int idx = text.IndexOf('\n'); while (idx >= 0) { lines++; idx = text.IndexOf('\n', idx + 1); } return lines; } public static void FixLargeFonts(Control mainCtrl, Control ctrl) { using (Graphics graphics = mainCtrl.CreateGraphics()) { SizeF textSize = graphics.MeasureString(ctrl.Text, mainCtrl.Font); if (textSize.Height > ctrl.Height - 4) { int newButtonHeight = (int)(textSize.Height + 7.5); SetButtonHeight(mainCtrl, newButtonHeight, 1); } } } } }