From ed7a736ca22bb768672135ad0d468c00be4c5dac Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Fri, 3 Feb 2023 09:34:16 +0100 Subject: [PATCH] feat(webvtt): webvtt colors output (#4954) Adds color support for SimpleTextDisplayer and WebVttGenerator (only one place to fix both now thanks to #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 #4545 --------- Co-authored-by: Alvaro Velad Galvan --- lib/text/cue.js | 32 +++++----- lib/text/text_utils.js | 91 ++++++++++++++++++++++------- test/text/vtt_text_parser_unit.js | 30 +++++----- test/text/web_vtt_generator_unit.js | 12 +++- 4 files changed, 111 insertions(+), 54 deletions(-) diff --git a/lib/text/cue.js b/lib/text/cue.js index 570fa6cbda..c6b1d05eec 100644 --- a/lib/text/cue.js +++ b/lib/text/cue.js @@ -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', }; @@ -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', }; diff --git a/lib/text/text_utils.js b/lib/text/text_utils.js index feb5a1e79b..95c9601090 100644 --- a/lib/text/text_utils.js +++ b/lib/text/text_utils.js @@ -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}`; - }, ''); + return openStyleTags.reduceRight((acc, [tag, classes = '']) => { + return `<${tag}${classes}>${acc}`; + }, 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; } /** diff --git a/test/text/vtt_text_parser_unit.js b/test/text/vtt_text_parser_unit.js index 836590be17..aadc45f950 100644 --- a/test/text/vtt_text_parser_unit.js +++ b/test/text/vtt_text_parser_unit.js @@ -910,7 +910,7 @@ describe('VttTextParser', () => { startTime: 20, endTime: 40, payload: 'Test', - color: '#FF0', + color: 'yellow', }, ], }, @@ -922,8 +922,8 @@ describe('VttTextParser', () => { startTime: 40, endTime: 50, payload: 'Test2', - color: '#0FF', - backgroundColor: '#00F', + color: 'cyan', + backgroundColor: 'blue', }, ], }, @@ -935,8 +935,8 @@ describe('VttTextParser', () => { startTime: 50, endTime: 60, payload: 'Test 3', - color: '#F0F', - backgroundColor: '#000', + color: 'magenta', + backgroundColor: 'black', }, ], }, @@ -954,7 +954,7 @@ describe('VttTextParser', () => { startTime: 60, endTime: 70, payload: 'Test4.1', - color: '#FF0', + color: 'yellow', }, { startTime: 60, @@ -971,7 +971,7 @@ describe('VttTextParser', () => { startTime: 60, endTime: 70, payload: 'Test4.2', - color: '#00F', + color: 'blue', }, ], }, @@ -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', }, ], }, @@ -1015,7 +1015,7 @@ describe('VttTextParser', () => { startTime: 100, endTime: 110, payload: 'forward slash 1/2 in text', - color: '#0F0', + color: 'lime', }, ], }, @@ -1132,8 +1132,8 @@ describe('VttTextParser', () => { startTime: 10, endTime: 20, payload: 'Example 1', - color: '#F00', - backgroundColor: '#FF0', + color: 'red', + backgroundColor: 'yellow', fontSize: '10px', }, ], @@ -1141,7 +1141,7 @@ describe('VttTextParser', () => { ], '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' + 'Example 1\n\n', {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); @@ -1159,7 +1159,7 @@ describe('VttTextParser', () => { startTime: 10, endTime: 20, payload: '1', - color: '#F0F', + color: 'magenta', }, { startTime: 10, @@ -1171,7 +1171,7 @@ describe('VttTextParser', () => { startTime: 10, endTime: 20, payload: '2', - color: '#F0F', + color: 'magenta', fontStyle: Cue.fontStyle.ITALIC, }, ], diff --git a/test/text/web_vtt_generator_unit.js b/test/text/web_vtt_generator_unit.js index 25b9d06c41..8ef71b4cef 100644 --- a/test/text/web_vtt_generator_unit.js +++ b/test/text/web_vtt_generator_unit.js @@ -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'); @@ -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' + + 'Test\n\n' + '00:00:40.000 --> 00:00:50.000 align:right vertical:rl\n' + - 'Test2\n\n' + + 'Test2\n\n' + '00:00:50.000 --> 00:00:51.000 align:middle\n' + - 'Test3\n\n' + + 'Test3\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' +