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
- Make sure CheckInit is called on the correct init segment and its returned timescale is passed to ExtractSub.
- Verify the init segment actually contains a wvtt sample entry (it must be a WebVTT-in-MP4 track).
- If CheckInit returns sawWVTT == false / timescale 0, treat the stream as not being VTT-in-MP4 and use a different subtitle path.
- 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
- Never hardcode 0 as timescale — always pass the value returned by CheckInit.
- Check sawWvtt/timescale from CheckInit before proceeding to ExtractSub.
- Verify the init segment contains a wvtt sample entry.
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
- Bad vtt!
- MDHD version can only be 0 or 1
- TFDT version can only be 0 or 1
- A TFHD box should have a valid flags value
- ResString.badM3u8
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)