diff --git a/se5/ArteCheck.Tests/DurationToleranceTests.cs b/se5/ArteCheck.Tests/DurationToleranceTests.cs
new file mode 100644
index 00000000..b01523ec
--- /dev/null
+++ b/se5/ArteCheck.Tests/DurationToleranceTests.cs
@@ -0,0 +1,31 @@
+using SubtitleEdit.Plugins.ArteCheck;
+using SubtitleEdit.Plugins.Shared;
+
+namespace ArteCheck.Tests;
+
+/// 25 fps, 25-frame minimum, 15% tolerance: 21.25 frames is shown as 21, so 21 frames is accepted.
+public class DurationToleranceTests
+{
+ private const double Hour10 = 10 * 3_600_000.0;
+
+ private static List Analyze(int frames) => ArteChecker.Analyze(
+ new[] { new PluginParagraph { StartMs = Hour10, EndMs = Hour10 + frames * ArteChecker.FrameMs, Text = "Hi", MarginV = "22" } },
+ null,
+ new ArteOptions { EnabledChecks = new() { ArteCheckType.DisplayDuration } });
+
+ [Theory]
+ [InlineData(20, true)]
+ [InlineData(21, false)]
+ [InlineData(22, false)]
+ public void ToleratedMinimum_IsRoundedToWholeFrames(int frames, bool reported)
+ {
+ Assert.Equal(reported, Analyze(frames).Any(f => f.Kind == ArteFixKind.DisplayDuration));
+ }
+
+ [Fact]
+ public void ToleratedMinimum_MessageShowsTheLimitTheCheckUses()
+ {
+ var fix = Assert.Single(Analyze(20), f => f.Kind == ArteFixKind.DisplayDuration);
+ Assert.Contains("tolerated minimum of 0 s 21 fr", fix.Reason);
+ }
+}
diff --git a/se5/ArteCheck/ArteChecker.cs b/se5/ArteCheck/ArteChecker.cs
index d2a02caa..4cf4f73d 100644
--- a/se5/ArteCheck/ArteChecker.cs
+++ b/se5/ArteCheck/ArteChecker.cs
@@ -281,10 +281,15 @@ private double RequiredDurationMs(string text)
return Math.Max(_options.MinimumDurationMs, reading);
}
+ ///
+ /// The shortest duration accepted, on whole frames - the limit the message shows (FormatDuration
+ /// rounds to frames) must be the one the check uses: 25 frames - 15% = 21.25 shows as 21 frames,
+ /// so 21 frames passes and 20 is reported (same as Triathlon-rally's web checker).
+ ///
private double AcceptedMinimumDurationMs(string text) =>
_options.AcceptShortDurations
? _options.ShortMinimumFrames * FrameMs
- : RequiredDurationMs(text) * Math.Max(0, 1.0 - _options.ReadingDurationTolerancePercent / 100.0);
+ : RoundToFrame(RequiredDurationMs(text) * Math.Max(0, 1.0 - _options.ReadingDurationTolerancePercent / 100.0));
private void AnalyzeDisplayDurations(List subtitle)
{