Add subtitles to HLS streams

View Markdown

Adaptive streams without captions fail accessibility requirements and lose viewers. “Lectern” (an online education platform like Coursera) needs every lecture to ship with subtitle tracks so students can follow along in noisy environments or different languages.

API

ittybit adaptive \
  -i https://lectern-app.com/courses/bio-201/lecture-4.mov \
  --format hls \
  --subtitle https://lectern-app.com/courses/bio-201/lecture-4.vtt \
  --subtitle-label English
const task = {
  input: 'https://lectern-app.com/courses/bio-201/lecture-4.mov',
  kind: 'adaptive_video',
  options: {
    format: 'hls',
    subtitles: [
      {
        url: 'https://lectern-app.com/courses/bio-201/lecture-4.vtt',
        label: 'English',
        language: 'en',
        default: true,
      },
    ],
  },
};

const res = await fetch('https://api.ittybit.com/jobs', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(task),
});
const data = await res.json();
import requests

task = {
    "input": "https://lectern-app.com/courses/bio-201/lecture-4.mov",
    "kind": "adaptive_video",
    "options": {
        "format": "hls",
        "subtitles": [
            {
                "url": "https://lectern-app.com/courses/bio-201/lecture-4.vtt",
                "label": "English",
                "language": "en",
                "default": True,
            },
        ],
    },
}

res = requests.post(
    "https://api.ittybit.com/jobs",
    headers={"Authorization": f"Bearer {api_key}"},
    json=task,
)
data = res.json()
TASK='{
  "input": "https://lectern-app.com/courses/bio-201/lecture-4.mov",
  "kind": "adaptive_video",
  "options": {
    "format": "hls",
    "subtitles": [
      {
        "url": "https://lectern-app.com/courses/bio-201/lecture-4.vtt",
        "label": "English",
        "language": "en",
        "default": true
      }
    ]
  }
}'

curl -X POST https://api.ittybit.com/jobs \
  -H "Authorization: Bearer $ITTYBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$TASK"

The HLS manifest references the VTT file as a subtitle track. Players that support HLS (Safari, hls.js, Video.js) show a captions toggle automatically.

CLI

ittybit adaptive \
  -i lecture-4.mov \
  -o lecture-4.m3u8 \
  --subtitle lecture-4.vtt \
  --subtitle-label English \
  --subtitle-language en

Multiple languages

Add several subtitle tracks to the same stream:

{
  "input": "https://lectern-app.com/courses/bio-201/lecture-4.mov",
  "kind": "adaptive_video",
  "options": {
    "format": "hls",
    "subtitles": [
      {
        "url": "https://lectern-app.com/captions/lecture-4-en.vtt",
        "label": "English",
        "language": "en",
        "default": true
      },
      {
        "url": "https://lectern-app.com/captions/lecture-4-es.vtt",
        "label": "Espa\u00f1ol",
        "language": "es"
      },
      {
        "url": "https://lectern-app.com/captions/lecture-4-fr.vtt",
        "label": "Fran\u00e7ais",
        "language": "fr"
      }
    ]
  }
}

Each track appears as a selectable option in the player’s caption menu.

VTT format requirements

RequirementDetail
FormatWebVTT (.vtt) only
EncodingUTF-8
TimestampsMust align with the source video timeline
HostedThe URL must be publicly accessible at task time

When to use subtitle tracks vs burned-in captions

ApproachUse case
Subtitle tracks (this guide)Multi-language, togglable, accessible
Burned-in (hardcoded)Social media clips, guaranteed visibility

Subtitle tracks keep the text separate from the video, so viewers can toggle them on or off and switch languages without re-encoding.