nilaoda/N_m3u8DL-RE · error · Exception

Missing timescale for VTT content!

Error message

Missing timescale for VTT content!

What it means

MP4VttUtil.ExtractSub converts per-segment media times to real timestamps using a timescale obtained from the init segment (CheckInit). If the caller passes timescale 0 — meaning CheckInit never found a usable mdhd/timescale — cues would get nonsensical timestamps, so ExtractSub refuses to proceed.

Solutions

  1. Make sure CheckInit is called on the correct init segment and its returned timescale is passed to ExtractSub.
  2. Verify the init segment actually contains a wvtt sample entry (it must be a WebVTT-in-MP4 track).
  3. If CheckInit returns sawWVTT == false / timescale 0, treat the stream as not being VTT-in-MP4 and use a different subtitle path.
  4. Catch the exception and skip subtitle extraction instead of aborting the whole download.

Example fix

// before
var (sawWvtt, timescale) = MP4VttUtil.CheckInit(initBytes);
var sub = MP4VttUtil.ExtractSub(segmentFiles, timescale); // throws when timescale==0

// after
var (sawWvtt, timescale) = MP4VttUtil.CheckInit(initBytes);
if (timescale == 0)
{
    Log.Warning("No timescale found; subtitle track is not WebVTT-in-MP4");
    return null;
}
var sub = MP4VttUtil.ExtractSub(segmentFiles, timescale);
Defensive patterns

Strategy: validation

Validate before calling

var (sawWvtt, timescale) = MP4VttUtil.CheckInit(initBytes);
if (timescale == 0)
    throw new InvalidDataException("No timescale in init segment; not a VTT-in-MP4 track");
MP4VttUtil.ExtractSub(segmentFiles, timescale);

Try / catch

try { var sub = MP4VttUtil.ExtractSub(files, ts); }
catch (Exception ex) when (ex.Message.Contains("Missing timescale"))
{ /* skip subtitle extraction for this stream */ }

Prevention

When it happens

Trigger: Calling ExtractSub(files, timescale) with timescale == 0, typically because CheckInit returned (false, 0) — no wvtt sample description or no readable mdhd in the init segment — and the caller ignored that result.

Common situations: The 'file list' contains media segments whose init segment lacks a wvtt box or a parseable mdhd, the wrong init segment was parsed, or the caller hardcoded 0 instead of using the value returned by CheckInit.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of nilaoda/N_m3u8DL-RE@e113dee70c (2026-09-13). Data as JSON: /api/errors/b1bddb5820ad10f0. Report an issue: GitHub.

Appendix: source

Thrown at src/N_m3u8DL-RE.Parser/Mp4/MP4VttUtil.cs:39

                    throw new Exception("MDHD version can only be 0 or 1");
                timescale = MP4Parser.ParseMDHD(box.Reader, box.Version);
            })
            .Box("minf", MP4Parser.Children)
            .Box("stbl", MP4Parser.Children)
            .FullBox("stsd", MP4Parser.SampleDescription)
            .Box("wvtt", _ => {
                // A valid vtt init segment, though we have no actual subtitles yet.
                sawWVTT = true;
            })
            .Parse(data);

        return (sawWVTT, timescale);
    }

    public static WebVttSub ExtractSub(IEnumerable<string> files, uint timescale)
    {
        if (timescale == 0)
            throw new Exception("Missing timescale for VTT content!");

        List<SubCue> cues = [];

        foreach (var item in files)
        {
            var dataSeg = File.ReadAllBytes(item);

            bool sawTFDT = false;
            bool sawTRUN = false;
            bool sawMDAT = false;
            byte[]? rawPayload = null;
            ulong baseTime = 0;
            ulong defaultDuration = 0;
            List<Sample> presentations = [];


            // parse media
            new MP4Parser()

View on GitHub (pinned to e113dee70c)