Captions & Subtitles
Add subtitles to videos — upload VTT/SRT files manually or auto-generate with AI. Captions appear in the Plyr player with styled rendering and language selection.
Two ways to add captions
- Upload — Upload .vtt or .srt files (SRT auto-converted to VTT)
- AI Generate — AI creates timed captions from video metadata + chapters
1. Upload Captions
API Endpoint
POST /api/mediakit/videos/:id/captions
Content-Type: multipart/form-data
Authorization: Bearer <jwt_token>
# Form fields:
file = subtitles.vtt # .vtt or .srt file (required)
language = English # Display name (default: "English")
language_code = en # ISO 639-1 code (default: "en")
is_default = true # Show by default in playercURL Example
# Upload English subtitles
curl -X POST https://mediakitapi.gritcms.com/api/mediakit/videos/8/captions \
-H "Authorization: Bearer $TOKEN" \
-F "file=@subtitles.vtt" \
-F "language=English" \
-F "language_code=en" \
-F "is_default=true"
# Upload Spanish subtitles
curl -X POST https://mediakitapi.gritcms.com/api/mediakit/videos/8/captions \
-H "Authorization: Bearer $TOKEN" \
-F "file=@subtitulos.srt" \
-F "language=Español" \
-F "language_code=es"JavaScript / Next.js
async function uploadCaptions(videoId: number, file: File, language: string, code: string) {
const formData = new FormData()
formData.append('file', file)
formData.append('language', language)
formData.append('language_code', code)
formData.append('is_default', 'true')
const res = await fetch(
`${MEDIAKIT_URL}/api/mediakit/videos/${videoId}/captions`,
{
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: formData,
}
)
const data = await res.json()
console.log('Caption created:', data.data.id)
}SRT Format (auto-converted)
Upload .srt files and they're automatically converted to WebVTT. Both formats are stored.
1
00:00:00,000 --> 00:00:03,500
Welcome to this tutorial
2
00:00:03,500 --> 00:00:07,000
Today we'll cover video streaming
3
00:00:07,000 --> 00:00:11,500
Let's start with the upload pipelineWebVTT Format (native)
WEBVTT
00:00:00.000 --> 00:00:03.500
Welcome to this tutorial
00:00:03.500 --> 00:00:07.000
Today we'll cover video streaming
00:00:07.000 --> 00:00:11.500
Let's start with the upload pipeline2. AI-Generated Captions
Generate captions automatically using AI. The model creates timed WebVTT content based on the video's title, description, duration, and chapter markers (if available).
POST /api/ai/videos/:id/analyse
Content-Type: application/json
Authorization: Bearer <jwt_token>
{ "type": "captions" }// From your backend or admin panel
const res = await fetch(
`${MEDIAKIT_URL}/api/ai/videos/${videoId}/analyse`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({ type: 'captions' }),
}
)
// Captions are generated, saved as VTT, and linked to the video
// They'll appear in the player automatically on next loadNote on AI captions
AI-generated captions are based on video metadata (title, description, chapters) — not actual speech transcription. For real speech-to-text from audio, integrate OpenAI Whisper or AssemblyAI and upload the resulting VTT file.
3. Player Integration
Captions are automatically loaded from the playback API and rendered in the Plyr player. No extra code needed — just upload captions and they appear.
import { PlyrPlayer } from '@mediakit-dev/react'
// Captions load automatically from the playback API
<PlyrPlayer videoId="8" captions={true} />
// The player shows:
// - CC button in controls bar
// - Settings → Captions → language selector
// - Styled subtitle overlay on the videoCaption Styling
Captions are styled with a modern, readable appearance:
- Dark semi-transparent background (
rgba(0,0,0,0.8)) - System sans-serif font, weight 500
- Max width 80%, auto-centered
- Responsive sizing — larger in fullscreen
- No text shadow (cleaner than default)
- 4px border radius on caption boxes
Override styles with CSS targeting .plyr__caption:
/* Yellow captions like Netflix */
.mk-plyr-wrapper .plyr__caption {
background: transparent !important;
color: #ffd700 !important;
text-shadow: 2px 2px 4px rgba(0,0,0,0.9) !important;
font-size: 1.2rem !important;
}
/* Larger font for accessibility */
.mk-plyr-wrapper .plyr__caption {
font-size: 1.3rem !important;
line-height: 1.6 !important;
}
/* Karaoke-style highlighting */
.mk-plyr-wrapper video::cue {
background: rgba(108,92,231,0.9);
color: #fff;
}4. API Reference
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/mediakit/videos/:id/captions | JWT | Upload VTT/SRT file |
| GET | /api/mediakit/videos/:id/captions | Public | List captions for a video |
| DELETE | /api/mediakit/captions/:id | JWT | Delete a caption |
| POST | /api/ai/videos/:id/analyse | JWT | AI generate (type=captions) |
5. Multiple Languages
// English (default)
await uploadCaptions(videoId, enFile, 'English', 'en')
// Spanish
await uploadCaptions(videoId, esFile, 'Español', 'es')
// French
await uploadCaptions(videoId, frFile, 'Français', 'fr')
// Japanese
await uploadCaptions(videoId, jaFile, '日本語', 'ja')
// In the player: Settings → Captions → select language
// The player automatically shows all available tracksPlayback API Response
{
"data": {
"id": 8,
"title": "Getting Started Tutorial",
"hls_url": "https://mediakitapi.gritcms.com/api/storage/videos/8/hls/index.m3u8",
"captions": [
{
"id": 1,
"language": "English",
"language_code": "en",
"vtt_url": "https://mediakitapi.gritcms.com/api/storage/videos/8/captions/en.vtt",
"is_default": true,
"ai_generated": false,
"source": "upload"
},
{
"id": 2,
"language": "Español",
"language_code": "es",
"vtt_url": "https://mediakitapi.gritcms.com/api/storage/videos/8/captions/es.vtt",
"is_default": false,
"ai_generated": false,
"source": "upload"
}
]
}
}