Skip to content

Commit

Permalink
feat(webvtt): webvtt colors output (shaka-project#4954)
Browse files Browse the repository at this point in the history
Adds color support for SimpleTextDisplayer and WebVttGenerator (only one
place to fix both now thanks to shaka-project#4941).

It's limited to the [8 colors
classes](https://w3c.github.io/webvtt/#default-text-color) supported by
the WebVTT specification, and also works with their 3 or 6-digit hex
variants (if the stream has TTML subtitles).

It does not support rgb, rgba or any colors other than these 8.

Fixes shaka-project#4545

---------

Co-authored-by: Alvaro Velad Galvan <ladvan91@hotmail.com>
  • Loading branch information
friday and avelad committed Feb 3, 2023
1 parent de6abde commit ed7a736
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 54 deletions.
32 changes: 16 additions & 16 deletions lib/text/cue.js
Expand Up @@ -415,14 +415,14 @@ shaka.text.Cue.lineAlign = {
* @export
*/
shaka.text.Cue.defaultTextColor = {
'white': '#FFF',
'lime': '#0F0',
'cyan': '#0FF',
'red': '#F00',
'yellow': '#FF0',
'magenta': '#F0F',
'blue': '#00F',
'black': '#000',
'white': 'white',
'lime': 'lime',
'cyan': 'cyan',
'red': 'red',
'yellow': 'yellow',
'magenta': 'magenta',
'blue': 'blue',
'black': 'black',
};


Expand All @@ -433,14 +433,14 @@ shaka.text.Cue.defaultTextColor = {
* @export
*/
shaka.text.Cue.defaultTextBackgroundColor = {
'bg_white': '#FFF',
'bg_lime': '#0F0',
'bg_cyan': '#0FF',
'bg_red': '#F00',
'bg_yellow': '#FF0',
'bg_magenta': '#F0F',
'bg_blue': '#00F',
'bg_black': '#000',
'bg_white': 'white',
'bg_lime': 'lime',
'bg_cyan': 'cyan',
'bg_red': 'red',
'bg_yellow': 'yellow',
'bg_magenta': 'magenta',
'bg_blue': 'blue',
'bg_black': 'black',
};


Expand Down
91 changes: 71 additions & 20 deletions lib/text/text_utils.js
Expand Up @@ -19,40 +19,91 @@ shaka.text.Utils = class {
* @private
*/
static flattenPayload_(cue) {
// Handle styles (currently bold/italics/underline).
// TODO: add support for color rendering.
if (cue.lineBreak) {
// This is a vertical lineBreak, so insert a newline.
return '\n';
}
if (cue.nestedCues.length) {
return cue.nestedCues.map(shaka.text.Utils.flattenPayload_).join('');
}

// Handle bold, italics and underline
const openStyleTags = [];
const bold = cue.fontWeight >= shaka.text.Cue.fontWeight.BOLD;
const italics = cue.fontStyle == shaka.text.Cue.fontStyle.ITALIC;
const underline = cue.textDecoration.includes(
shaka.text.Cue.textDecoration.UNDERLINE);
if (bold) {
openStyleTags.push('b');
openStyleTags.push(['b']);
}
if (italics) {
openStyleTags.push('i');
openStyleTags.push(['i']);
}
if (underline) {
openStyleTags.push('u');
openStyleTags.push(['u']);
}
// Handle color classes, if the value consists of letters
let classes = '';
const color = shaka.text.Utils.getColorName_(cue.color);
if (color) {
classes += `.${color}`;
}
const bgColor = shaka.text.Utils.getColorName_(cue.backgroundColor);
if (bgColor) {
classes += `.bg_${bgColor}`;
}
if (classes) {
openStyleTags.push(['c', classes]);
}

// Prefix opens tags, suffix closes tags in reverse order of opening.
const prefixStyleTags = openStyleTags.reduce((acc, tag) => {
return `${acc}<${tag}>`;
}, '');
const suffixStyleTags = openStyleTags.reduceRight((acc, tag) => {
return `${acc}</${tag}>`;
}, '');
return openStyleTags.reduceRight((acc, [tag, classes = '']) => {
return `<${tag}${classes}>${acc}</${tag}>`;
}, cue.payload);
}

if (cue.lineBreak) {
// This is a vertical lineBreak, so insert a newline.
return '\n';
} else if (cue.nestedCues.length) {
return cue.nestedCues.map(shaka.text.Utils.flattenPayload_).join('');
} else {
// This is a real cue.
return prefixStyleTags + cue.payload + suffixStyleTags;
/**
* Gets the color name from a color string.
*
* @param {string} string
* @return {?string}
* @private
*/
static getColorName_(string) {
switch (string.toLowerCase()) {
case 'white':
case '#fff':
case '#ffffff':
return 'white';
case 'lime':
case '#0f0':
case '#00ff00':
return 'lime';
case 'cyan':
case '#0ff':
case '#00ffff':
return 'cyan';
case 'red':
case '#f00':
case '#ff0000':
return 'red';
case 'yellow':
case '#ff0':
case '#ffff00':
return 'yellow';
case 'magenta':
case '#f0f':
return 'magenta';
case 'blue':
case '#00f':
case '#0000ff':
return 'blue';
case 'black':
case '#000':
case '#000000':
return 'black';
}
// No color name
return null;
}

/**
Expand Down
30 changes: 15 additions & 15 deletions test/text/vtt_text_parser_unit.js
Expand Up @@ -910,7 +910,7 @@ describe('VttTextParser', () => {
startTime: 20,
endTime: 40,
payload: 'Test',
color: '#FF0',
color: 'yellow',
},
],
},
Expand All @@ -922,8 +922,8 @@ describe('VttTextParser', () => {
startTime: 40,
endTime: 50,
payload: 'Test2',
color: '#0FF',
backgroundColor: '#00F',
color: 'cyan',
backgroundColor: 'blue',
},
],
},
Expand All @@ -935,8 +935,8 @@ describe('VttTextParser', () => {
startTime: 50,
endTime: 60,
payload: 'Test 3',
color: '#F0F',
backgroundColor: '#000',
color: 'magenta',
backgroundColor: 'black',
},
],
},
Expand All @@ -954,7 +954,7 @@ describe('VttTextParser', () => {
startTime: 60,
endTime: 70,
payload: 'Test4.1',
color: '#FF0',
color: 'yellow',
},
{
startTime: 60,
Expand All @@ -971,7 +971,7 @@ describe('VttTextParser', () => {
startTime: 60,
endTime: 70,
payload: 'Test4.2',
color: '#00F',
color: 'blue',
},
],
},
Expand All @@ -984,13 +984,13 @@ describe('VttTextParser', () => {
startTime: 70,
endTime: 80,
payload: 'Test5.1',
color: '#F00',
color: 'red',
},
{
startTime: 70,
endTime: 80,
payload: 'Test5.2',
color: '#0F0',
color: 'lime',
},
],
},
Expand All @@ -1015,7 +1015,7 @@ describe('VttTextParser', () => {
startTime: 100,
endTime: 110,
payload: 'forward slash 1/2 in text',
color: '#0F0',
color: 'lime',
},
],
},
Expand Down Expand Up @@ -1132,16 +1132,16 @@ describe('VttTextParser', () => {
startTime: 10,
endTime: 20,
payload: 'Example 1',
color: '#F00',
backgroundColor: '#FF0',
color: 'red',
backgroundColor: 'yellow',
fontSize: '10px',
},
],
},
],
'WEBVTT\n\n' +
'STYLE\n' +
'::cue(.bg_blue) { font-size: 10px; background-color: #FF0 }\n\n' +
'::cue(.bg_blue) { font-size: 10px; background-color: yellow }\n\n' +
'00:00:10.000 --> 00:00:20.000\n' +
'<c.red.bg_blue>Example 1</c>\n\n',
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
Expand All @@ -1159,7 +1159,7 @@ describe('VttTextParser', () => {
startTime: 10,
endTime: 20,
payload: '1',
color: '#F0F',
color: 'magenta',
},
{
startTime: 10,
Expand All @@ -1171,7 +1171,7 @@ describe('VttTextParser', () => {
startTime: 10,
endTime: 20,
payload: '2',
color: '#F0F',
color: 'magenta',
fontStyle: Cue.fontStyle.ITALIC,
},
],
Expand Down
12 changes: 9 additions & 3 deletions test/text/web_vtt_generator_unit.js
Expand Up @@ -13,11 +13,17 @@ describe('WebVttGenerator', () => {
const shakaCue1 = new shaka.text.Cue(20, 40, 'Test');
shakaCue1.textAlign = shaka.text.Cue.textAlign.LEFT;
shakaCue1.writingMode = shaka.text.Cue.writingMode.VERTICAL_LEFT_TO_RIGHT;
shakaCue1.color = 'red';
shakaCue1.backgroundColor = '#f0f';
const shakaCue2 = new shaka.text.Cue(40, 50, 'Test2');
shakaCue2.textAlign = shaka.text.Cue.textAlign.RIGHT;
shakaCue2.writingMode = shaka.text.Cue.writingMode.VERTICAL_RIGHT_TO_LEFT;
shakaCue2.color = '#0f0';
shakaCue2.backgroundColor = 'lime';
const shakaCue3 = new shaka.text.Cue(50, 51, 'Test3');
shakaCue3.textAlign = shaka.text.Cue.textAlign.CENTER;
shakaCue3.color = '#ffff00';
shakaCue3.backgroundColor = '#00ffff';
const shakaCue4 = new shaka.text.Cue(52, 53, 'Test4');
shakaCue4.textAlign = shaka.text.Cue.textAlign.START;
const shakaCue5 = new shaka.text.Cue(53, 54, 'Test5');
Expand All @@ -36,11 +42,11 @@ describe('WebVttGenerator', () => {
adCuePoints,
'WEBVTT\n\n' +
'00:00:20.000 --> 00:00:40.000 align:left vertical:lr\n' +
'Test\n\n' +
'<c.red.bg_magenta>Test</c>\n\n' +
'00:00:40.000 --> 00:00:50.000 align:right vertical:rl\n' +
'Test2\n\n' +
'<c.lime.bg_lime>Test2</c>\n\n' +
'00:00:50.000 --> 00:00:51.000 align:middle\n' +
'Test3\n\n' +
'<c.yellow.bg_cyan>Test3</c>\n\n' +
'00:00:52.000 --> 00:00:53.000 align:start\n' +
'Test4\n\n' +
'00:00:53.000 --> 00:00:54.000 align:end\n' +
Expand Down

0 comments on commit ed7a736

Please sign in to comment.