How to extract video chapters for lms content structure
- Step 1Use a chaptered course master — The recording must already have chapters — written by HandBrake, your lecture-capture system, or an NLE's marker export. If it has none, there's nothing to read; place markers at the encode stage first.
- Step 2Drop the video file — Drag the MP4, MKV, or MOV onto the tool. One file per run. Free tier handles up to 1 GB; a long high-resolution lecture may need Pro (10 GB) or higher.
- Step 3FFmpeg.wasm reads the chapters locally — JAD runs
ffmpeg -i <file> -c copy -t 0 -f null -, parsing the container metadata without decoding the lecture — student footage stays on your machine. - Step 4Each chapter becomes a section object — Markers become
{ start, end, title }with times in seconds and titles verbatim (ornull). These map directly onto module sections / activities. - Step 5Download the JSON — Saves as
<filename>-chapters.jsonin the{ chapters: [...] }shape. - Step 6Build your LMS navigation or cue points — Transform each chapter into a player cue point, a SCORM menu entry, or an xAPI cue-point extension. Seconds make it a direct map; titles become the visible section labels.
Mapping chapters onto LMS concepts
The chapter JSON drives several common LMS consumers — you build the consumer, the tool supplies the data.
| Chapter field | LMS use | How you consume it | Notes |
|---|---|---|---|
start (seconds) | Cue point / seek target | player.currentTime = start | Direct — players use seconds |
title | Section / activity label | Menu text, SCORM item title | Replace null with a label first |
end (seconds) | Section length / completion gate | Mark section complete at end | Length = end - start |
chapters.length | Number of modules / activities | Outline size | Empty [] → no chapters authored |
Will your course video carry chapters?
Lecture-capture and authoring tools differ in whether they write chapter metadata.
| Source | Chapters in file? | Titles? | What to do |
|---|---|---|---|
| HandBrake encode with chapters | Yes | Yes / generic | Extract directly |
| NLE export with markers → chapters | If exported as chapters | Yes | Extract; titles from your markers |
| Lecture-capture (Panopto/Kaltura download) | Varies | Varies | Extract; may be empty if not authored |
| Raw Zoom / Teams recording | No | n/a | Empty [] — author chapters at encode |
Cookbook
Course-video JSON and an LMS cue-point build. Times in seconds; titles verbatim or null.
A chaptered lecture mapped to module sections
A 40-minute lecture encoded with chapters per topic. Each chapter becomes a module section.
Input: module3-lecture.mp4 (chapters per topic)
Output: module3-lecture-chapters.json
{
"chapters": [
{ "start": 0, "end": 480.0, "title": "Overview" },
{ "start": 480.0, "end": 1440.0, "title": "Theory" },
{ "start": 1440.0, "end": 2160.0, "title": "Worked example" },
{ "start": 2160.0, "end": 2400.0, "title": "Summary" }
]
}Building HTML5 player cue points
Turn each chapter into a clickable seek link in a course page. Seconds drop straight into the player's currentTime.
const { chapters } = require('./module3-lecture-chapters.json');
const links = chapters.map(c =>
`<button onclick="v.currentTime=${c.start}">${c.title ?? 'Section'}</button>`
);
document.getElementById('nav').innerHTML = links.join('');
// learners jump to any section without scrubbingAn empty array — no chapters authored
A raw Zoom lecture recording has no chapter markers. Author them at the encode stage; the tool can't invent structure that isn't there.
Input: zoom-lecture.mp4 (no chapters)
Output: zoom-lecture-chapters.json
{
"chapters": []
}
→ Re-encode with chapters (HandBrake), or derive cuts
with scene-detector, then map to sections.Emitting xAPI-style cue points
Shape chapters into a cue-point list you can attach to an xAPI statement's extensions, keyed by seconds.
const { chapters } = require('./module3-lecture-chapters.json');
const cues = chapters.map((c, i) => ({
id: `section-${i + 1}`,
startTime: c.start,
title: c.title ?? `Section ${i + 1}`
}));
console.log(JSON.stringify({ 'https://example.org/cuepoints': cues }, null, 2));Computing section lengths for completion gates
Use seconds arithmetic to set a 'watched 90%' completion threshold per section.
const { chapters } = require('./module3-lecture-chapters.json');
for (const c of chapters) {
const len = c.end - c.start;
console.log((c.title ?? 'section'), 'complete at', (c.start + len * 0.9).toFixed(0), 's');
}Edge cases and what actually happens
Course video has no chapters
Empty arrayReturns { "chapters": [] }. A raw Zoom/Teams lecture has no markers — author them at the encode stage (HandBrake) or derive section boundaries with the scene-detector, then map those to modules.
Chapter titles are generic (Chapter 1, 2…)
RelabelA HandBrake encode often writes generic names. Replace them by index with your real topic titles before they become section labels (see the cue-point examples).
Titles are null
title: nullSome encodes write boundaries without names. The start/end are exact and still drive cue points; supply human-readable section labels in your LMS mapping.
FERPA / student-data sensitivity
By designFootage with student faces or names stays on your machine — FFmpeg.wasm reads only the header and nothing uploads. If you also need to redact faces before publishing, that's a separate step with the face-blur tool.
Non-ASCII section titles
PreservedTitles in any language read as UTF-8 and write verbatim, so multilingual course outlines come through intact for your LMS menu.
Very long lecture (90 min+)
SupportedHeader-only parsing reads chapters in seconds regardless of runtime. Free tier caps the file at 1 GB; a long HD lecture may need Pro (10 GB) or higher.
You expected SCORM/xAPI output directly
Different formatThis tool outputs chapter JSON, not a SCORM imsmanifest.xml or xAPI statements. Use the JSON as input to your packaging step — the cookbook shows cue-point and xAPI-extension shapes you build from it.
You want to split the lecture per section
Different toolExtraction reads markers only; it doesn't cut video. To produce one file per section use the video-splitter, or the lossless-trimmer for a single section clip.
Last section's end equals total runtime
ExpectedWhen the final chapter omits an end, FFmpeg fills it from the file duration. So the last section's end legitimately equals the lecture length — fine for a completion gate.
Frequently asked questions
How do chapters map onto an LMS module structure?
Each chapter's title becomes a section/activity label and its start (seconds) becomes a seek target or cue point. The array length is your number of sections. You build the SCORM menu, xAPI cue points, or HTML5 nav from this JSON — the cookbook shows each.
Does it output SCORM or xAPI directly?
No. It outputs chapter JSON ({ chapters: [{ start, end, title }] }). That JSON is the input to your packaging step — you transform it into a SCORM manifest entry or xAPI cue-point extension (examples in the cookbook).
Is student footage uploaded anywhere?
No. FFmpeg.wasm reads the container header in your browser and the video bytes never leave the machine. That keeps faces and names off any server, which helps with FERPA and institutional data policies.
My lecture has no chapters — what now?
Raw Zoom/Teams recordings have none. Re-encode with chapters in HandBrake, or derive section boundaries from visual cuts with the scene-detector, then map those timestamps to your modules. The tool can't create structure that isn't in the file.
What's the output shape and filename?
{ "chapters": [{ "start": <seconds>, "end": <seconds>, "title": <string or null> }] }, saved as <filename>-chapters.json. Start/end are floating-point seconds; titles are verbatim or null.
Why are times in seconds rather than HH:MM:SS?
Because players seek by seconds (currentTime = start) and completion gates compute from seconds (end - start). It's the directly usable form for LMS cue points; format to timecode only for display.
Can I relabel generic chapter titles?
Yes, in your mapping code — title: c.title ?? topics[i]. Generic Chapter N names or null titles get replaced with your real topic names before they appear as section labels.
Will it split the lecture into per-section files?
No — it reads markers only. To create one file per section use the video-splitter, or the lossless-trimmer to pull a single section. Extraction gives you the timestamps to drive either.
Can I redact students before publishing the course?
That's a separate tool — chapter extraction doesn't alter the video. For privacy, blur faces with the face-blur tool, then extract chapters from the redacted master.
How long a lecture can I process?
No duration cap — only file size per tier: Free 1 GB, Pro 10 GB, Pro-media 100 GB, Developer 100 GB. Header-only reading keeps even a 2-hour lecture fast.
Do multilingual section titles survive?
Yes. Titles read as UTF-8 and write verbatim, so non-English section names come through intact for your LMS navigation menu.
Why does my last section end at the total runtime?
If the final chapter omits an end time, FFmpeg fills it from the file duration, so the last section's end equals the lecture length. That's expected and works fine as a completion boundary.
Privacy first
Every JAD Video tool runs entirely in your browser via WebCodecs and FFmpeg (WebAssembly). Your video files never leave your device — verified by zero outbound network requests during processing.