FA-77931 / Subtitle cue timing / Open access
Subtitle frame-rate retiming: start rounding · case 01
Cue starts land one millisecond early whenever the scaled time has a fractional half or more.
ROOT CAUSE
The scaled start is truncated with floor instead of rounded half up.
VERIFIED REPAIR
Round the scaled start half up with floor(x+1/2).
Unsuccessful approach: Python's round uses half-to-even, so exact .5 starts with an even floor still round down.
Case contract
Cue times authored against a src frame rate are rescaled for playback at dst by t*src/dst, rounded half up to integer ms. Rates are "N", "N/D", or the aliases "23.976"/"29.97" meaning (N+1)*1000/1001. The end is rounded independently; an end that does not exceed the start becomes start+1.
Why this case matters
Subtitle timing defects shift, hide or overlap captions that viewers depend on for comprehension and accessibility.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(cues, src, dst):
def rate(r):
if '/' in r:
a,b=r.split('/')
return Fraction(int(a),int(b))
if '.' in r:
return Fraction(int(float(r)+0.5)*1000,1001)
return Fraction(int(r))
k=rate(src)/rate(dst)
out=[]
for start,end in cues:
rs=math.floor(start*k)
re_=math.floor(end*k+Fraction(1,2))
if re_<=rs:
re_=rs+1
out.append([rs,re_])
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: start rounding', [[[5, 6]], '24', '25'], [[5, 6]]), ('regression variant: start rounding', [[[1, 4], [7, 9], [12345, 14346], [1001, 1002]], '24000/1001', '12'], [[2, 8], [14, 18], [24665, 28663], [2000, 2002]]), ('partial repair probe: start rounding', [[[418354, 420355]], '60', '48'], [[522943, 525444]]), ('partial repair variant: start rounding', [[[1, 3], [7, 8], [1, 3]], '24', '48'], [[1, 2], [4, 5], [1, 2]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('normal control', [[[1, 1001]], '30000/1001', '29.97'], [[1, 1001]]), ('normal control', [[[540231, 541231], [0, 3]], '50', '29.97'], [[901285, 902954], [0, 5]]), ('normal control', [[[999, 2714], [12345, 13345], [1, 2002]], '29.97', '30000/1001'], [[999, 2714], [12345, 13345], [1, 2002]])], [('regression: start rounding', [[[1, 2], [3, 4]], '23.976', '25'], [[1, 2], [3, 4]]), ('regression variant: start rounding', [[[3, 6]], '30', '48'], [[2, 4]]), ('partial repair probe: start rounding', [[[1001, 2001]], '15', '30'], [[501, 1001]]), ('partial repair variant: start rounding', [[[3, 4], [999, 1999], [7, 1584], [12345, 14346]], '25', '50'], [[2, 3], [500, 1000], [4, 792], [6173, 7173]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('normal control', [[[1001, 1003], [7, 4855], [12345, 13345]], '50', '50'], [[1001, 1003], [7, 4855], [12345, 13345]]), ('normal control', [[[7, 9], [3, 6], [0, 3], [1, 4]], '24', '23.976'], [[7, 9], [3, 6], [0, 3], [1, 4]]), ('normal control', [[[3, 1003], [999, 1000], [3, 5]], '50', '30'], [[5, 1672], [1665, 1667], [5, 8]])], [('regression: start rounding', [[[7, 10], [1001, 4303], [12345, 12348], [0, 101]], '25', '29.97'], [[6, 8], [835, 3589], [10298, 10300], [0, 84]]), ('regression variant: start rounding', [[[583859, 583862], [0, 1000], [3, 6], [881562, 881564]], '25', '30'], [[486549, 486552], [0, 833], [3, 5], [734635, 734637]]), ('partial repair probe: start rounding', [[[1, 4995]], '25', '50'], [[1, 2498]]), ('partial repair variant: start rounding', [[[348744, 348746], [282090, 282093]], '15', '12'], [[435930, 435933], [352613, 352616]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('normal control', [[[594534, 596535], [7, 10]], '29.97', '29.97'], [[594534, 596535], [7, 10]]), ('normal control', [[[821125, 821127], [0, 1]], '30', '30'], [[821125, 821127], [0, 1]]), ('normal control', [[[7, 1007]], '48', '30'], [[11, 1611]])], [('regression: start rounding', [[[269050, 271051]], '30000/1001', '23.976'], [[336313, 338814]]), ('regression variant: start rounding', [[[999, 4970]], '30', '60'], [[500, 2485]]), ('partial repair probe: start rounding', [[[3, 2004], [7, 10], [566844, 568845]], '25', '30'], [[3, 1670], [6, 8], [472370, 474038]]), ('partial repair variant: start rounding', [[[1, 4], [12345, 12348], [7, 10], [3, 6]], '25', '30'], [[1, 3], [10288, 10290], [6, 8], [3, 5]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('normal control', [[[1, 4]], '24000/1001', '50'], [[0, 2]]), ('normal control', [[[0, 3], [1001, 3002]], '12', '48'], [[0, 1], [250, 751]]), ('normal control', [[[3, 2004]], '24000/1001', '30000/1001'], [[2, 1603]])], [('regression: start rounding', [[[268354, 268356], [12345, 14346], [0, 42]], '60', '25'], [[644050, 644054], [29628, 34430], [0, 101]]), ('regression variant: start rounding', [[[1, 4]], '23.976', '15'], [[2, 6]]), ('partial repair probe: start rounding', [[[999, 1999]], '50', '60'], [[833, 1666]]), ('partial repair variant: start rounding', [[[1001, 5110], [1001, 2001], [3, 6]], '50', '12'], [[4171, 21292], [4171, 8338], [13, 25]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('normal control', [[[999, 1001], [1001, 1003]], '24000/1001', '25'], [[958, 960], [960, 962]]), ('normal control', [[[12345, 12851], [0, 1000], [7, 2008]], '48', '24'], [[24690, 25702], [0, 2000], [14, 4016]]), ('normal control', [[[12345, 13204], [1001, 3749]], '23.976', '30'], [[9866, 10553], [800, 2996]])]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: start rounding | [[4, 6]] | [[5, 6]] | Failed |
| regression variant: start rounding | [[1, 8], [13, 18], [24665, 28663], [2000, 2002]] | [[2, 8], [14, 18], [24665, 28663], [2000, 2002]] | Failed |
| partial repair probe: start rounding | [[522942, 525444]] | [[522943, 525444]] | Failed |
| partial repair variant: start rounding | [[0, 2], [3, 4], [0, 2]] | [[1, 2], [4, 5], [1, 2]] | Failed |
| boundary control | [[500, 1000]] | [[500, 1000]] | Passed |
| boundary control | [[1000, 3000]] | [[1000, 3000]] | Passed |
| normal control | [[1, 1001]] | [[1, 1001]] | Passed |
| normal control | [[901285, 902954], [0, 5]] | [[901285, 902954], [0, 5]] | Passed |
| normal control | [[999, 2714], [12345, 13345], [1, 2002]] | [[999, 2714], [12345, 13345], [1, 2002]] | Passed |
SHA-256 / d64d07ab20c0cec00c74a58e9c2f8273fc0b150a289003a33331b16e271fadd7
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(cues, src, dst):
def rate(r):
if '/' in r:
a,b=r.split('/')
return Fraction(int(a),int(b))
if '.' in r:
return Fraction(int(float(r)+0.5)*1000,1001)
return Fraction(int(r))
k=rate(src)/rate(dst)
out=[]
for start,end in cues:
rs=round(start*k)
re_=math.floor(end*k+Fraction(1,2))
if re_<=rs:
re_=rs+1
out.append([rs,re_])
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: start rounding', [[[5, 6]], '24', '25'], [[5, 6]]), ('regression variant: start rounding', [[[1, 4], [7, 9], [12345, 14346], [1001, 1002]], '24000/1001', '12'], [[2, 8], [14, 18], [24665, 28663], [2000, 2002]]), ('partial repair probe: start rounding', [[[418354, 420355]], '60', '48'], [[522943, 525444]]), ('partial repair variant: start rounding', [[[1, 3], [7, 8], [1, 3]], '24', '48'], [[1, 2], [4, 5], [1, 2]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('normal control', [[[1, 1001]], '30000/1001', '29.97'], [[1, 1001]]), ('normal control', [[[540231, 541231], [0, 3]], '50', '29.97'], [[901285, 902954], [0, 5]]), ('normal control', [[[999, 2714], [12345, 13345], [1, 2002]], '29.97', '30000/1001'], [[999, 2714], [12345, 13345], [1, 2002]])], [('regression: start rounding', [[[1, 2], [3, 4]], '23.976', '25'], [[1, 2], [3, 4]]), ('regression variant: start rounding', [[[3, 6]], '30', '48'], [[2, 4]]), ('partial repair probe: start rounding', [[[1001, 2001]], '15', '30'], [[501, 1001]]), ('partial repair variant: start rounding', [[[3, 4], [999, 1999], [7, 1584], [12345, 14346]], '25', '50'], [[2, 3], [500, 1000], [4, 792], [6173, 7173]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('normal control', [[[1001, 1003], [7, 4855], [12345, 13345]], '50', '50'], [[1001, 1003], [7, 4855], [12345, 13345]]), ('normal control', [[[7, 9], [3, 6], [0, 3], [1, 4]], '24', '23.976'], [[7, 9], [3, 6], [0, 3], [1, 4]]), ('normal control', [[[3, 1003], [999, 1000], [3, 5]], '50', '30'], [[5, 1672], [1665, 1667], [5, 8]])], [('regression: start rounding', [[[7, 10], [1001, 4303], [12345, 12348], [0, 101]], '25', '29.97'], [[6, 8], [835, 3589], [10298, 10300], [0, 84]]), ('regression variant: start rounding', [[[583859, 583862], [0, 1000], [3, 6], [881562, 881564]], '25', '30'], [[486549, 486552], [0, 833], [3, 5], [734635, 734637]]), ('partial repair probe: start rounding', [[[1, 4995]], '25', '50'], [[1, 2498]]), ('partial repair variant: start rounding', [[[348744, 348746], [282090, 282093]], '15', '12'], [[435930, 435933], [352613, 352616]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('normal control', [[[594534, 596535], [7, 10]], '29.97', '29.97'], [[594534, 596535], [7, 10]]), ('normal control', [[[821125, 821127], [0, 1]], '30', '30'], [[821125, 821127], [0, 1]]), ('normal control', [[[7, 1007]], '48', '30'], [[11, 1611]])], [('regression: start rounding', [[[269050, 271051]], '30000/1001', '23.976'], [[336313, 338814]]), ('regression variant: start rounding', [[[999, 4970]], '30', '60'], [[500, 2485]]), ('partial repair probe: start rounding', [[[3, 2004], [7, 10], [566844, 568845]], '25', '30'], [[3, 1670], [6, 8], [472370, 474038]]), ('partial repair variant: start rounding', [[[1, 4], [12345, 12348], [7, 10], [3, 6]], '25', '30'], [[1, 3], [10288, 10290], [6, 8], [3, 5]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('normal control', [[[1, 4]], '24000/1001', '50'], [[0, 2]]), ('normal control', [[[0, 3], [1001, 3002]], '12', '48'], [[0, 1], [250, 751]]), ('normal control', [[[3, 2004]], '24000/1001', '30000/1001'], [[2, 1603]])], [('regression: start rounding', [[[268354, 268356], [12345, 14346], [0, 42]], '60', '25'], [[644050, 644054], [29628, 34430], [0, 101]]), ('regression variant: start rounding', [[[1, 4]], '23.976', '15'], [[2, 6]]), ('partial repair probe: start rounding', [[[999, 1999]], '50', '60'], [[833, 1666]]), ('partial repair variant: start rounding', [[[1001, 5110], [1001, 2001], [3, 6]], '50', '12'], [[4171, 21292], [4171, 8338], [13, 25]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('normal control', [[[999, 1001], [1001, 1003]], '24000/1001', '25'], [[958, 960], [960, 962]]), ('normal control', [[[12345, 12851], [0, 1000], [7, 2008]], '48', '24'], [[24690, 25702], [0, 2000], [14, 4016]]), ('normal control', [[[12345, 13204], [1001, 3749]], '23.976', '30'], [[9866, 10553], [800, 2996]])]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: start rounding | [[5, 6]] | [[5, 6]] | Passed |
| regression variant: start rounding | [[2, 8], [14, 18], [24665, 28663], [2000, 2002]] | [[2, 8], [14, 18], [24665, 28663], [2000, 2002]] | Passed |
| partial repair probe: start rounding | [[522942, 525444]] | [[522943, 525444]] | Failed |
| partial repair variant: start rounding | [[0, 2], [4, 5], [0, 2]] | [[1, 2], [4, 5], [1, 2]] | Failed |
| boundary control | [[500, 1000]] | [[500, 1000]] | Passed |
| boundary control | [[1000, 3000]] | [[1000, 3000]] | Passed |
| normal control | [[1, 1001]] | [[1, 1001]] | Passed |
| normal control | [[901285, 902954], [0, 5]] | [[901285, 902954], [0, 5]] | Passed |
| normal control | [[999, 2714], [12345, 13345], [1, 2002]] | [[999, 2714], [12345, 13345], [1, 2002]] | Passed |
SHA-256 / 4bcd44c0488ce47402172ce3e46bf571b3cace9989939672f4ce88227484550e
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(cues, src, dst):
def rate(r):
if '/' in r:
a,b=r.split('/')
return Fraction(int(a),int(b))
if '.' in r:
return Fraction(int(float(r)+0.5)*1000,1001)
return Fraction(int(r))
k=rate(src)/rate(dst)
out=[]
for start,end in cues:
rs=math.floor(start*k+Fraction(1,2))
re_=math.floor(end*k+Fraction(1,2))
if re_<=rs:
re_=rs+1
out.append([rs,re_])
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: start rounding', [[[5, 6]], '24', '25'], [[5, 6]]), ('regression variant: start rounding', [[[1, 4], [7, 9], [12345, 14346], [1001, 1002]], '24000/1001', '12'], [[2, 8], [14, 18], [24665, 28663], [2000, 2002]]), ('partial repair probe: start rounding', [[[418354, 420355]], '60', '48'], [[522943, 525444]]), ('partial repair variant: start rounding', [[[1, 3], [7, 8], [1, 3]], '24', '48'], [[1, 2], [4, 5], [1, 2]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('normal control', [[[1, 1001]], '30000/1001', '29.97'], [[1, 1001]]), ('normal control', [[[540231, 541231], [0, 3]], '50', '29.97'], [[901285, 902954], [0, 5]]), ('normal control', [[[999, 2714], [12345, 13345], [1, 2002]], '29.97', '30000/1001'], [[999, 2714], [12345, 13345], [1, 2002]])], [('regression: start rounding', [[[1, 2], [3, 4]], '23.976', '25'], [[1, 2], [3, 4]]), ('regression variant: start rounding', [[[3, 6]], '30', '48'], [[2, 4]]), ('partial repair probe: start rounding', [[[1001, 2001]], '15', '30'], [[501, 1001]]), ('partial repair variant: start rounding', [[[3, 4], [999, 1999], [7, 1584], [12345, 14346]], '25', '50'], [[2, 3], [500, 1000], [4, 792], [6173, 7173]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('normal control', [[[1001, 1003], [7, 4855], [12345, 13345]], '50', '50'], [[1001, 1003], [7, 4855], [12345, 13345]]), ('normal control', [[[7, 9], [3, 6], [0, 3], [1, 4]], '24', '23.976'], [[7, 9], [3, 6], [0, 3], [1, 4]]), ('normal control', [[[3, 1003], [999, 1000], [3, 5]], '50', '30'], [[5, 1672], [1665, 1667], [5, 8]])], [('regression: start rounding', [[[7, 10], [1001, 4303], [12345, 12348], [0, 101]], '25', '29.97'], [[6, 8], [835, 3589], [10298, 10300], [0, 84]]), ('regression variant: start rounding', [[[583859, 583862], [0, 1000], [3, 6], [881562, 881564]], '25', '30'], [[486549, 486552], [0, 833], [3, 5], [734635, 734637]]), ('partial repair probe: start rounding', [[[1, 4995]], '25', '50'], [[1, 2498]]), ('partial repair variant: start rounding', [[[348744, 348746], [282090, 282093]], '15', '12'], [[435930, 435933], [352613, 352616]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('normal control', [[[594534, 596535], [7, 10]], '29.97', '29.97'], [[594534, 596535], [7, 10]]), ('normal control', [[[821125, 821127], [0, 1]], '30', '30'], [[821125, 821127], [0, 1]]), ('normal control', [[[7, 1007]], '48', '30'], [[11, 1611]])], [('regression: start rounding', [[[269050, 271051]], '30000/1001', '23.976'], [[336313, 338814]]), ('regression variant: start rounding', [[[999, 4970]], '30', '60'], [[500, 2485]]), ('partial repair probe: start rounding', [[[3, 2004], [7, 10], [566844, 568845]], '25', '30'], [[3, 1670], [6, 8], [472370, 474038]]), ('partial repair variant: start rounding', [[[1, 4], [12345, 12348], [7, 10], [3, 6]], '25', '30'], [[1, 3], [10288, 10290], [6, 8], [3, 5]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('normal control', [[[1, 4]], '24000/1001', '50'], [[0, 2]]), ('normal control', [[[0, 3], [1001, 3002]], '12', '48'], [[0, 1], [250, 751]]), ('normal control', [[[3, 2004]], '24000/1001', '30000/1001'], [[2, 1603]])], [('regression: start rounding', [[[268354, 268356], [12345, 14346], [0, 42]], '60', '25'], [[644050, 644054], [29628, 34430], [0, 101]]), ('regression variant: start rounding', [[[1, 4]], '23.976', '15'], [[2, 6]]), ('partial repair probe: start rounding', [[[999, 1999]], '50', '60'], [[833, 1666]]), ('partial repair variant: start rounding', [[[1001, 5110], [1001, 2001], [3, 6]], '50', '12'], [[4171, 21292], [4171, 8338], [13, 25]]), ('boundary control', [[[1000, 2000]], '25', '50'], [[500, 1000]]), ('boundary control', [[[1001, 3003]], '24000/1001', '24'], [[1000, 3000]]), ('normal control', [[[999, 1001], [1001, 1003]], '24000/1001', '25'], [[958, 960], [960, 962]]), ('normal control', [[[12345, 12851], [0, 1000], [7, 2008]], '48', '24'], [[24690, 25702], [0, 2000], [14, 4016]]), ('normal control', [[[12345, 13204], [1001, 3749]], '23.976', '30'], [[9866, 10553], [800, 2996]])]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: start rounding | [[5, 6]] | [[5, 6]] | Passed |
| regression variant: start rounding | [[2, 8], [14, 18], [24665, 28663], [2000, 2002]] | [[2, 8], [14, 18], [24665, 28663], [2000, 2002]] | Passed |
| partial repair probe: start rounding | [[522943, 525444]] | [[522943, 525444]] | Passed |
| partial repair variant: start rounding | [[1, 2], [4, 5], [1, 2]] | [[1, 2], [4, 5], [1, 2]] | Passed |
| boundary control | [[500, 1000]] | [[500, 1000]] | Passed |
| boundary control | [[1000, 3000]] | [[1000, 3000]] | Passed |
| normal control | [[1, 1001]] | [[1, 1001]] | Passed |
| normal control | [[901285, 902954], [0, 5]] | [[901285, 902954], [0, 5]] | Passed |
| normal control | [[999, 2714], [12345, 13345], [1, 2002]] | [[999, 2714], [12345, 13345], [1, 2002]] | Passed |
SHA-256 / ff2392c1eaf9822ad4557705a7469e67aa47f91e38bc64a31f6036ae340397a5
Verification & scope
A deterministic bounded teaching model with a stipulated toy contract; it does not claim conformance to any subtitle standard. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:49:30.602844+00:00.
Case digest / ff1d4378edad8e206d6b674c9a1a7e58c339d1f4431e3b47e852c546da29a51f