Skip to content

Commit

Permalink
feat: kareoke style subtitles (#563)
Browse files Browse the repository at this point in the history
* feat: karaoke style subtitles

* fix: types exports

* feat: kareoke style subtitles

* feat: kareoke style subtitles

* feat: kareoke style subtitles

* chore: add example page
  • Loading branch information
tsi authored Feb 26, 2024
1 parent bbdb28f commit 99c1830
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 67 deletions.
173 changes: 131 additions & 42 deletions docs/subtitles-and-captions.html
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,42 @@
});
});

// Karaoke
const karaokePlayer = cloudinary.videoPlayer('karaoke', {
cloudName: 'demo',
autoplay: true,
muted: true
});

karaokePlayer.source('lincoln', {
textTracks: {
options: {
fontFace: 'Lobster',
fontSize: '200%',
gravity: 'top',
wordHighlightStyle: {
color: 'white',
'text-shadow': `2px 2px 0px violet,
4px 4px 0px indigo,
6px 6px 0px blue,
8px 8px 0px green,
10px 10px 0px yellow,
12px 12px 0px orange,
14px 14px 0px red`
}
},
captions: {
label: 'KARAOKE',
language: 'en',
wordHighlight: true,
maxWords: 5,
timeOffset: -0.2,
default: true
}
}
});
}, false);
</script>

</head>
<body>
<div class="container p-4 col-12 col-md-9">
Expand All @@ -198,8 +231,8 @@ <h3 class="mb-4">Subtitles & Captions</h3>
autoplay
class="cld-video-player cld-fluid"
crossorigin="anonymous"
width="500">
</video>
width="500"
></video>

<h4 class="mt-4 mb-2">Playlist Subtitles (switch per source)</h4>

Expand All @@ -210,8 +243,8 @@ <h4 class="mt-4 mb-2">Playlist Subtitles (switch per source)</h4>
muted
class="cld-video-player cld-fluid"
crossorigin="anonymous"
width="500">
</video>
width="500"
></video>

<h4 class="mt-4 mb-2">Paced & Styled Captions</h4>

Expand Down Expand Up @@ -260,8 +293,20 @@ <h4 class="mt-4 mb-2">Paced & Styled Captions</h4>
muted
class="cld-video-player cld-fluid"
crossorigin="anonymous"
width="500">
</video>
width="500"
></video>

<h4 class="mt-4 mb-2">Karaoke player</h4>

<video
id="karaoke"
playsinline
controls
muted
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 Down Expand Up @@ -298,6 +343,16 @@ <h3 class="mt-4">Example Code:</h3>
crossorigin="anonymous"
width="500">
&lt;/video&gt;

&lt;video
id="karaoke"
controls
muted
autoplay
class="cld-video-player"
crossorigin="anonymous"
width="500">
&lt;/video&gt;
</code>
<code class="language-javascript">

Expand Down Expand Up @@ -393,43 +448,77 @@ <h3 class="mt-4">Example Code:</h3>
muted: true
});

const textTracks = {
options: {
// theme: "", // one of 'default', 'videojs-default', 'yellow-outlined', 'player-colors' & '3d'
// fontFace: "", // any Google font
// fontSize: "", // any CSS value
// gravity: "", // i.e. 'top-left', 'center' etc
// box: { // Object of x/y/width/height
// x: "0%",
// y: "0%",
// width: "100%",
// height: "100%"
// },
// style: { // Styles key-value object
// color: "hotpink"
// }
},
captions: {
label: 'English captions',
language: 'en',
maxWords: 4,
default: true,
},
subtitles: [
{
label: 'German subtitles',
language: 'de',
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970250/video-player/vtt/Meetup_german.vtt'
pacedPlayer.source('lincoln', {
textTracks: {
options: {
// theme: "", // one of 'default', 'videojs-default', 'yellow-outlined', 'player-colors' & '3d'
// fontFace: "", // any Google font
// fontSize: "", // any CSS value
// gravity: "", // i.e. 'top-left', 'center' etc
// box: { // Object of x/y/width/height
// x: "0%",
// y: "0%",
// width: "100%",
// height: "100%"
// },
// style: { // Styles key-value object
// color: "hotpink"
// }
},
{
label: 'Russian subtitles',
language: 'ru',
url: 'https://res.cloudinary.com/demo/raw/upload/v1636970275/video-player/vtt/Meetup_russian.vtt'
captions: {
label: 'English Paced',
language: 'en',
maxWords: 4,
default: true
},
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'
}
]
}
});

// Karaoke
const karaokePlayer = cloudinary.videoPlayer('karaoke', {
cloudName: 'demo',
autoplay: true,
muted: true
});

karaokePlayer.source('lincoln', {
textTracks: {
options: {
fontFace: 'Lobster',
fontSize: '200%',
gravity: 'top',
wordHighlightStyle: {
color: 'white',
'text-shadow': `2px 2px 0px violet,
4px 4px 0px indigo,
6px 6px 0px blue,
8px 8px 0px green,
10px 10px 0px yellow,
12px 12px 0px orange,
14px 14px 0px red`
}
},
captions: {
label: 'KARAOKE',
language: 'en',
wordHighlight: true,
maxWords: 5,
timeOffset: -0.2,
default: true
}
]
}
pacedPlayer.source('lincoln', {
textTracks
}
});

</code>
Expand Down
6 changes: 5 additions & 1 deletion src/assets/styles/components/text-tracks.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
> div {
margin: 3% !important;
}
// Word highlight
&.cld-paced-text-tracks b {
color: var(--color-accent);
}
}
.vjs-text-track-cue {
top: auto !important;
Expand All @@ -27,7 +31,7 @@
.vjs-text-track-display:not(.cld-styled-text-tracks-theme-videojs-default) {
.vjs-text-track-cue {
font-family: inherit !important;
& > div {
> div {
font-weight: 700;
background-color: transparent !important;
text-shadow: 0 0 0.2em rgba(0, 0, 0, 0.8);
Expand Down
64 changes: 49 additions & 15 deletions src/plugins/paced-transcript/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ function pacedTranscript(config) {
source.publicId(),
extendCloudinaryConfig(player.cloudinary.cloudinaryConfig(), { resource_type: 'raw' }),
) + '.transcript',
maxWords: config.maxWords || 5 // Number of words per caption
maxWords: config.maxWords,
wordHighlight: config.wordHighlight,
timeOffset: config.timeOffset || 0
};

const classNames = player.textTrackDisplay.el().classList;
classNames.add('cld-paced-text-tracks');

// Load the transcription file
const initTranscript = async () => {
try {
Expand Down Expand Up @@ -46,26 +51,55 @@ function pacedTranscript(config) {

// Generate captions from the transcription data
const parseTranscript = transcriptionData => {
const maxWords = options.maxWords;
const captions = [];

const addCaption = ({ startTime, endTime, text }) => {
captions.push({
startTime: startTime + options.timeOffset,
endTime: endTime + options.timeOffset,
text
});
};

transcriptionData.forEach(segment => {
const words = segment.words;
const maxWords = options.maxWords || words.length;

for (let i = 0; i < words.length; i += maxWords) {
const startTime = words[i].start_time;
const endTime = words[Math.min(i + maxWords - 1, words.length - 1)].end_time;

const captionText = words
.slice(i, i + maxWords)
.map(word => word.word)
.join(' ');

captions.push({
startTime: startTime,
endTime: endTime,
text: captionText
});
if (options.wordHighlight) {
// Create a caption for every word, in which the current word is highlighted
words.slice(i, Math.min(i + maxWords, words.length)).forEach((word, idx) => {
addCaption({
startTime: word.start_time,
endTime: word.end_time,
text: words
.slice(i, i + maxWords)
.map(w => (w === word ? `<b>${w.word}</b>` : w.word))
.join(' ')
});

// if we haven't reached the end of the words array, and there's a gap between the current word end_time and the next word start_time, add a non-highlighted caption to fill the gap
if (words[idx + 1] && word.end_time < words[idx + 1].start_time) {
addCaption({
startTime: word.end_time,
endTime: words[idx + 1].start_time,
text: words
.slice(i, i + maxWords)
.map(word => word.word)
.join(' ')
});
}
});
} else {
captions.push({
startTime: words[i].start_time,
endTime: words[Math.min(i + maxWords - 1, words.length - 1)].end_time,
text: words
.slice(i, i + maxWords)
.map(word => word.word)
.join(' ')
});
}
}
});

Expand Down
11 changes: 10 additions & 1 deletion src/plugins/styled-text-tracks/styled-text-tracks.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const styledTextTracks = (config, player) => {
fontSize: config.fontSize,
gravity: config.gravity || 'bottom',
box: config.box,
style: config.style
style: config.style,
wordHighlightStyle: config.wordHighlightStyle
};

// Class Names - Theme/Gravity
Expand Down Expand Up @@ -75,6 +76,14 @@ const styledTextTracks = (config, player) => {
'.vjs-text-track-display.cld-styled-text-tracks .vjs-text-track-cue > div'
);
}

// Custom styles
if (options.wordHighlightStyle) {
applyImportantStyle(
options.wordHighlightStyle,
'.vjs-text-track-display.cld-styled-text-tracks .vjs-text-track-cue b'
);
}
};

export default styledTextTracks;
6 changes: 3 additions & 3 deletions src/plugins/styled-text-tracks/styled-text-tracks.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
.vjs-text-track-display {

&.cld-styled-text-tracks-theme-yellow-outlined {
.vjs-text-track-cue {
div.vjs-text-track-cue {
& > div {
color: #FEF94A !important;
text-shadow:
Expand All @@ -44,7 +44,7 @@
}

&.cld-styled-text-tracks-theme-3d {
.vjs-text-track-cue {
div.vjs-text-track-cue {
& > div {
$base-size: 0.03em;
$base-color: #ff76ad;
Expand All @@ -59,7 +59,7 @@
}

&.cld-styled-text-tracks-theme-player-colors {
.vjs-text-track-cue {
div.vjs-text-track-cue {
& > div {
color: var(--color-text) !important;
background-color: var(--color-accent) !important;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/cloudinary.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ const isKeyInTransformation = (transformation, key) => {

const addTextTracks = (tracks, videojs) => {
tracks.forEach(track => {
if (track.maxWords && videojs.pacedTranscript) {
if ((track.maxWords || track.wordHighlight) && videojs.pacedTranscript) {
videojs.pacedTranscript(track);
} else if (track.src) {
fetch(track.src, GET_ERROR_DEFAULT_REQUEST).then(r => {
Expand Down
Loading

0 comments on commit 99c1830

Please sign in to comment.