Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support srt subtitle format #743

Merged
merged 7 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 35 additions & 24 deletions docs/es-modules/subtitles-and-captions.html
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ <h4 class="mt-4 mb-2">Karaoke player</h4>
width="500"
></video>

<h4 class="mt-4 mb-2">Translated Transcript</h4>

<video
id="translated-transcript"
playsinline
controls
class="cld-video-player cld-fluid"
crossorigin="anonymous"
width="500"
></video>

<p class="mt-4">
<a href="https://cloudinary.com/documentation/cloudinary_video_player"
>Full documentation</a
Expand All @@ -117,31 +128,31 @@ <h4 class="mt-4 mb-2">Karaoke player</h4>
import 'cloudinary-video-player/playlist';

const player = videoPlayer('player', {
cloudName: 'demo'
cloudName: 'prod'
});

player.source('video-player/stubhub', {
textTracks: {
captions: {
label: 'English captions',
language: 'en',
default: true,
url: 'https://res.cloudinary.com/demo/raw/upload/v1636972013/video-player/vtt/Meetup_english.vtt'
},
subtitles: [
{
label: 'German subtitles',
language: 'de',
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970250/video-player/vtt/Meetup_german.vtt'
player.source(
'video/examples/big_buck_bunny_trailer_720p',
{
info: { title: 'SRT & VTT from URL' },
textTracks: {
options: {
theme: "videojs-default"
},
{
label: 'Russian subtitles',
language: 'ru',
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970275/video-player/vtt/Meetup_russian.vtt'
}
]
captions: {
label: 'VTT from URL',
default: true,
url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.vtt'
},
subtitles: [
{
label: 'SRT from URL',
url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.srt'
}
]
}
}
});
);

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

// Paced
const pacedPlayer = cloudinary.videoPlayer('paced', {
const pacedPlayer = videoPlayer('paced', {
cloudName: 'prod'
});

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

// Karaoke
const karaokePlayer = cloudinary.videoPlayer('karaoke', {
const karaokePlayer = videoPlayer('karaoke', {
cloudName: 'prod'
});

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

// Auto-translated transcript
const translatedTranscriptPlayer = cloudinary.videoPlayer('translated-transcript', {
const translatedTranscriptPlayer = videoPlayer('translated-transcript', {
cloudName: 'prod'
});

Expand Down
46 changes: 20 additions & 26 deletions docs/subtitles-and-captions.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,26 @@
window.addEventListener('load', function(){

var player = cloudinary.videoPlayer('player', {
cloud_name: 'demo'
cloud_name: 'prod'
});

player.source(
'video-player/stubhub',
'video/examples/big_buck_bunny_trailer_720p',
{
info: { title: 'SRT & VTT from URL' },
textTracks: {
options: {
theme: "videojs-default"
},
captions: {
label: 'English captions',
language: 'en',
label: 'VTT from URL',
default: true,
url: 'https://res.cloudinary.com/demo/raw/upload/v1636972013/video-player/vtt/Meetup_english.vtt'
url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.vtt'
},
subtitles: [
{
label: 'German subtitles',
language: 'de',
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970250/video-player/vtt/Meetup_german.vtt'
},
{
label: 'Russian subtitles',
language: 'ru',
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970275/video-player/vtt/Meetup_russian.vtt'
label: 'SRT from URL',
url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.srt'
}
]
}
Expand Down Expand Up @@ -353,29 +350,26 @@ <h3 class="mt-4">Example Code:</h3>

// Initialize players
var player = cloudinary.videoPlayer('player', {
cloud_name: 'demo'
cloud_name: 'prod'
});

player.source(
'video-player/stubhub',
'video/examples/big_buck_bunny_trailer_720p',
{
info: { title: 'SRT & VTT from URL' },
textTracks: {
options: {
theme: "videojs-default"
},
captions: {
label: 'English captions',
language: 'en',
label: 'VTT from URL',
default: true,
url: 'https://res.cloudinary.com/demo/raw/upload/v1636972013/video-player/vtt/Meetup_english.vtt'
url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.vtt'
},
subtitles: [
{
label: 'German subtitles',
language: 'de',
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970250/video-player/vtt/Meetup_german.vtt'
},
{
label: 'Russian subtitles',
language: 'ru',
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970275/video-player/vtt/Meetup_russian.vtt'
label: 'SRT from URL',
url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.srt'
}
]
}
Expand Down
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"cloudinary-video-analytics": "1.7.1",
"cloudinary-video-player-profiles": "1.1.0",
"lodash": "^4.17.21",
"srt-parser-2": "^1.2.3",
"uuid": "^10.0.0",
"video.js": "^8.17.1",
"videojs-contrib-ads": "^7.5.2",
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import chapters from './chapters';
import imaPlugin from './ima';
import playlist from './playlist';
import shoppable from './shoppable-plugin';
import srtTextTracks from './srt-text-tracks';
import styledTextTracks from './styled-text-tracks';
import interactionAreas from './interaction-areas';

Expand All @@ -40,6 +41,7 @@ const plugins = {
imaPlugin,
playlist,
shoppable,
srtTextTracks,
styledTextTracks,
interactionAreas
};
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/paced-transcript/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function pacedTranscript(config) {
transcriptResponse = await fallbackFetch(`${basePath}.transcript`);
}
}
if (!transcriptResponse.ok) return;
if (!transcriptResponse?.ok) return;
const transcriptData = await transcriptResponse.json();
const captions = parseTranscript(transcriptData);

Expand Down
10 changes: 10 additions & 0 deletions src/plugins/srt-text-tracks/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import srtTextTracks from './srt-text-tracks';

export default async function srtTextTracksPlugin(config) {
const player = this;
try {
player.ready(() => srtTextTracks(config, player));
} catch (error) {
console.error('Failed to load plugin:', error);
}
}
60 changes: 60 additions & 0 deletions src/plugins/srt-text-tracks/srt-text-tracks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import srtParser2 from 'srt-parser-2';

function srtTextTracks(config, player) {
// Load the SRT file and convert it to WebVTT
const initSRT = async () => {
let srtResponse;
if (config.src) {
try {
srtResponse = await fetch(config.src);
if (!srtResponse.ok) {
throw new Error(`Failed fetching from ${config.src} with status code ${srtResponse.status}`);
}
} catch (error) {
console.error(error);
}
}
if (!srtResponse.ok) return;

const srtData = await srtResponse.text();
const webvttCues = srt2webvtt(srtData); // Get the array of cues

const srtTrack = player.addRemoteTextTrack({
kind: config.kind || 'subtitles',
label: config.label || 'Subtitles',
srclang: config.srclang,
default: config.default,
mode: config.default ? 'showing' : 'disabled'
});

// required for Safari to display the captions
// https://github.com/videojs/video.js/issues/8519
await new Promise(resolve => setTimeout(resolve, 100));

// Add the WebVTT data to the track
webvttCues.forEach(cue => {
if (cue) {
srtTrack.track.addCue(new VTTCue(cue.startTime, cue.endTime, cue.text));
}
});
};

player.one('loadedmetadata', () => {
initSRT();
});
}

// SRT parser
const srt2webvtt = data => {
const SRTParser = new srtParser2();

const cues = SRTParser.fromSrt(data);

return cues.map(cue => ({
startTime: cue.startSeconds,
endTime: cue.endSeconds,
text: cue.text
}));
};

export default srtTextTracks;
2 changes: 2 additions & 0 deletions src/utils/cloudinary.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ const addTextTracks = (tracks, videojs) => {
videojs.addRemoteTextTrack(track, true);
}
});
} else if (track.src && track.src.endsWith('.srt')) {
videojs.srtTextTracks(track);
} else if (videojs.pacedTranscript && (!track.src || track.src.endsWith('.transcript'))) {
videojs.pacedTranscript(track);
}
Expand Down
7 changes: 5 additions & 2 deletions src/utils/get-analytics-player-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ const getTranscriptOptions = (textTracks = {}) => {
const tracksArr = [textTracks.captions, ...textTracks.subtitles];
return {
textTracks: hasConfig(textTracks),
textTracksLength: tracksArr.length,
textTracksOptions: hasConfig(textTracks.options) || Object.keys(textTracks.options).join(','),
pacedTextTracks: hasConfig(textTracks) && JSON.stringify(textTracks || {}).includes('"maxWords":') || null,
wordHighlight: hasConfig(textTracks) && JSON.stringify(textTracks || {}).includes('"wordHighlight":') || null,
transcriptLanguages: tracksArr.filter((track) => !track.url).map((track) => track.language || '').join(',') || null,
transcriptAutoLoaded: tracksArr.some((track) => !track.url) || null,
transcriptFromURl: tracksArr.some((track) => track.url?.endsWith('.transcript')) || null,
transcriptLanguages: tracksArr.filter((track) => !track.url).map((track) => track.language || '').join(',') || null,
vttFromUrl: tracksArr.some((track) => track.url?.endsWith('.vtt')) || null
vttFromUrl: tracksArr.some((track) => track.url?.endsWith('.vtt')) || null,
srtFromUrl: tracksArr.some((track) => track.url?.endsWith('.srt')) || null
};
};

Expand Down
Loading