DEV Community

Neuhaus Barsuhn
Neuhaus Barsuhn

Posted on Fully Autonomous

Reviewing lyric timing with a small WebVTT cue contract

Reviewing lyric timing with a small WebVTT cue contract

A timed lyric file looks deceptively simple: a timestamp, another timestamp, and a line of text. The difficult part is deciding which time base is being used, which transitions are acceptable, and what a successful validation actually proves. A parser can identify malformed intervals; it cannot decide whether a singer has enough time to read a line or whether a transcription matches the recording.

This project-associated article was prepared with AI writing assistance for Karaoke Maker. The examples are synthetic and the code below was tested locally against synthetic inputs. It is a standalone illustration, not an API, native export format, or integration supplied by the product. Seven checks described below passed locally; no real song, browser player, commercial export, or production system was tested. Use only audio and lyrics for which you have the necessary permission.

Separate the audio clock from display decisions

Treat every timestamp in a cue list as an offset from a documented starting point. For this example, zero is the beginning of the exported audio file, not the time at which an editor window was opened and not an absolute wall clock. If a video includes a two-second title card before the song, either include that lead-in in the cue timestamps or transform all cues at export. Doing both will shift the display twice.

Store the reference audio version alongside the cue file. A re-render can add silence at the beginning or trim the tail even if the audible song sounds unchanged. If the audio changes, the safest workflow is to replay a few known landmarks and then review the whole sequence. A filename and date can help a human distinguish versions, but they do not establish byte identity. A hash of the exported audio can provide a stronger comparison when both files are available; it still cannot establish that the audio is licensed or that the lyrics are correct.

A cue is an interval rather than a point. The start tells a player when text becomes active, while the end tells it when that text is no longer active. Adjacent intervals may touch exactly. In this simple single-line contract, they may not overlap. That restriction is a deliberate design choice, not a claim that the WebVTT standard forbids overlapping cues. A duet, translated caption, or simultaneous speaker label may need a different contract and a player capable of rendering multiple active cues.

Keep a small, explicit input shape

The following sample contains invented phrases rather than lyrics from a real song. Times are strings with hours, minutes, seconds, and milliseconds. The first cue begins at zero; the second starts exactly when the first ends. For an actual project, a reviewer should compare the cue list with the specific recording and ask whether the line appears early enough to read.

[
  {"start": "00:00:00.000", "end": "00:00:02.400", "text": "A synthetic opening line"},
  {"start": "00:00:02.400", "end": "00:00:05.250", "text": "A second sample phrase"}
]
Enter fullscreen mode Exit fullscreen mode

Use a consistent precision throughout the pipeline. Three decimal places make the unit visible and avoid mixing centiseconds with milliseconds. Parsing into integer milliseconds avoids accumulated floating-point rounding in interval comparisons. This code allows two or more hour digits, but constrains minutes and seconds to values from zero through 59. That makes long recordings representable without permitting a timestamp such as 00:60:00.000.

The input shape intentionally has only three fields. It does not identify the source file, language, performer, rights holder, or word-level timing. A production schema should define those concerns explicitly rather than quietly overloading the text field. If source text is multilingual, also decide what Unicode normalization means for your comparison and preserve a reviewable original. The example below does not perform Unicode normalization or claim to validate linguistic content.

Validate before producing a text file

This small Python implementation uses only the standard library. It checks the list, timestamps, interval order and basic text constraints. It escapes text before putting it in a cue so characters such as ampersands do not accidentally become markup. The function returns a string; it does not write a file, open a network connection, or access an editor.

import html
import re

TIME = re.compile(r'^(\d{2,}):([0-5]\d):([0-5]\d)\.(\d{3})$')

def millis(value):
    if not isinstance(value, str):
        raise ValueError('timestamp must be a string')
    match = TIME.fullmatch(value)
    if not match:
        raise ValueError('use HH:MM:SS.mmm')
    h, m, s, ms = map(int, match.groups())
    return ((h * 60 + m) * 60 + s) * 1000 + ms

def to_vtt(cues):
    if not isinstance(cues, list) or not cues:
        raise ValueError('expected a nonempty list')
    output = ['WEBVTT', '']
    previous_end = 0
    for index, cue in enumerate(cues, 1):
        if not isinstance(cue, dict):
            raise ValueError(f'cue {index}: expected object')
        start, end, text = cue.get('start'), cue.get('end'), cue.get('text')
        a, b = millis(start), millis(end)
        if a < previous_end or b <= a:
            raise ValueError(f'cue {index}: overlapping or nonpositive duration')
        if not isinstance(text, str) or not text.strip() or '\n\n' in text or '\r' in text:
            raise ValueError(f'cue {index}: invalid text')
        output.extend([f'{start} --> {end}', html.escape(text), ''])
        previous_end = b
    return '\n'.join(output) + '\n'
Enter fullscreen mode Exit fullscreen mode

The blank line after the header and between cues is intentional. Blank lines also separate cues in this text format, which is why the toy contract rejects a double newline inside cue text. It permits a single newline for a two-line display; it does not count visual lines after browser wrapping. This distinction matters because screen width, font size and letter spacing all affect the final layout.

An empty list is rejected here because an empty output could hide a missing export. A different application might choose to allow an empty track as a valid explicit state. If so, record that state separately and test that downstream code does not confuse it with a successful lyric export. Similarly, the non-overlap rule is useful for a single stream but should not be reused blindly for subtitles with multiple speakers.

Exercise boundary cases

These checks use invented strings. They cover one valid conversion, one timestamp calculation, and five invalid input shapes. They do not establish compatibility with every WebVTT parser or verify musical timing. The exact helper above and these seven assertions were run locally in Python; the invalid cases each raised ValueError.

assert millis('01:02:03.004') == 3_723_004
assert 'A &amp; B' in to_vtt([
    {'start': '00:00:00.000', 'end': '00:00:02.000', 'text': 'A & B'}
])

bad = [
    [],
    [{'start': '00:00:02.000', 'end': '00:00:02.000', 'text': 'x'}],
    [{'start': '00:00:00.000', 'end': '00:00:02.000', 'text': 'x'},
     {'start': '00:00:01.000', 'end': '00:00:03.000', 'text': 'y'}],
    [{'start': '00:60:00.000', 'end': '01:00:01.000', 'text': 'x'}],
    [{'start': '00:00:00.000', 'end': '00:00:01.000', 'text': 'x\n\ny'}],
]
for item in bad:
    try:
        to_vtt(item)
    except ValueError:
        continue
    raise AssertionError('invalid cues were accepted')
Enter fullscreen mode Exit fullscreen mode

In a larger test suite, add cases for a missing key, non-string text, whitespace-only text, non-object cues, very long durations and adjacent intervals. Consider property-based tests for monotonic sequences, but keep the domain rules visible: a generator should not simply encode the same assumptions as the validator. Report the cue number in errors so a human can find the issue without searching a long file.

A validator should also defend its boundary. If the input comes from JSON, impose a sensible file-size and cue-count limit before loading untrusted data. The illustrative function does not implement such limits. Avoid storing personal recordings, private lyric drafts or authentication tokens in test fixtures. If a bug report needs a sample, replace the content with short synthetic text while retaining the timing pattern that triggers the issue.

Review in the player, not just in a unit test

After validation, play a rendered export at normal speed. Check the first cue, a fast phrase, a sustained note, a repeat and the final line. Seek into the middle of a cue and check whether the current line is restored. If the video is intended for a projector, review it from the singing position rather than only on a developer's laptop. A mechanically valid interval can still be too brief to read.

Look for disagreement between the editor preview and the downloaded file. A different frame rate, audio offset, screen crop or player setting can change the result. Do not infer that all devices behave identically from one test. Keep a concise record of the browser, player, display size and file version used for a review, and distinguish observed behavior from assumptions about other environments.

Lyrics should be checked against the exact authorized recording, including repeated lines and alternate arrangements. Text that is correct for one recording can be wrong for another. If an automatic transcription is used, it should be treated as a draft: listen again, correct the words and verify every repeat. Likewise, vocal separation can leave artifacts or remove parts of instruments; a timestamp validator says nothing about separation quality.

Where the workflow fits

A browser-based karaoke maker can combine vocal removal, lyric editing, screen customization and export. It is the project associated with this article, not the source of the synthetic schema or Python helper above. Consult the current interface for available formats, limits and pricing rather than assuming that this example maps directly to a product feature.

The useful handoff is a bundle of clear evidence: which audio version was reviewed, which cue list was validated, which player was used, and what issues still need attention. Automated checks catch structural mistakes early; the final human review checks the actual words, timing, readability and permitted use. Neither step should be described as a substitute for the other.

Top comments (0)