<bmx-caption-editor>
Captions and subtitles made and corrected in the page, against the media they belong to: for training and e-learning teams, broadcasters and publishers, transcription services, and anyone who has to meet a captioning requirement.
15 properties · 3 events · 21 methods · 5 parts
Example
A first draft of the induction's captions, with the faults a quick transcription leaves: an overlap, a caption too short to read, long lines and two captions run together. Try "Fix automatically", then split and re-time what is left.
Show markup
<bmx-caption-editor
id="ex-captions"
label="Induction captions"
src="assets/media/induction.mp4"
poster="assets/media/induction-poster.jpg"
language="en"
speakers="Narrator, Priya"
file-name="induction"
style="--bmx-caption-editor-height: 16rem"
></bmx-caption-editor>
<p class="note" id="ex-captions-out" role="status">A first draft of the induction's captions, with the faults a quick transcription leaves: an overlap, a caption too short to read, long lines and two captions run together. Try "Fix automatically", then split and re-time what is left.</p>
<div class="row" style="gap: 0.75rem; flex-wrap: wrap; align-items: center; margin-block: 0.5rem 1.5rem">
<label>Rules
<select id="ex-captions-rules">
<option value="streaming">Streaming (42 characters, 20 a second)</option>
<option value="broadcast">Broadcast (37 characters, 17 a second)</option>
<option value="education">Education (32 characters, 15 a second)</option>
</select>
</label>
<button type="button" id="ex-captions-vtt">Show the WebVTT</button>
</div>
<pre id="ex-captions-text" hidden style="max-block-size: 16rem; overflow: auto"></pre>
<script type="module">
await customElements.whenDefined('bmx-caption-editor');
const editor = document.getElementById('ex-captions');
const out = document.getElementById('ex-captions-out');
const draft = await (await fetch('assets/media/induction-draft.srt')).text();
await editor.setCues(draft, { keepHistory: false });
editor.addEventListener('bmxChange', async e => {
const problems = await editor.check();
out.textContent = `${e.detail.cues.length} captions (${e.detail.reason}); ${problems.length ? `${problems.length} problem${problems.length === 1 ? '' : 's'} left` : 'no problems left'}.`;
});
document.getElementById('ex-captions-rules').addEventListener('change', e => (editor.rules = e.target.value));
document.getElementById('ex-captions-vtt').addEventListener('click', async () => {
const pre = document.getElementById('ex-captions-text');
pre.textContent = await editor.exportAs('vtt');
pre.hidden = false;
});
</script>
WHAT IT DOES
- The media plays with the captions drawn over it as they are edited.
- A waveform timeline, read from the media in the page (or from a peaks file for very long media), with the captions as blocks: drag to move, drag an edge to re-time, drag across sound to add one, snapping to the neighbouring captions and the playhead.
- A list of every caption with its timing, reading speed and problems, and a panel for the one chosen: times typed or set from the playhead, the speaker, top or bottom, and the text with a count for every line.
- Every caption is checked as it changes against a set of rules (line length, lines, reading speed, durations, gaps, overlaps); what can be fixed without a person is fixed with one action.
- Timing tools: shift, sync to two points (for another cut or frame rate), snap to frames, and rough timing of a plain transcript to the speech.
- WebVTT, SRT, transcripts and JSON in and out; undo and redo.
Nothing leaves the page: the media is read where it is, and the captions
are given back through cues, bmxChange and the export methods.
dist/node/bmx-caption-editor.mjs has the same engine for a server.
KEYBOARD
Everything the pointer does on the timeline can be done from the list and the panel, and the editing commands have shortcuts that work while typing: Control+Space plays and pauses; Alt with [ or ] sets the start or end to the playhead; Alt+N adds, Alt+S splits, Alt+M merges, Alt+Delete deletes; Alt+Up/Down move between captions; Alt+, and Alt+. step the playhead.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
crossorigin |
crossorigin |
'anonymous' | 'use-credentials' |
— | How the media is requested from another site. The waveform needs the site to allow it. |
cues |
cues |
readonly BmxCue[] | string |
— | The captions: an array of cues, its JSON, or WebVTT or SRT text. Kept up to date as they are edited. |
fileName |
file-name |
string |
'captions' |
The name files are saved under, without the extension. |
follow |
follow |
boolean |
true |
Keep the playhead in view on the timeline, and the playing caption in the list, while the media plays. |
kind |
kind |
'video' | 'audio' |
'video' |
video shows the picture; audio keeps only the transport and the waveform. |
label |
label |
string |
— | The editor's accessible name. |
language |
language |
string |
— | The captions' language, for spelling and the text's lang. |
peaks |
property only | unknown |
— | A waveform made elsewhere (audiowaveform JSON, or { rate, data }), for media too long to read in the page. |
poster |
poster |
string |
— | A picture shown before the video plays. |
readonly |
readonly |
boolean |
false |
Look but do not change. |
rules |
rules |
BmxCaptionPreset | Partial<BmxCaptionRules> | string |
'streaming' |
The rules captions are checked against: a preset (streaming, broadcast, education, relaxed) or limits (an object or its JSON). |
speakers |
speakers |
readonly string[] | string |
— | Speakers offered in the speaker field, besides those already in the captions (an array, or a comma-separated list). |
src |
src |
string |
— | The video or audio the captions are for. |
strings |
strings |
Partial<BmxCaptionEditorStrings> | string |
— | Replacements for any of the wording (an object, or its JSON). |
zoom |
zoom |
number |
100 |
Pixels a second on the timeline. |
Events
| Event | Detail | Description |
|---|---|---|
bmxChange |
BmxCaptionEditorChangeDetail |
Fired when the captions change, with the whole list and why. |
bmxMediaError |
BmxCaptionEditorMediaErrorDetail |
Fired when the media cannot be played or its sound read. |
bmxSelect |
BmxCaptionEditorSelectDetail |
Fired when another caption is chosen. |
Methods
| Method | Signature | Description |
|---|---|---|
addCue |
addCue(cue?: Partial<BmxCue>) => Promise<BmxCue | null> |
Adds a caption, at the playhead when no times are given. Returns it (null when read-only). |
check |
check() => Promise<BmxCaptionProblem[]> |
Every problem against the rules. |
deleteCue |
deleteCue(id?: string) => Promise<void> |
Deletes a caption (the chosen one when no id is given). |
download |
download(format?: BmxCaptionFormat) => Promise<void> |
Saves the captions as a file in a format. |
exportAs |
exportAs(format?: BmxCaptionFormat) => Promise<string> |
The captions as text in a format. |
fix |
fix(kinds?: BmxCaptionProblemKind[]) => Promise<number> |
Fixes what can be fixed without a person (all kinds, or those given). Returns how many captions changed. |
getCues |
getCues() => Promise<BmxCue[]> |
The captions, sorted, as copies. |
getPeaks |
getPeaks() => Promise<BmxPeaks | null> |
The waveform, once read. |
importText |
importText(text: string, format?: BmxCaptionFormat) => Promise<number> |
Reads captions from text in any of the formats (by its content unless format is given), replacing what is there. Returns how many were read. |
mergeWithNext |
mergeWithNext(id?: string) => Promise<void> |
Merges a caption (the chosen one) with the one after it. |
pause |
pause() => Promise<void> |
Pauses the media. |
play |
play() => Promise<void> |
Plays the media. |
redo |
redo() => Promise<void> |
Puts back what undo took. |
seek |
seek(time: number) => Promise<void> |
Moves the playhead. |
selectCue |
selectCue(id: string | null, options?: { seek?: boolean; }) => Promise<void> |
Chooses a caption by id (or none), and moves the playhead to it. |
setCues |
setCues(cues: readonly BmxCue[] | string, options?: { keepHistory?: boolean; }) => Promise<void> |
Replaces the captions (cues, their JSON, or WebVTT, SRT or a transcript as text). Undo goes back to what was there. |
shift |
shift(seconds: number, fromId?: string) => Promise<void> |
Moves captions by a number of seconds: all of them, or from a caption on. |
splitAt |
splitAt(time?: number, id?: string) => Promise<void> |
Splits a caption (the chosen one) at a time (the playhead). |
sync |
sync(from: [number, number], to: [number, number]) => Promise<void> |
Re-times every caption so that time from[0] becomes to[0] and from[1] becomes to[1]. |
timeTranscript |
timeTranscript(text: string) => Promise<number> |
Cuts plain text into captions and spreads them over the speech in the media (or at reading speed when there is no waveform). |
undo |
undo() => Promise<void> |
Takes back the last change. |
CSS shadow parts
| Part | Description |
|---|---|
list |
The list of captions. |
panel |
The panel for the chosen caption. |
preview |
The media with the captions over it. |
timeline |
The waveform and caption blocks. |
toolbar |
The tools along the top. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-caption-editor-block |
A caption block's fill on the timeline. |
--bmx-caption-editor-block-chosen |
The chosen caption's block. |
--bmx-caption-editor-height |
The list's height. |
--bmx-caption-editor-playhead |
The playhead. |
--bmx-caption-editor-preview-width |
The media's width beside the panel. |
--bmx-caption-editor-radius |
Corner radius of the frame. |
--bmx-caption-editor-ruler |
The time ruler's colour. |
--bmx-caption-editor-timeline-height |
The timeline's height. |
--bmx-caption-editor-wave |
The waveform's colour. |