Skip to content

Commit 3461a76

Browse files
authored
feat: support srt subtitle format (#743)
* feat: support srt subtitle format * chore: add example and usage monitoring * chore: esm examples * chore: esm example * chore: examples * feat: support srt subtitle format
1 parent 7787c36 commit 3461a76

File tree

10 files changed

+149
-53
lines changed

10 files changed

+149
-53
lines changed

docs/es-modules/subtitles-and-captions.html

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ <h4 class="mt-4 mb-2">Karaoke player</h4>
104104
width="500"
105105
></video>
106106

107+
<h4 class="mt-4 mb-2">Translated Transcript</h4>
108+
109+
<video
110+
id="translated-transcript"
111+
playsinline
112+
controls
113+
class="cld-video-player cld-fluid"
114+
crossorigin="anonymous"
115+
width="500"
116+
></video>
117+
107118
<p class="mt-4">
108119
<a href="https://cloudinary.com/documentation/cloudinary_video_player"
109120
>Full documentation</a
@@ -117,31 +128,31 @@ <h4 class="mt-4 mb-2">Karaoke player</h4>
117128
import 'cloudinary-video-player/playlist';
118129

119130
const player = videoPlayer('player', {
120-
cloudName: 'demo'
131+
cloudName: 'prod'
121132
});
122133

123-
player.source('video-player/stubhub', {
124-
textTracks: {
125-
captions: {
126-
label: 'English captions',
127-
language: 'en',
128-
default: true,
129-
url: 'https://res.cloudinary.com/demo/raw/upload/v1636972013/video-player/vtt/Meetup_english.vtt'
130-
},
131-
subtitles: [
132-
{
133-
label: 'German subtitles',
134-
language: 'de',
135-
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970250/video-player/vtt/Meetup_german.vtt'
134+
player.source(
135+
'video/examples/big_buck_bunny_trailer_720p',
136+
{
137+
info: { title: 'SRT & VTT from URL' },
138+
textTracks: {
139+
options: {
140+
theme: "videojs-default"
136141
},
137-
{
138-
label: 'Russian subtitles',
139-
language: 'ru',
140-
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970275/video-player/vtt/Meetup_russian.vtt'
141-
}
142-
]
142+
captions: {
143+
label: 'VTT from URL',
144+
default: true,
145+
url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.vtt'
146+
},
147+
subtitles: [
148+
{
149+
label: 'SRT from URL',
150+
url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.srt'
151+
}
152+
]
153+
}
143154
}
144-
});
155+
);
145156

146157
// Playlist
147158
const playlist = videoPlayer('playlist', {
@@ -202,7 +213,7 @@ <h4 class="mt-4 mb-2">Karaoke player</h4>
202213
playlist.playlist(playlistSources, playlistOptions);
203214

204215
// Paced
205-
const pacedPlayer = cloudinary.videoPlayer('paced', {
216+
const pacedPlayer = videoPlayer('paced', {
206217
cloudName: 'prod'
207218
});
208219

@@ -250,7 +261,7 @@ <h4 class="mt-4 mb-2">Karaoke player</h4>
250261
});
251262

252263
// Karaoke
253-
const karaokePlayer = cloudinary.videoPlayer('karaoke', {
264+
const karaokePlayer = videoPlayer('karaoke', {
254265
cloudName: 'prod'
255266
});
256267

@@ -282,7 +293,7 @@ <h4 class="mt-4 mb-2">Karaoke player</h4>
282293
});
283294

284295
// Auto-translated transcript
285-
const translatedTranscriptPlayer = cloudinary.videoPlayer('translated-transcript', {
296+
const translatedTranscriptPlayer = videoPlayer('translated-transcript', {
286297
cloudName: 'prod'
287298
});
288299

docs/subtitles-and-captions.html

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,26 @@
2828
window.addEventListener('load', function(){
2929

3030
var player = cloudinary.videoPlayer('player', {
31-
cloud_name: 'demo'
31+
cloud_name: 'prod'
3232
});
3333

3434
player.source(
35-
'video-player/stubhub',
35+
'video/examples/big_buck_bunny_trailer_720p',
3636
{
37+
info: { title: 'SRT & VTT from URL' },
3738
textTracks: {
39+
options: {
40+
theme: "videojs-default"
41+
},
3842
captions: {
39-
label: 'English captions',
40-
language: 'en',
43+
label: 'VTT from URL',
4144
default: true,
42-
url: 'https://res.cloudinary.com/demo/raw/upload/v1636972013/video-player/vtt/Meetup_english.vtt'
45+
url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.vtt'
4346
},
4447
subtitles: [
4548
{
46-
label: 'German subtitles',
47-
language: 'de',
48-
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970250/video-player/vtt/Meetup_german.vtt'
49-
},
50-
{
51-
label: 'Russian subtitles',
52-
language: 'ru',
53-
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970275/video-player/vtt/Meetup_russian.vtt'
49+
label: 'SRT from URL',
50+
url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.srt'
5451
}
5552
]
5653
}
@@ -353,29 +350,26 @@ <h3 class="mt-4">Example Code:</h3>
353350

354351
// Initialize players
355352
var player = cloudinary.videoPlayer('player', {
356-
cloud_name: 'demo'
353+
cloud_name: 'prod'
357354
});
358355

359356
player.source(
360-
'video-player/stubhub',
357+
'video/examples/big_buck_bunny_trailer_720p',
361358
{
359+
info: { title: 'SRT & VTT from URL' },
362360
textTracks: {
361+
options: {
362+
theme: "videojs-default"
363+
},
363364
captions: {
364-
label: 'English captions',
365-
language: 'en',
365+
label: 'VTT from URL',
366366
default: true,
367-
url: 'https://res.cloudinary.com/demo/raw/upload/v1636972013/video-player/vtt/Meetup_english.vtt'
367+
url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.vtt'
368368
},
369369
subtitles: [
370370
{
371-
label: 'German subtitles',
372-
language: 'de',
373-
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970250/video-player/vtt/Meetup_german.vtt'
374-
},
375-
{
376-
label: 'Russian subtitles',
377-
language: 'ru',
378-
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970275/video-player/vtt/Meetup_russian.vtt'
371+
label: 'SRT from URL',
372+
url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.srt'
379373
}
380374
]
381375
}

package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
"cloudinary-video-analytics": "1.7.1",
8383
"cloudinary-video-player-profiles": "1.1.0",
8484
"lodash": "^4.17.21",
85+
"srt-parser-2": "^1.2.3",
8586
"uuid": "^10.0.0",
8687
"video.js": "^8.17.1",
8788
"videojs-contrib-ads": "^7.5.2",

src/plugins/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import chapters from './chapters';
2020
import imaPlugin from './ima';
2121
import playlist from './playlist';
2222
import shoppable from './shoppable-plugin';
23+
import srtTextTracks from './srt-text-tracks';
2324
import styledTextTracks from './styled-text-tracks';
2425
import interactionAreas from './interaction-areas';
2526

@@ -40,6 +41,7 @@ const plugins = {
4041
imaPlugin,
4142
playlist,
4243
shoppable,
44+
srtTextTracks,
4345
styledTextTracks,
4446
interactionAreas
4547
};

src/plugins/paced-transcript/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ function pacedTranscript(config) {
5252
transcriptResponse = await fallbackFetch(`${basePath}.transcript`);
5353
}
5454
}
55-
if (!transcriptResponse.ok) return;
55+
if (!transcriptResponse?.ok) return;
5656
const transcriptData = await transcriptResponse.json();
5757
const captions = parseTranscript(transcriptData);
5858

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import srtTextTracks from './srt-text-tracks';
2+
3+
export default async function srtTextTracksPlugin(config) {
4+
const player = this;
5+
try {
6+
player.ready(() => srtTextTracks(config, player));
7+
} catch (error) {
8+
console.error('Failed to load plugin:', error);
9+
}
10+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import srtParser2 from 'srt-parser-2';
2+
3+
function srtTextTracks(config, player) {
4+
// Load the SRT file and convert it to WebVTT
5+
const initSRT = async () => {
6+
let srtResponse;
7+
if (config.src) {
8+
try {
9+
srtResponse = await fetch(config.src);
10+
if (!srtResponse.ok) {
11+
throw new Error(`Failed fetching from ${config.src} with status code ${srtResponse.status}`);
12+
}
13+
} catch (error) {
14+
console.error(error);
15+
}
16+
}
17+
if (!srtResponse.ok) return;
18+
19+
const srtData = await srtResponse.text();
20+
const webvttCues = srt2webvtt(srtData); // Get the array of cues
21+
22+
const srtTrack = player.addRemoteTextTrack({
23+
kind: config.kind || 'subtitles',
24+
label: config.label || 'Subtitles',
25+
srclang: config.srclang,
26+
default: config.default,
27+
mode: config.default ? 'showing' : 'disabled'
28+
});
29+
30+
// required for Safari to display the captions
31+
// https://github.com/videojs/video.js/issues/8519
32+
await new Promise(resolve => setTimeout(resolve, 100));
33+
34+
// Add the WebVTT data to the track
35+
webvttCues.forEach(cue => {
36+
if (cue) {
37+
srtTrack.track.addCue(new VTTCue(cue.startTime, cue.endTime, cue.text));
38+
}
39+
});
40+
};
41+
42+
player.one('loadedmetadata', () => {
43+
initSRT();
44+
});
45+
}
46+
47+
// SRT parser
48+
const srt2webvtt = data => {
49+
const SRTParser = new srtParser2();
50+
51+
const cues = SRTParser.fromSrt(data);
52+
53+
return cues.map(cue => ({
54+
startTime: cue.startSeconds,
55+
endTime: cue.endSeconds,
56+
text: cue.text
57+
}));
58+
};
59+
60+
export default srtTextTracks;

src/utils/cloudinary.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ const addTextTracks = (tracks, videojs) => {
107107
videojs.addRemoteTextTrack(track, true);
108108
}
109109
});
110+
} else if (track.src && track.src.endsWith('.srt')) {
111+
videojs.srtTextTracks(track);
110112
} else if (videojs.pacedTranscript && (!track.src || track.src.endsWith('.transcript'))) {
111113
videojs.pacedTranscript(track);
112114
}

src/utils/get-analytics-player-options.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@ const getTranscriptOptions = (textTracks = {}) => {
2323
const tracksArr = [textTracks.captions, ...textTracks.subtitles];
2424
return {
2525
textTracks: hasConfig(textTracks),
26+
textTracksLength: tracksArr.length,
27+
textTracksOptions: hasConfig(textTracks.options) || Object.keys(textTracks.options).join(','),
2628
pacedTextTracks: hasConfig(textTracks) && JSON.stringify(textTracks || {}).includes('"maxWords":') || null,
2729
wordHighlight: hasConfig(textTracks) && JSON.stringify(textTracks || {}).includes('"wordHighlight":') || null,
30+
transcriptLanguages: tracksArr.filter((track) => !track.url).map((track) => track.language || '').join(',') || null,
2831
transcriptAutoLoaded: tracksArr.some((track) => !track.url) || null,
2932
transcriptFromURl: tracksArr.some((track) => track.url?.endsWith('.transcript')) || null,
30-
transcriptLanguages: tracksArr.filter((track) => !track.url).map((track) => track.language || '').join(',') || null,
31-
vttFromUrl: tracksArr.some((track) => track.url?.endsWith('.vtt')) || null
33+
vttFromUrl: tracksArr.some((track) => track.url?.endsWith('.vtt')) || null,
34+
srtFromUrl: tracksArr.some((track) => track.url?.endsWith('.srt')) || null
3235
};
3336
};
3437

0 commit comments

Comments
 (0)