v2.6.0

<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

PropertyAttributeTypeDefaultDescription
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

EventDetailDescription
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

MethodSignatureDescription
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

PartDescription
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

PropertyDescription
--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.