On this page
Treat WebVTT timing as a contract
The source dates shown in this guide identify documentation checks on 22 October 2026, not original publication dates. Follow the citation for the WebVTT specification being discussed.
The World Wide Web Consortium, (opens in a new tab) specification describes a WebVTT file body as UTF-8 text with the text/vtt MIME type. A cue has a start timestamp, a separator made of two hyphens followed by a greater-than sign, and an end timestamp. The start must be at least the start offsets of previous cues, and the end must be later than the cue start. Cue identifiers cannot contain a line break or that separator. The companion World Wide Web Consortium, (opens in a new tab) material is a separate presentation reference, not an identity or accuracy test.
The specification permits overlapping cues in general. A local transcript contract may forbid overlap for a single-speaker caption track, while a multi-speaker or metadata track may intentionally use nested or overlapping cues. Record track purpose before classifying overlap as a failure. Keep cue syntax, speaker annotation, and content accuracy as separate review dimensions.
| Dimension | Record | Do not infer |
|---|---|---|
| Text encoding | UTF-8 contract and parser result | That readable output proves every byte was valid |
| Cue order | Cue identifier, start, end, and sequence | That file order is business or speaker order |
| Duration | End minus start and duration policy | That a short cue is missing speech |
| Speaker token | Synthetic or approved category code | That a voice label proves identity |
| Alignment | Anchor type and tolerance | That a timing match validates words |
| Overlap | Track purpose and local policy | That every overlap is invalid |
Create a synthetic cue and anchor fixture
Use labels that cannot be confused with a real person and keep transcript text short. Pair a synthetic WebVTT fixture with an approved reference-anchor worksheet, not a copied recording. Name whether the anchor came from a synthetic generator, a manual approval, or another controlled annotation.
Synthetic WebVTT cues
WEBVTT
cue-001
00:00:00.000 --> 00:00:01.800
<v SPEAKER_A>synthetic greeting</v>
cue-002
00:00:01.900 --> 00:00:03.400
<v SPEAKER_B>synthetic response</v>Synthetic approved anchors
[
{"speaker": "SPEAKER_A", "start": 0.000, "end": 1.800},
{"speaker": "SPEAKER_B", "start": 1.900, "end": 3.400}
]The values and labels are invented. If no approved anchor exists, the result is UNVERIFIED, not a failed transcript. A voice span carrying the token SPEAKER_A records an annotation; it does not prove that a diarization process identified a person or that the text is accurate. Keep real names and raw speech out of a public diagnostic.
Check timestamps and speaker tokens with bounded arithmetic
A local tolerance is an input, not a WebVTT rule. Choose it from the recording and annotation process, and record the decision. A bounded comparison can retain cue indexes and stable classes without copying cue text, voice names, or media paths.
Bounded cue and anchor comparison
import re
TIMESTAMP = re.compile(r"^(?:(\d+):)?(\d{2}):(\d{2})\.(\d{3})$")
def seconds(value):
match = TIMESTAMP.fullmatch(value)
if not match: raise ValueError("invalid_timestamp")
hours = int(match.group(1) or 0)
minutes, seconds_part = int(match.group(2)), int(match.group(3))
millis = int(match.group(4))
if minutes >= 60 or seconds_part >= 60: raise ValueError("invalid_timestamp")
return hours * 3600 + minutes * 60 + seconds_part + millis / 1000
def compare_cues(cues, anchors, tolerance=0.25, max_items=5000):
findings, previous, alignment_unverified = [], None, False
for index, cue in enumerate(cues):
if index >= max_items:
findings.append({"kind": "item_limit_reached"}); alignment_unverified = True; break
start, end = seconds(cue["start"]), seconds(cue["end"])
if end <= start: findings.append({"index": index, "kind": "non_positive_duration"})
if previous is not None and start < previous: findings.append({"index": index, "kind": "start_out_of_order"})
previous = start
if index >= len(anchors):
findings.append({"index": index, "kind": "anchor_missing"}); alignment_unverified = True; continue
anchor = anchors[index]
if abs(start - anchor["start"]) > tolerance or abs(end - anchor["end"]) > tolerance: findings.append({"index": index, "kind": "outside_alignment_tolerance"})
if cue.get("speaker") != anchor.get("speaker"): findings.append({"index": index, "kind": "speaker_token_mismatch"})
inspected_cues = min(len(cues), max_items)
for index in range(inspected_cues, min(len(anchors), max_items)):
findings.append({"index": index, "kind": "cue_missing_for_anchor"}); alignment_unverified = True
if len(anchors) > max_items:
findings.append({"kind": "anchor_limit_reached"}); alignment_unverified = True
if alignment_unverified: findings.append({"kind": "alignment_unverified"})
return findingsThe function bounds the number of inspected items and emits stable classes without copying text or media identifiers. A cue without a corresponding anchor is anchor_missing, while an extra anchor without a cue is cue_missing_for_anchor; either state is unverified alignment, not a pass. A real implementation also needs to parse cue identifiers, settings, voice spans, UTF-8 errors, and multi-line payloads under its chosen parser contract. Keep parser failures separate from a valid cue whose speaker or anchor does not match.
Interpret overlap, gaps, and speaker changes locally
For each cue, record cue identifier, start, end, duration, speaker-token state, anchor reference, tolerance policy, overlap state, gap to the next cue, and disposition. Do not call a gap a missing transcript unless the track contract says every spoken interval must be represented. Do not call an overlap invalid unless the track purpose or local policy forbids it.
| Observation | Safe interpretation | Next action |
|---|---|---|
| Header contract fails | The selected file is not the stated WebVTT form | Preserve source and classify header failure |
| Cue end is not later than start | The cue interval is structurally invalid | Hold the cue; do not guess duration |
| Cue starts decrease | Cue order conflicts with the timing rule | Preserve identifiers and request correction |
| Cues overlap | Intervals overlap under a track-specific choice | Compare track purpose and overlap policy |
| Gap exists between cues | No cue covers that interval | Compare anchors; do not infer missing speech |
| Speaker token changes | Annotation changed between cues | Compare approved mapping; do not infer identity |
| Token matches anchor within tolerance | Structural alignment observation passed | Keep tolerance and anchor source; do not infer text accuracy |
| No approved anchor exists | Alignment cannot be measured | Mark UNVERIFIED and avoid a pass claim |
| Limit reached or cue parse failed | Coverage or syntax is incomplete | Retain counts and stable classes; do not publish raw payload |
If a cue contains a voice span, store a normalized token such as SPEAKER_A only when the approved policy permits it. A speaker token can still be identifying when the track is small, so use synthetic labels or approved category codes. The same caution applies to words: a timing match does not validate what was said.
Keep diagnostics safe and the next step qualified
A public report can contain cue indexes, durations, gap and overlap counts, stable speaker-token states, tolerance, anchor type, and parser version. It should not contain raw transcript text, voice names, private paths, audio, faces, or unbounded parse errors. Keep the raw transcript within the owner-approved handling boundary.
Pair this check with the timestamp and timezone guide and data provenance log. Those resources own general timestamp semantics and source, rights, and processing evidence; this guide owns bounded cue, speaker-token, and anchor alignment observations.
After the owner has a supported non-health scope, authority to describe the transcript, and a metadata or synthetic alignment packet, the qualified appraisal request path may be a next preparation step. It does not validate transcripts, identify speakers, certify accessibility, produce a quote, match a buyer, issue a legal opinion, or grant a license. Keep raw speech outside this guide and within the owner-approved handling boundary.
Takeaways
- Validate WebVTT header, UTF-8 contract, cue start order, and positive duration before semantic review.
- Treat overlap and gaps as track-policy questions, not universal errors.
- Compare speaker tokens to approved synthetic or controlled anchors, and never treat labels as identity proof.
- Record alignment tolerance and anchor source; no anchor means unverified measurement.
- Publish stable counts and classes, not raw speech, private labels, or media paths.