/// <summary> /// Two lines maximum /// Text should be on one line if it fits /// </summary> public void Check(Subtitle subtitle, NetflixQualityController controller) { foreach (Paragraph p in subtitle.Paragraphs) { if (p.Text.SplitToLines().Count > 2) { var fixedParagraph = new Paragraph(p, false); fixedParagraph.Text = Utilities.AutoBreakLine(fixedParagraph.Text, controller.SingleLineMaxLength, controller.SingleLineMaxLength - 3, controller.Language); if (fixedParagraph.Text.SplitToLines().Count > 2) { fixedParagraph = null; // cannot fix text } string comment = "Two lines maximum"; controller.AddRecord(p, fixedParagraph, comment); } else if (p.Text.SplitToLines().Count == 2 && p.Text.Contains(Environment.NewLine) && p.Text.Replace(Environment.NewLine, " ").Replace(" ", " ").CountCharacters(false, Configuration.Settings.General.IgnoreArabicDiacritics) <= controller.SingleLineMaxLength && p.Text != Utilities.AutoBreakLine(p.Text, controller.Language)) { var fixedParagraph = new Paragraph(p, false); fixedParagraph.Text = Utilities.AutoBreakLine(fixedParagraph.Text, controller.SingleLineMaxLength, controller.SingleLineMaxLength - 3, controller.Language); string comment = "Text can fit on one line"; controller.AddRecord(p, fixedParagraph, comment); } } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { if (controller.Language == "ja") { return; } double twoFramesGap = 1000.0 / controller.FrameRate * 2.0; int halfSecGap = (int)Math.Round(controller.FrameRate / 2, MidpointRounding.AwayFromZero); for (int index = 0; index < subtitle.Paragraphs.Count; index++) { var p = subtitle.Paragraphs[index]; var next = subtitle.GetParagraphOrDefault(index + 1); if (next == null) { continue; } var gapInFrames = SubtitleFormat.MillisecondsToFrames(next.StartTime.TotalMilliseconds - p.EndTime.TotalMilliseconds); if (gapInFrames > 2 && gapInFrames < halfSecGap && !p.StartTime.IsMaxTime) { var fixedParagraph = new Paragraph(p, false) { EndTime = { TotalMilliseconds = next.StartTime.TotalMilliseconds - twoFramesGap } }; string comment = $"3-{halfSecGap - 1} frames gap => 2 frames gap"; controller.AddRecord(p, fixedParagraph, comment); } } }
/// <summary> /// Minimum duration: 5/6 second (833 ms) - also see https://github.com/SubtitleEdit/plugins/issues/129 /// </summary> public void Check(Subtitle subtitle, NetflixQualityController controller) { for (int index = 0; index < subtitle.Paragraphs.Count; index++) { var p = subtitle.Paragraphs[index]; if (controller.Language == "ja") { if (p.Duration.TotalMilliseconds < 500) { string comment = "Minimum duration: 0.5 second"; controller.AddRecord(p, p.StartTime.ToHHMMSSFF(), comment, p.Duration.TotalSeconds.ToString(CultureInfo.InvariantCulture)); } continue; } var next = subtitle.GetParagraphOrDefault(index + 1); if (p.Duration.TotalMilliseconds < 833) { Paragraph fixedParagraph = null; if (next == null || next.StartTime.TotalMilliseconds > p.StartTime.TotalMilliseconds + 834) { // we can fix duration fixedParagraph = new Paragraph(p, false); fixedParagraph.Duration.TotalMilliseconds = 834; } string comment = "Minimum duration: 5/6 second (833 ms)"; controller.AddRecord(p, fixedParagraph, comment); } } }
/// <summary> /// Speed - max 17 (for most languages) characters per second /// </summary> public void Check(Subtitle subtitle, NetflixQualityController controller) { var oldIgnoreWhiteSpace = Configuration.Settings.General.CharactersPerSecondsIgnoreWhiteSpace; try { Configuration.Settings.General.CharactersPerSecondsIgnoreWhiteSpace = false; foreach (Paragraph p in subtitle.Paragraphs) { var jp = new Paragraph(p); if (controller.Language == "ja") { jp.Text = HtmlUtil.RemoveHtmlTags(jp.Text, true); jp.Text = NetflixImsc11Japanese.RemoveTags(jp.Text); } var charactersPerSeconds = Utilities.GetCharactersPerSecond(jp); if (charactersPerSeconds > controller.CharactersPerSecond) { var fixedParagraph = new Paragraph(p, false); while (Utilities.GetCharactersPerSecond(fixedParagraph) > controller.CharactersPerSecond) { fixedParagraph.EndTime.TotalMilliseconds++; } string comment = "Maximum " + controller.CharactersPerSecond + " characters per second"; controller.AddRecord(p, fixedParagraph, comment, charactersPerSeconds.ToString(CultureInfo.InvariantCulture)); } } } finally { Configuration.Settings.General.CharactersPerSecondsIgnoreWhiteSpace = oldIgnoreWhiteSpace; } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { ICalcLength calc = CalcFactory.MakeCalculator(nameof(CalcAll)); var charactersPerSecond = controller.CharactersPerSecond; var comment = "Maximum " + charactersPerSecond + " characters per second"; foreach (var p in subtitle.Paragraphs) { var jp = new Paragraph(p); if (controller.Language == "ja") { jp.Text = HtmlUtil.RemoveHtmlTags(jp.Text, true); jp.Text = NetflixImsc11Japanese.RemoveTags(jp.Text); } if (controller.Language == "ko") { calc = CalcFactory.MakeCalculator(nameof(CalcCjk)); } var charactersPerSeconds = Utilities.GetCharactersPerSecond(jp, calc); if (charactersPerSeconds > charactersPerSecond && !p.StartTime.IsMaxTime) { var fixedParagraph = new Paragraph(p, false); while (Utilities.GetCharactersPerSecond(fixedParagraph) > charactersPerSecond) { fixedParagraph.EndTime.TotalMilliseconds++; } controller.AddRecord(p, fixedParagraph, comment, FormattableString.Invariant($"CPS={charactersPerSeconds:0.##}")); } } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { foreach (Paragraph p in subtitle.Paragraphs) { // Line endings if (LineEndingSpaceBefore.IsMatch(p.Text)) { AddWhiteSpaceWarning(p, controller, 1); } if (LineEndingSpaceAfter.IsMatch(p.Text)) { AddWhiteSpaceWarning(p, controller, p.Text.Length); } // Spaces before punctuation foreach (Match m in SpacesBeforePunctuation.Matches(p.Text)) { AddWhiteSpaceWarning(p, controller, m.Index + 1); } // 2+ consecutive spaces foreach (Match m in TwoPlusConsequentSpaces.Matches(p.Text)) { AddWhiteSpaceWarning(p, controller, m.Index); } } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { foreach (Paragraph p in subtitle.Paragraphs) { if (p.Text.Contains("i>", StringComparison.OrdinalIgnoreCase)) { if (controller.AllowItalics) { var fixedParagraph = new Paragraph(p, false); fixedParagraph.Text = HtmlUtil.FixInvalidItalicTags(fixedParagraph.Text); if (fixedParagraph.Text != p.Text) { string comment = "Fixed italics"; controller.AddRecord(p, fixedParagraph, comment); } } else { var fixedParagraph = new Paragraph(p, false); fixedParagraph.Text = HtmlUtil.RemoveHtmlTags(fixedParagraph.Text); if (fixedParagraph.Text != p.Text) { string comment = "Italics not allowed"; controller.AddRecord(p, fixedParagraph, comment); } } } } }
/// <summary> /// Speed - max 17 (for most languages) characters per second /// </summary> public void Check(Subtitle subtitle, NetflixQualityController controller) { var oldIgnoreWhiteSpace = Configuration.Settings.General.CharactersPerSecondsIgnoreWhiteSpace; try { foreach (Paragraph p in subtitle.Paragraphs) { var charactersPerSeconds = Utilities.GetCharactersPerSecond(p); if (charactersPerSeconds > controller.CharactersPerSecond) { var fixedParagraph = new Paragraph(p, false); while (Utilities.GetCharactersPerSecond(fixedParagraph) > controller.CharactersPerSecond) { fixedParagraph.EndTime.TotalMilliseconds++; } string comment = "Maximum " + controller.CharactersPerSecond + " characters per second"; controller.AddRecord(p, fixedParagraph, comment, charactersPerSeconds.ToString(CultureInfo.InvariantCulture)); } } } finally { Configuration.Settings.General.CharactersPerSecondsIgnoreWhiteSpace = oldIgnoreWhiteSpace; } }
/// <summary> /// Two frames gap minimum /// </summary> public void Check(Subtitle subtitle, NetflixQualityController controller) { if (controller.Language == "ja") { return; } for (int index = 0; index < subtitle.Paragraphs.Count; index++) { Paragraph p = subtitle.Paragraphs[index]; var next = subtitle.GetParagraphOrDefault(index + 1); double twoFramesGap = 1000.0 / controller.FrameRate * 2.0; if (next != null && SubtitleFormat.MillisecondsToFrames(p.EndTime.TotalMilliseconds + twoFramesGap) > SubtitleFormat.MillisecondsToFrames(next.StartTime.TotalMilliseconds) && !p.StartTime.IsMaxTime) { var fixedParagraph = new Paragraph(p, false) { EndTime = { TotalMilliseconds = next.StartTime.TotalMilliseconds - twoFramesGap } }; string comment; if (p.EndTime.TotalMilliseconds > next.StartTime.TotalMilliseconds) { comment = "Minimum two frames gap (Overlapping)"; } else { comment = "Minimum two frames gap"; } controller.AddRecord(p, fixedParagraph, comment); } } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { if (controller.Language == "ja") { return; } for (int index = 0; index < subtitle.Paragraphs.Count; index++) { var p = subtitle.Paragraphs[index]; var next = subtitle.GetParagraphOrDefault(index + 1); if (next == null) { continue; } double twoFramesGap = 1000.0 / controller.FrameRate * 2.0; var gapInFrames = SubtitleFormat.MillisecondsToFrames(next.StartTime.TotalMilliseconds) - SubtitleFormat.MillisecondsToFrames(p.EndTime.TotalMilliseconds); if (gapInFrames >= 3 && gapInFrames <= 11 && !p.StartTime.IsMaxTime) { var fixedParagraph = new Paragraph(p, false) { EndTime = { TotalMilliseconds = next.StartTime.TotalMilliseconds - twoFramesGap } }; string comment = "3-11 frames gap => 2 frames gap"; controller.AddRecord(p, fixedParagraph, comment); } } }
private static void AddWhiteSpaceWarning(Paragraph p, NetflixQualityController report, int pos) { string timeCode = p.StartTime.ToHHMMSSFF(); string context = NetflixQualityController.StringContext(p.Text, pos, 6); string comment = string.Format(NetflixLanguage.WhiteSpaceCheckReport, pos); report.AddRecord(p, timeCode, context, comment); }
/// <summary> /// From 1 to 10, numbers should be written out: one, two, three, etc. /// </summary> public void Check(Subtitle subtitle, NetflixQualityController controller) { foreach (Paragraph p in subtitle.Paragraphs) { string newText = p.Text; var m = NumberOneToNine.Match(newText); while (m.Success) { bool ok = newText.Length <= m.Index + 1 || newText.Length > m.Index + 1 && !":.".Contains(newText[m.Index + 1].ToString()); if (!ok && newText.Length > m.Index + 1) { var rest = newText.Substring(m.Index + 1); if (rest == "." || rest == "?" || rest == "!" || rest == ".</i>" || rest == "?</i>" || rest == "!</i>" || rest == "." + Environment.NewLine || rest == "?" + Environment.NewLine || rest == "!" + Environment.NewLine || rest == ".</i>" + Environment.NewLine || rest == "?</i>" + Environment.NewLine || rest == "!</i>" + Environment.NewLine) { ok = true; } } if (ok && m.Index > 0 && ":.".Contains(newText[m.Index - 1].ToString())) { ok = false; } if (ok) { newText = newText.Remove(m.Index, 1).Insert(m.Index, NetflixHelper.ConvertNumberToString(m.Value.Substring(0, 1), false, controller.Language)); } m = NumberOneToNine.Match(newText, m.Index + 1); } m = NumberTen.Match(newText); while (m.Success) { bool ok = newText.Length <= m.Index + 2 || newText.Length > m.Index + 2 && newText[m.Index + 2] != ':'; if (ok && m.Index > 0 && ":.".Contains(newText[m.Index - 1].ToString())) { ok = false; } if (ok) { newText = newText.Remove(m.Index, 2).Insert(m.Index, "ten"); } m = NumberTen.Match(newText, m.Index + 1); } if (newText != p.Text) { var fixedParagraph = new Paragraph(p, false) { Text = newText }; string comment = "From 1 to 10, numbers should be written out: one, two, three, etc"; controller.AddRecord(p, fixedParagraph, comment); } } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { if (subtitle?.Header != null && subtitle.Header.Contains("ttp:frameRate=")) { var xml = new XmlDocument { XmlResolver = null }; try { xml.LoadXml(subtitle.Header); } catch { return; } if (xml.DocumentElement == null) { return; } const string ns = "http://www.w3.org/ns/ttml"; var nsmgr = new XmlNamespaceManager(xml.NameTable); nsmgr.AddNamespace("ttml", ns); var frameRateAttr = xml.DocumentElement.Attributes["ttp:frameRate"]; if (frameRateAttr == null) { return; } double fr; if (!double.TryParse(frameRateAttr.Value, out fr)) { controller.AddRecord(null, null, $"Frame rate is invalid: \'{frameRateAttr.Value}\'"); } var frameRateMultiplier = xml.DocumentElement.Attributes["ttp:frameRateMultiplier"]; if (frameRateMultiplier != null) { var arr = frameRateMultiplier.InnerText.Split(); if (arr.Length == 2 && Utilities.IsInteger(arr[0]) && Utilities.IsInteger(arr[1]) && int.Parse(arr[1]) > 0) { fr = double.Parse(arr[0]) * fr / double.Parse(arr[1]); CheckFrameRate(fr, controller); } } else { if (Utilities.IsInteger(frameRateAttr.InnerText)) { fr = double.Parse(frameRateAttr.InnerText); CheckFrameRate(fr, controller); } } } }
private static void CheckFrameRate(double fr, NetflixQualityController controller) { foreach (var validFrameRate in ValidFrameRates) { if (Math.Abs(validFrameRate - fr) < 0.01) { return; } } controller.AddRecord(null, null, "Frame rate is invalid"); }
/// <summary> /// Use a hyphen with or without a space to indicate two speakers in one subtitle /// </summary> public void Check(Subtitle subtitle, NetflixQualityController controller) { if (controller.DualSpeakersHasHyphenAndNoSpace) { RemoveSpaceAfterHyphenInDialogues(subtitle, controller); } else { AddSpaceAfterHyphenInDialogues(subtitle, controller); } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { foreach (Paragraph p in subtitle.Paragraphs) { if (p.Duration.TotalMilliseconds > 7000) { var fixedParagraph = new Paragraph(p, false); fixedParagraph.EndTime.TotalMilliseconds = fixedParagraph.StartTime.TotalMilliseconds + 7000; string comment = "Maximum duration: 7 seconds per subtitle event"; controller.AddRecord(p, fixedParagraph, comment); } } }
/// <summary> /// Use a hyphen without a space to indicate two speakers in one subtitle /// </summary> public void Check(Subtitle subtitle, NetflixQualityController controller) { if (!controller.DualSpeakersHasHypenAndNoSplace) { return; } foreach (Paragraph p in subtitle.Paragraphs) { var arr = p.Text.SplitToLines(); if (arr.Length == 2 && p.Text.Contains("-")) { string newText = p.Text; if (arr[0].StartsWith("- ") && arr[1].StartsWith("- ")) { newText = "-" + arr[0].Remove(0, 2) + Environment.NewLine + "-" + arr[1].Remove(0, 2); } else if (arr[0].StartsWith("<i>- ") && arr[1].StartsWith("<i>- ")) { newText = "<i>-" + arr[0].Remove(0, 5) + Environment.NewLine + "<i>-" + arr[1].Remove(0, 5); } else if (arr[0].StartsWith("<i>- ") && arr[1].StartsWith("- ")) { newText = "<i>-" + arr[0].Remove(0, 5) + Environment.NewLine + "-" + arr[1].Remove(0, 2); } else if (arr[0].StartsWith("- ") && arr[1].StartsWith("<i>- ")) { newText = "-" + arr[0].Remove(0, 2) + Environment.NewLine + "<i>-" + arr[1].Remove(0, 5); } else if ((arr[0].StartsWith("-") || arr[0].StartsWith("<i>-")) && arr[1].StartsWith("- ")) { newText = "-" + arr[0] + Environment.NewLine + "-" + arr[1].Remove(0, 2); } else if (arr[0].StartsWith("- ") && (arr[1].StartsWith("-") || arr[1].StartsWith("<i>-"))) { newText = "-" + arr[0].Remove(0, 2) + Environment.NewLine + "-" + arr[1]; } if (newText != p.Text) { var fixedParagraph = new Paragraph(p, false) { Text = newText }; string comment = "Dual Speakers: Use a hyphen without a space"; controller.AddRecord(p, fixedParagraph, comment); } } } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { string comment = "Use the single smart character (U+2026) as opposed to three dots/periods in a row"; foreach (var paragraph in subtitle.Paragraphs) { if (paragraph.Text.Contains("...")) { var fixedParagraph = new Paragraph(paragraph, false) { Text = paragraph.Text.Replace("...", "…") }; controller.AddRecord(paragraph, fixedParagraph, comment); } } }
/// <summary> /// Maximum 42 chars per line for the majority of languages. /// </summary> public void Check(Subtitle subtitle, NetflixQualityController controller) { foreach (var p in subtitle.Paragraphs) { foreach (var line in p.Text.SplitToLines()) { if (HtmlUtil.RemoveHtmlTags(line, true).Length > controller.SingleLineMaxLength) { var fixedParagraph = new Paragraph(p, false); fixedParagraph.Text = Utilities.AutoBreakLine(fixedParagraph.Text, controller.SingleLineMaxLength, controller.SingleLineMaxLength - 3, controller.Language); string comment = "Single line length > " + controller.SingleLineMaxLength; controller.AddRecord(p, fixedParagraph, comment, line.Length.ToString(CultureInfo.InvariantCulture)); } } } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { foreach (var p in subtitle.Paragraphs) { foreach (var line in p.Text.SplitToLines()) { if (controller.Language == "ja") { var vertical = p.Text.Contains("{\\an7", StringComparison.Ordinal) || p.Text.Contains("{\\an9", StringComparison.Ordinal); var text = HtmlUtil.RemoveHtmlTags(line, true); text = NetflixImsc11Japanese.RemoveTags(text); if (vertical) // Vertical subtitles - Maximum 11 full-width characters per line { if (CalculateJapaneseLength(text) > 11) { var comment = "Single vertical line length > 11"; controller.AddRecord(p, p.StartTime.ToHHMMSSFF(), comment, line.Length.ToString(CultureInfo.InvariantCulture)); } } else // Horizontal subtitles - Maximum 13 full-width characters per line { if (CalculateJapaneseLength(text) > 13) { var comment = "Single horizontal line length > 13"; controller.AddRecord(p, p.StartTime.ToHHMMSSFF(), comment, line.Length.ToString(CultureInfo.InvariantCulture)); } } } else if (controller.Language == "ko" && line.CountCharacters(nameof(CalcCjk)) > controller.SingleLineMaxLength) { var fixedParagraph = new Paragraph(p, false); fixedParagraph.Text = Utilities.AutoBreakLine(fixedParagraph.Text, controller.SingleLineMaxLength, controller.SingleLineMaxLength - 3, controller.Language); var comment = "Single line length > " + controller.SingleLineMaxLength; controller.AddRecord(p, fixedParagraph, comment, line.CountCharacters(nameof(CalcCjk)).ToString(CultureInfo.InvariantCulture)); } else if (line.CountCharacters() > controller.SingleLineMaxLength) { var fixedParagraph = new Paragraph(p, false); fixedParagraph.Text = Utilities.AutoBreakLine(fixedParagraph.Text, controller.SingleLineMaxLength, controller.SingleLineMaxLength - 3, controller.Language); var comment = "Single line length > " + controller.SingleLineMaxLength; controller.AddRecord(p, fixedParagraph, comment, line.Length.ToString(CultureInfo.InvariantCulture)); } } } }
private static void RemoveSpaceAfterHyphenInDialogues(Subtitle subtitle, NetflixQualityController controller) { foreach (Paragraph p in subtitle.Paragraphs) { var arr = p.Text.SplitToLines(); if (arr.Count == 2 && p.Text.Contains("-")) { string newText = p.Text; if (arr[0].StartsWith("- ", StringComparison.Ordinal) && arr[1].StartsWith("- ", StringComparison.Ordinal)) { newText = "-" + arr[0].Remove(0, 2) + Environment.NewLine + "-" + arr[1].Remove(0, 2); } else if (arr[0].StartsWith("<i>- ", StringComparison.Ordinal) && arr[1].StartsWith("<i>- ", StringComparison.Ordinal)) { newText = "<i>-" + arr[0].Remove(0, 5) + Environment.NewLine + "<i>-" + arr[1].Remove(0, 5); } else if (arr[0].StartsWith("<i>- ", StringComparison.Ordinal) && arr[1].StartsWith("- ", StringComparison.Ordinal)) { newText = "<i>-" + arr[0].Remove(0, 5) + Environment.NewLine + "-" + arr[1].Remove(0, 2); } else if (arr[0].StartsWith("- ", StringComparison.Ordinal) && arr[1].StartsWith("<i>- ", StringComparison.Ordinal)) { newText = "-" + arr[0].Remove(0, 2) + Environment.NewLine + "<i>-" + arr[1].Remove(0, 5); } else if ((arr[0].StartsWith("-", StringComparison.Ordinal) || arr[0].StartsWith("<i>-", StringComparison.Ordinal)) && arr[1].StartsWith("- ", StringComparison.Ordinal)) { newText = "-" + arr[0] + Environment.NewLine + "-" + arr[1].Remove(0, 2); } else if (arr[0].StartsWith("- ", StringComparison.Ordinal) && (arr[1].StartsWith("-", StringComparison.Ordinal) || arr[1].StartsWith("<i>-", StringComparison.Ordinal))) { newText = "-" + arr[0].Remove(0, 2) + Environment.NewLine + "-" + arr[1]; } if (newText != p.Text) { var fixedParagraph = new Paragraph(p, false) { Text = newText }; string comment = "Dual Speakers: Use a hyphen without a space"; controller.AddRecord(p, fixedParagraph, comment); } } } }
private static void AddSpaceAfterHyphenInDialogues(Subtitle subtitle, NetflixQualityController controller) { var sub = new Subtitle(subtitle); for (int i = 0; i < sub.Paragraphs.Count; i++) { Paragraph p = new Paragraph(sub.Paragraphs[i]); var arr = p.Text.SplitToLines(); if (arr.Count == 2 && p.Text.Contains("-") && arr[0].Length > 3 && arr[1].Length > 3) { string newText = p.Text; if (arr[0][0] == '-' && char.IsLetter(arr[0][1]) && (arr[1].StartsWith("-", StringComparison.Ordinal) || arr[1].StartsWith("<i>-", StringComparison.Ordinal))) { newText = arr[0].Insert(1, " ") + Environment.NewLine + arr[1]; arr = newText.SplitToLines(); } else if (arr[0].StartsWith("<i>-", StringComparison.Ordinal) && arr[0].Length > 5 && char.IsLetter(arr[0][4]) && (arr[1].StartsWith("-", StringComparison.Ordinal) || arr[1].StartsWith("<i>-", StringComparison.Ordinal))) { newText = arr[0].Insert(4, " ") + Environment.NewLine + arr[1]; arr = newText.SplitToLines(); } if (arr[1][0] == '-' && char.IsLetter(arr[1][1]) && (arr[0].StartsWith("-", StringComparison.Ordinal) || arr[0].StartsWith("<i>-", StringComparison.Ordinal))) { newText = arr[0] + Environment.NewLine + arr[1].Insert(1, " "); } else if (arr[1].StartsWith("<i>-", StringComparison.Ordinal) && arr[1].Length > 5 && char.IsLetter(arr[1][4]) && (arr[0].StartsWith("-", StringComparison.Ordinal) || arr[0].StartsWith("<i>-", StringComparison.Ordinal))) { newText = arr[0] + Environment.NewLine + arr[1].Insert(4, " "); } if (newText != p.Text) { var fixedParagraph = new Paragraph(p, false) { Text = newText }; string comment = "Dual Speakers: Use a space after hyphen"; controller.AddRecord(p, fixedParagraph, comment); } } } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { if (controller.Language == "jp") { return; } foreach (Paragraph p in subtitle.Paragraphs) { string newText = p.Text; var arr = p.Text.SplitToLines(); if (newText.StartsWith("(", StringComparison.Ordinal) && newText.EndsWith(")", StringComparison.Ordinal)) { newText = "[" + newText.Substring(1, newText.Length - 2) + "]"; } else if (newText.StartsWith("{", StringComparison.Ordinal) && newText.EndsWith("}", StringComparison.Ordinal)) { newText = "[" + newText.Substring(1, newText.Length - 2) + "]"; } else if (arr.Count == 2 && arr[0].StartsWith("-", StringComparison.Ordinal) && arr[1].StartsWith("-", StringComparison.Ordinal)) { if ((arr[0].StartsWith("-(", StringComparison.Ordinal) && arr[0].EndsWith(")", StringComparison.Ordinal)) || (arr[0].StartsWith("-{", StringComparison.Ordinal) && arr[0].EndsWith("}", StringComparison.Ordinal))) { arr[0] = "-[" + newText.Substring(2, newText.Length - 3) + "]"; } if ((arr[1].StartsWith("-(", StringComparison.Ordinal) && arr[1].EndsWith(")", StringComparison.Ordinal)) || (arr[1].StartsWith("-{", StringComparison.Ordinal) && arr[1].EndsWith("}", StringComparison.Ordinal))) { arr[1] = "-[" + arr[1].Substring(2, arr[1].Length - 3) + "]"; } newText = arr[0] + Environment.NewLine + arr[1]; } if (newText != p.Text) { var fixedParagraph = new Paragraph(p, false) { Text = newText }; string comment = "Use brackets [ ] to enclose speaker IDs or sound effects"; controller.AddRecord(p, fixedParagraph, comment); } } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { foreach (Paragraph p in subtitle.Paragraphs) { string newText = p.Text; var m = NumberStart.Match(newText); while (m.Success) { int length = m.Length - 2; newText = newText.Remove(m.Index, length).Insert(m.Index, NetflixHelper.ConvertNumberToString(m.Value.Substring(0, length), true, controller.Language)); m = NumberStart.Match(newText, m.Index + 1); } m = NumberStartInside.Match(newText); while (m.Success) { int length = m.Length - 4; newText = newText.Remove(m.Index + 2, length).Insert(m.Index + 2, NetflixHelper.ConvertNumberToString(m.Value.Substring(2, length), true, controller.Language)); m = NumberStartInside.Match(newText, m.Index + 1); } m = NumberStartInside2.Match(newText); while (m.Success) { int length = m.Length - 5; newText = newText.Remove(m.Index + 3, length).Insert(m.Index + 3, NetflixHelper.ConvertNumberToString(m.Value.Substring(3, length), true, controller.Language)); m = NumberStartInside2.Match(newText, m.Index + 1); } if (newText != p.Text) { var fixedParagraph = new Paragraph(p, false) { Text = newText }; string comment = "When a number begins a sentence, it should always be spelled out"; controller.AddRecord(p, fixedParagraph, comment); } } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { if (controller.Language == "ja") { return; } var dialogHelper = new DialogSplitMerge { DialogStyle = controller.SpeakerStyle }; string comment = "Dual Speakers: Use a hyphen without a space"; if (controller.SpeakerStyle == DialogType.DashBothLinesWithSpace) { comment = "Dual Speakers: Use a hyphen with a space"; } else if (controller.SpeakerStyle == DialogType.DashSecondLineWithSpace) { comment = "Dual Speakers: Use a hyphen with a space to denote the second speaker only"; } else if (controller.SpeakerStyle == DialogType.DashSecondLineWithoutSpace) { comment = "Dual Speakers: Use a hyphen without a space to denote the second speaker only"; } for (int i = 0; i < subtitle.Paragraphs.Count; i++) { var p = subtitle.Paragraphs[i]; string oldText = p.Text; string newText = dialogHelper.FixDashesAndSpaces(p.Text, p, subtitle.GetParagraphOrDefault(i - 1)); if (newText != oldText) { var fixedParagraph = new Paragraph(p, false) { Text = newText }; controller.AddRecord(p, fixedParagraph, comment); } } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { // Load allowed glyphs var allowedGlyphsSet = LoadNetflixGlyphs(); foreach (Paragraph paragraph in subtitle.Paragraphs) { for (int pos = 0, actualPos = 0; pos < paragraph.Text.Length; pos += char.IsSurrogatePair(paragraph.Text, pos) ? 2 : 1, actualPos++) { int curCodepoint = char.ConvertToUtf32(paragraph.Text, pos); if (!allowedGlyphsSet.Contains(curCodepoint)) { string timecode = paragraph.StartTime.ToHHMMSSFF(); string context = NetflixQualityController.StringContext(paragraph.Text, pos, 6); string comment = string.Format(Configuration.Settings.Language.NetflixQualityCheck.GlyphCheckReport, $"U+{curCodepoint:X}", actualPos); controller.AddRecord(paragraph, timecode, context, comment); } } } }
public void Check(Subtitle subtitle, NetflixQualityController controller) { if (!controller.VideoExists) { return; } var SceneChanges = SceneChangeHelper.FromDisk(controller.VideoFileName); if (SceneChanges == null || SceneChanges.Count == 0) { return; } int halfSecGapInFrames = (int)Math.Round(controller.FrameRate / 2); double twoFramesGap = 1000.0 / controller.FrameRate * 2.0; foreach (Paragraph p in subtitle.Paragraphs) { var fixedParagraph = new Paragraph(p, false); string comment = string.Empty; List <double> previousStartSceneChanges = SceneChanges.Where(x => SubtitleFormat.MillisecondsToFrames(x * 1000) < SubtitleFormat.MillisecondsToFrames(p.StartTime.TotalMilliseconds)).ToList(); List <double> nextStartSceneChanges = SceneChanges.Where(x => SubtitleFormat.MillisecondsToFrames(x * 1000) > SubtitleFormat.MillisecondsToFrames(p.StartTime.TotalMilliseconds)).ToList(); List <double> previousEndSceneChanges = SceneChanges.Where(x => SubtitleFormat.MillisecondsToFrames(x * 1000) < SubtitleFormat.MillisecondsToFrames(p.EndTime.TotalMilliseconds)).ToList(); List <double> nextEndSceneChanges = SceneChanges.Where(x => SubtitleFormat.MillisecondsToFrames(x * 1000) > SubtitleFormat.MillisecondsToFrames(p.EndTime.TotalMilliseconds)).ToList(); var onSceneChange = SceneChanges.Where(x => SubtitleFormat.MillisecondsToFrames(x * 1000) == SubtitleFormat.MillisecondsToFrames(p.EndTime.TotalMilliseconds)).FirstOrDefault(); if (previousStartSceneChanges.Count > 0) { double nearestStartPrevSceneChange = previousStartSceneChanges.Aggregate((x, y) => Math.Abs(x - p.StartTime.TotalSeconds) < Math.Abs(y - p.StartTime.TotalSeconds) ? x : y); if (SubtitleFormat.MillisecondsToFrames(p.StartTime.TotalMilliseconds - nearestStartPrevSceneChange * 1000) < halfSecGapInFrames) { fixedParagraph.StartTime.TotalMilliseconds = nearestStartPrevSceneChange * 1000; comment = $"The in-cue is within {halfSecGapInFrames} frames after the shot change, snap the in-cue to the shot-change"; controller.AddRecord(p, fixedParagraph, comment); } } if (nextStartSceneChanges.Count > 0) { double nearestStartNextSceneChange = nextStartSceneChanges.Aggregate((x, y) => Math.Abs(x - p.StartTime.TotalSeconds) < Math.Abs(y - p.StartTime.TotalSeconds) ? x : y); var gapToSceneChange = SubtitleFormat.MillisecondsToFrames(nearestStartNextSceneChange * 1000 - p.StartTime.TotalMilliseconds); var threshold = (int)Math.Round(halfSecGapInFrames * 0.75); if (gapToSceneChange < halfSecGapInFrames) { if (gapToSceneChange < threshold) { fixedParagraph.StartTime.TotalMilliseconds = nearestStartNextSceneChange * 1000; comment = $"The in-cue is 1-{threshold - 1} frames before the shot change, snap the in-cue to the shot change"; } else { fixedParagraph.StartTime.TotalMilliseconds = nearestStartNextSceneChange * 1000 - (1000.0 / controller.FrameRate * halfSecGapInFrames); comment = $"The in-cue is {threshold}-{halfSecGapInFrames - 1} frames before the shot change, pull the in-cue to half a second ({halfSecGapInFrames} frames) before the shot-change"; } controller.AddRecord(p, fixedParagraph, comment); } } if (previousEndSceneChanges.Count > 0) { double nearestEndPrevSceneChange = previousEndSceneChanges.Aggregate((x, y) => Math.Abs(x - p.EndTime.TotalSeconds) < Math.Abs(y - p.EndTime.TotalSeconds) ? x : y); if (SubtitleFormat.MillisecondsToFrames(p.EndTime.TotalMilliseconds - nearestEndPrevSceneChange * 1000) < halfSecGapInFrames) { fixedParagraph.EndTime.TotalMilliseconds = nearestEndPrevSceneChange * 1000 - twoFramesGap; comment = $"The out-cue is within {halfSecGapInFrames} frames after the shot change"; controller.AddRecord(p, fixedParagraph, comment); } } if (nextEndSceneChanges.Count > 0) { double nearestEndNextSceneChange = nextEndSceneChanges.Aggregate((x, y) => Math.Abs(x - p.EndTime.TotalSeconds) < Math.Abs(y - p.EndTime.TotalSeconds) ? x : y); if (SubtitleFormat.MillisecondsToFrames(nearestEndNextSceneChange * 1000 - p.EndTime.TotalMilliseconds) - 1 < halfSecGapInFrames && SubtitleFormat.MillisecondsToFrames(p.EndTime.TotalMilliseconds) != SubtitleFormat.MillisecondsToFrames(nearestEndNextSceneChange * 1000 - twoFramesGap)) { fixedParagraph.EndTime.TotalMilliseconds = nearestEndNextSceneChange * 1000 - twoFramesGap; comment = $"The out-cue is within {halfSecGapInFrames} frames of the last frame before the shot change"; controller.AddRecord(p, fixedParagraph, comment); } } if (onSceneChange > 0) { fixedParagraph.EndTime.TotalMilliseconds = onSceneChange * 1000 - twoFramesGap; comment = "The out-cue is on the shot change, respect the two-frame gap"; controller.AddRecord(p, fixedParagraph, comment); } } }
/// <summary> /// Check the newly-updated timing to Shot Changes rules. /// https://partnerhelp.netflixstudios.com/hc/en-us/articles/360051554394-Timed-Text-Style-Guide-Subtitle-Timing-Guidelines /// </summary> public void Check(Subtitle subtitle, NetflixQualityController controller) { if (!controller.VideoExists) { return; } var SceneChanges = SceneChangeHelper.FromDisk(controller.VideoFileName); if (SceneChanges == null || SceneChanges.Count == 0) { return; } const int twelveFramesGap = 12; double twoFramesGap = 1000.0 / controller.FrameRate * 2.0; foreach (Paragraph p in subtitle.Paragraphs) { var fixedParagraph = new Paragraph(p, false); string comment = string.Empty; List <double> previousStartSceneChanges = SceneChanges.Where(x => SubtitleFormat.MillisecondsToFrames(x * 1000) < SubtitleFormat.MillisecondsToFrames(p.StartTime.TotalMilliseconds)).ToList(); List <double> nextStartSceneChanges = SceneChanges.Where(x => SubtitleFormat.MillisecondsToFrames(x * 1000) > SubtitleFormat.MillisecondsToFrames(p.StartTime.TotalMilliseconds)).ToList(); List <double> previousEndSceneChanges = SceneChanges.Where(x => SubtitleFormat.MillisecondsToFrames(x * 1000) < SubtitleFormat.MillisecondsToFrames(p.EndTime.TotalMilliseconds)).ToList(); List <double> nextEndSceneChanges = SceneChanges.Where(x => SubtitleFormat.MillisecondsToFrames(x * 1000) > SubtitleFormat.MillisecondsToFrames(p.EndTime.TotalMilliseconds)).ToList(); var onSceneChange = SceneChanges.Where(x => SubtitleFormat.MillisecondsToFrames(x * 1000) == SubtitleFormat.MillisecondsToFrames(p.EndTime.TotalMilliseconds)).FirstOrDefault(); if (previousStartSceneChanges.Count > 0) { double nearestStartPrevSceneChange = previousStartSceneChanges.Aggregate((x, y) => Math.Abs(x - p.StartTime.TotalSeconds) < Math.Abs(y - p.StartTime.TotalSeconds) ? x : y); if (SubtitleFormat.MillisecondsToFrames(p.StartTime.TotalMilliseconds - nearestStartPrevSceneChange * 1000) < twelveFramesGap) { fixedParagraph.StartTime.TotalMilliseconds = nearestStartPrevSceneChange * 1000; comment = "The in-cue is within 12 frames after the shot change"; controller.AddRecord(p, fixedParagraph, comment); } } if (nextStartSceneChanges.Count > 0) { double nearestStartNextSceneChange = nextStartSceneChanges.Aggregate((x, y) => Math.Abs(x - p.StartTime.TotalSeconds) < Math.Abs(y - p.StartTime.TotalSeconds) ? x : y); if (SubtitleFormat.MillisecondsToFrames(nearestStartNextSceneChange * 1000 - p.StartTime.TotalMilliseconds) < twelveFramesGap) { fixedParagraph.StartTime.TotalMilliseconds = nearestStartNextSceneChange * 1000; comment = "The in-cue is within 12 frames before the shot change"; controller.AddRecord(p, fixedParagraph, comment); } } if (previousEndSceneChanges.Count > 0) { double nearestEndPrevSceneChange = previousEndSceneChanges.Aggregate((x, y) => Math.Abs(x - p.EndTime.TotalSeconds) < Math.Abs(y - p.EndTime.TotalSeconds) ? x : y); if (SubtitleFormat.MillisecondsToFrames(p.EndTime.TotalMilliseconds - nearestEndPrevSceneChange * 1000) < twelveFramesGap) { fixedParagraph.EndTime.TotalMilliseconds = nearestEndPrevSceneChange * 1000 - twoFramesGap; comment = "The out-cue is within 12 frames after the shot change"; controller.AddRecord(p, fixedParagraph, comment); } } if (nextEndSceneChanges.Count > 0) { double nearestEndNextSceneChange = nextEndSceneChanges.Aggregate((x, y) => Math.Abs(x - p.EndTime.TotalSeconds) < Math.Abs(y - p.EndTime.TotalSeconds) ? x : y); if (SubtitleFormat.MillisecondsToFrames(nearestEndNextSceneChange * 1000 - p.EndTime.TotalMilliseconds) - 1 < twelveFramesGap && SubtitleFormat.MillisecondsToFrames(p.EndTime.TotalMilliseconds) != SubtitleFormat.MillisecondsToFrames(nearestEndNextSceneChange * 1000 - twoFramesGap)) { fixedParagraph.EndTime.TotalMilliseconds = nearestEndNextSceneChange * 1000 - twoFramesGap; comment = "The out-cue is within 12 frames of the last frame before the shot change"; controller.AddRecord(p, fixedParagraph, comment); } } if (onSceneChange > 0) { fixedParagraph.EndTime.TotalMilliseconds = onSceneChange * 1000 - twoFramesGap; comment = "The out-cue is on the shot change (respect the two-frame gap)"; controller.AddRecord(p, fixedParagraph, comment); } } }