diff --git a/README.md b/README.md index 70ffdc4d7..1331411e1 100644 --- a/README.md +++ b/README.md @@ -298,12 +298,6 @@ Shaka Player supports: (depends on browser support via MediaSource). - SubRip (SRT) - UTF-8 encoding only - - LyRiCs (LRC) - - UTF-8 encoding only - - SubStation Alpha (SSA, ASS) - - UTF-8 encoding only - - SubViewer (SBV) - - UTF-8 encoding only Subtitles are rendered by the browser by default. Applications can create a [text display plugin][] for customer rendering to go beyond browser-supported diff --git a/build/types/optionalText b/build/types/optionalText index 03daef133..6227b2ca8 100644 --- a/build/types/optionalText +++ b/build/types/optionalText @@ -1,6 +1,3 @@ # Optional plugins related to text parsing and displaying. -+../../lib/text/lrc_text_parser.js -+../../lib/text/sbv_text_parser.js +../../lib/text/srt_text_parser.js -+../../lib/text/ssa_text_parser.js diff --git a/docs/tutorials/plugins.md b/docs/tutorials/plugins.md index 3152c9078..44ec0bb7a 100644 --- a/docs/tutorials/plugins.md +++ b/docs/tutorials/plugins.md @@ -39,9 +39,6 @@ __Subtitle/caption parsers__ - TTML: {@linksource shaka.text.TtmlTextParser} and {@linksource shaka.text.Mp4TtmlParser} - SubRip (SRT): {@linksource shaka.text.SrtTextParser} - - LyRiCs (LRC): {@linksource shaka.text.LrcTextParser} - - SubStation Alpha (SSA, ASS): {@linksource shaka.text.SsaTextParser} - - SubViewer (SBV): {@linksource shaka.text.SbvTextParser} __Subtitle/caption displayers__ - Configured at runtime on a Player instance diff --git a/lib/net/networking_utils.js b/lib/net/networking_utils.js index 275df1eeb..2d27f234e 100644 --- a/lib/net/networking_utils.js +++ b/lib/net/networking_utils.js @@ -94,14 +94,10 @@ shaka.net.NetworkingUtils.EXTENSIONS_TO_MIME_TYPES_ = new Map() .set('aac', 'audio/aac') .set('flac', 'audio/flac') .set('wav', 'audio/wav') - .set('sbv', 'text/x-subviewer') .set('srt', 'text/srt') .set('vtt', 'text/vtt') .set('webvtt', 'text/vtt') .set('ttml', 'application/ttml+xml') - .set('lrc', 'application/x-subtitle-lrc') - .set('ssa', 'text/x-ssa') - .set('ass', 'text/x-ssa') .set('jpeg', 'image/jpeg') .set('jpg', 'image/jpeg') .set('png', 'image/png') diff --git a/lib/text/lrc_text_parser.js b/lib/text/lrc_text_parser.js deleted file mode 100644 index e916975c8..000000000 --- a/lib/text/lrc_text_parser.js +++ /dev/null @@ -1,123 +0,0 @@ -/*! @license - * Shaka Player - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -goog.provide('shaka.text.LrcTextParser'); - -goog.require('goog.asserts'); -goog.require('shaka.log'); -goog.require('shaka.text.Cue'); -goog.require('shaka.text.TextEngine'); -goog.require('shaka.util.StringUtils'); - - -/** - * LRC file format: https://en.wikipedia.org/wiki/LRC_(file_format) - * - * @implements {shaka.extern.TextParser} - * @export - */ -shaka.text.LrcTextParser = class { - /** - * @override - * @export - */ - parseInit(data) { - goog.asserts.assert(false, 'LRC does not have init segments'); - } - - /** - * @override - * @export - */ - setManifestType(manifestType) { - // Unused. - } - - /** - * @override - * @export - */ - parseMedia(data, time) { - const StringUtils = shaka.util.StringUtils; - const LrcTextParser = shaka.text.LrcTextParser; - - // Get the input as a string. - const str = StringUtils.fromUTF8(data); - - /** @type {shaka.text.Cue} */ - let prevCue = null; - - /** @type {!Array} */ - const cues = []; - const lines = str.split(/\r?\n/); - for (const line of lines) { - if (!line || /^\s+$/.test(line)) { - continue; - } - - // LRC content - const match = LrcTextParser.lyricLine_.exec(line); - if (match) { - const startTime = LrcTextParser.parseTime_(match[1]); - // This time can be overwritten by a subsequent cue. - // By default we add 2 seconds of duration. - const endTime = time.segmentEnd ? time.segmentEnd : startTime + 2; - const payload = match[2]; - const cue = new shaka.text.Cue(startTime, endTime, payload); - - // Update previous - if (prevCue) { - prevCue.endTime = startTime; - cues.push(prevCue); - } - prevCue = cue; - continue; - } - shaka.log.warning('LrcTextParser encountered an unknown line.', line); - } - if (prevCue) { - cues.push(prevCue); - } - - return cues; - } - - /** - * Parses a LRC time from the given parser. - * - * @param {string} string - * @return {number} - * @private - */ - static parseTime_(string) { - const LrcTextParser = shaka.text.LrcTextParser; - const match = LrcTextParser.timeFormat_.exec(string); - const minutes = parseInt(match[1], 10); - const seconds = parseFloat(match[2].replace(',', '.')); - return minutes * 60 + seconds; - } -}; - -/** - * @const - * @private {!RegExp} - * @example [00:12.0]Text or [00:12.00]Text or [00:12.000]Text or - * [00:12,0]Text or [00:12,00]Text or [00:12,000]Text - */ -shaka.text.LrcTextParser.lyricLine_ = - /^\[(\d{1,2}:\d{1,2}(?:[.,]\d{1,3})?)\](.*)/; - -/** - * @const - * @private {!RegExp} - * @example 00:12.0 or 00:12.00 or 00:12.000 or - * 00:12,0 or 00:12,00 or 00:12,000 - */ -shaka.text.LrcTextParser.timeFormat_ = - /^(\d+):(\d{1,2}(?:[.,]\d{1,3})?)$/; - -shaka.text.TextEngine.registerParser( - 'application/x-subtitle-lrc', () => new shaka.text.LrcTextParser()); diff --git a/lib/text/sbv_text_parser.js b/lib/text/sbv_text_parser.js deleted file mode 100644 index 0cf8392fb..000000000 --- a/lib/text/sbv_text_parser.js +++ /dev/null @@ -1,91 +0,0 @@ -/*! @license - * Shaka Player - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -goog.provide('shaka.text.SbvTextParser'); - -goog.require('goog.asserts'); -goog.require('shaka.text.Cue'); -goog.require('shaka.text.TextEngine'); -goog.require('shaka.util.Error'); -goog.require('shaka.util.StringUtils'); -goog.require('shaka.util.TextParser'); - - -/** - * @implements {shaka.extern.TextParser} - * @export - */ -shaka.text.SbvTextParser = class { - /** - * @override - * @export - */ - parseInit(data) { - goog.asserts.assert(false, 'SubViewer does not have init segments'); - } - - /** - * @override - * @export - */ - setManifestType(manifestType) { - // Unused. - } - - /** - * @override - * @export - */ - parseMedia(data, time) { - const StringUtils = shaka.util.StringUtils; - - // Get the input as a string. - const strFromData = StringUtils.fromUTF8(data); - // remove dos newlines - let str = strFromData.replace(/\r+/g, ''); - // trim white space start and end - str = str.trim(); - - /** @type {!Array} */ - const cues = []; - - // Supports no cues - if (str == '') { - return cues; - } - - // get cues - const blocklist = str.split('\n\n'); - for (const block of blocklist) { - const lines = block.split('\n'); - // Parse the times. - const parser = new shaka.util.TextParser(lines[0]); - const start = parser.parseTime(); - const expect = parser.readRegex(/,/g); - const end = parser.parseTime(); - - if (start == null || expect == null || end == null) { - throw new shaka.util.Error( - shaka.util.Error.Severity.CRITICAL, - shaka.util.Error.Category.TEXT, - shaka.util.Error.Code.INVALID_TEXT_CUE, - 'Could not parse cue time range in SubViewer'); - } - - // Get the payload. - const payload = lines.slice(1).join('\n').trim(); - - const cue = new shaka.text.Cue(start, end, payload); - cues.push(cue); - } - - return cues; - } -}; - - -shaka.text.TextEngine.registerParser( - 'text/x-subviewer', () => new shaka.text.SbvTextParser()); diff --git a/lib/text/ssa_text_parser.js b/lib/text/ssa_text_parser.js deleted file mode 100644 index 4b5252290..000000000 --- a/lib/text/ssa_text_parser.js +++ /dev/null @@ -1,347 +0,0 @@ -/*! @license - * Shaka Player - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -// cspell:ignore AABBGGRR HAABBGGRR - -goog.provide('shaka.text.SsaTextParser'); - -goog.require('goog.asserts'); -goog.require('shaka.log'); -goog.require('shaka.text.Cue'); -goog.require('shaka.text.TextEngine'); -goog.require('shaka.util.StringUtils'); - - -/** - * Documentation: http://moodub.free.fr/video/ass-specs.doc - * https://en.wikipedia.org/wiki/SubStation_Alpha - * @implements {shaka.extern.TextParser} - * @export - */ -shaka.text.SsaTextParser = class { - /** - * @override - * @export - */ - parseInit(data) { - goog.asserts.assert(false, 'SSA does not have init segments'); - } - - /** - * @override - * @export - */ - setManifestType(manifestType) { - // Unused. - } - - /** - * @override - * @export - */ - parseMedia(data, time) { - const StringUtils = shaka.util.StringUtils; - const SsaTextParser = shaka.text.SsaTextParser; - - // Get the input as a string. - const str = StringUtils.fromUTF8(data); - - const section = { - styles: '', - events: '', - }; - - let tag = null; - let lines = null; - const parts = str.split(/\r?\n\s*\r?\n/); - for (const part of parts) { - lines = part; - // SSA content - const match = SsaTextParser.ssaContent_.exec(part); - if (match) { - tag = match[1]; - lines = match[2]; - } - if (tag == 'V4 Styles' || tag == 'V4+ Styles') { - section.styles = lines; - if (section.events) { - section.styles += '\n' + lines; - } else { - section.styles = lines; - } - continue; - } - if (tag == 'Events') { - if (section.events) { - section.events += '\n' + lines; - } else { - section.events = lines; - } - continue; - } - if (tag == 'Script Info') { - continue; - } - shaka.log.warning('SsaTextParser parser encountered an unknown part.', - lines); - } - - // Process styles - const styles = []; - - // Used to be able to iterate over the style parameters. - let styleColumns = null; - - const styleLines = section.styles.split(/\r?\n/); - for (const line of styleLines) { - if (/^\s*;/.test(line)) { - // Skip comment - continue; - } - const lineParts = SsaTextParser.lineParts_.exec(line); - if (lineParts) { - const name = lineParts[1].trim(); - const value = lineParts[2].trim(); - if (name == 'Format') { - styleColumns = value.split(SsaTextParser.valuesFormat_); - continue; - } - if (name == 'Style') { - const values = value.split(SsaTextParser.valuesFormat_); - const style = {}; - for (let c = 0; c < styleColumns.length && c < values.length; c++) { - style[styleColumns[c]] = values[c]; - } - styles.push(style); - continue; - } - } - } - - // Process cues - /** @type {!Array} */ - const cues = []; - - // Used to be able to iterate over the event parameters. - let eventColumns = null; - - const eventLines = section.events.split(/\r?\n/); - for (const line of eventLines) { - if (/^\s*;/.test(line)) { - // Skip comment - continue; - } - const lineParts = SsaTextParser.lineParts_.exec(line); - if (lineParts) { - const name = lineParts[1].trim(); - const value = lineParts[2].trim(); - if (name == 'Format') { - eventColumns = value.split(SsaTextParser.valuesFormat_); - continue; - } - if (name == 'Dialogue') { - const values = value.split(SsaTextParser.valuesFormat_); - const data = {}; - for (let c = 0; c < eventColumns.length && c < values.length; c++) { - data[eventColumns[c]] = values[c]; - } - - const startTime = SsaTextParser.parseTime_(data['Start']); - const endTime = SsaTextParser.parseTime_(data['End']); - - // Note: Normally, you should take the "Text" field, but if it - // has a comma, it fails. - const payload = values.slice(eventColumns.length - 1).join(',') - .replace(/\\N/g, '\n') // '\n' for new line - .replace(/\{[^}]+\}/g, ''); // {\pos(400,570)} - - const cue = new shaka.text.Cue(startTime, endTime, payload); - - const styleName = data['Style']; - const styleData = styles.find((s) => s['Name'] == styleName); - if (styleData) { - SsaTextParser.addStyle_(cue, styleData); - } - cues.push(cue); - continue; - } - } - } - - return cues; - } - - /** - * Adds applicable style properties to a cue. - * - * @param {shaka.text.Cue} cue - * @param {Object} style - * @private - */ - static addStyle_(cue, style) { - const Cue = shaka.text.Cue; - const SsaTextParser = shaka.text.SsaTextParser; - const fontFamily = style['Fontname']; - if (fontFamily) { - cue.fontFamily = fontFamily; - } - const fontSize = style['Fontsize']; - if (fontSize) { - cue.fontSize = fontSize + 'px'; - } - const color = style['PrimaryColour']; - if (color) { - const ccsColor = SsaTextParser.parseSsaColor_(color); - if (ccsColor) { - cue.color = ccsColor; - } - } - const backgroundColor = style['BackColour']; - if (backgroundColor) { - const cssBackgroundColor = SsaTextParser.parseSsaColor_(backgroundColor); - if (cssBackgroundColor) { - cue.backgroundColor = cssBackgroundColor; - } - } - const bold = style['Bold']; - if (bold) { - cue.fontWeight = Cue.fontWeight.BOLD; - } - const italic = style['Italic']; - if (italic) { - cue.fontStyle = Cue.fontStyle.ITALIC; - } - const underline = style['Underline']; - if (underline) { - cue.textDecoration.push(Cue.textDecoration.UNDERLINE); - } - const letterSpacing = style['Spacing']; - if (letterSpacing) { - cue.letterSpacing = letterSpacing + 'px'; - } - const alignment = style['Alignment']; - if (alignment) { - const alignmentInt = parseInt(alignment, 10); - switch (alignmentInt) { - case 1: - cue.displayAlign = Cue.displayAlign.AFTER; - cue.textAlign = Cue.textAlign.START; - break; - case 2: - cue.displayAlign = Cue.displayAlign.AFTER; - cue.textAlign = Cue.textAlign.CENTER; - break; - case 3: - cue.displayAlign = Cue.displayAlign.AFTER; - cue.textAlign = Cue.textAlign.END; - break; - case 5: - cue.displayAlign = Cue.displayAlign.BEFORE; - cue.textAlign = Cue.textAlign.START; - break; - case 6: - cue.displayAlign = Cue.displayAlign.BEFORE; - cue.textAlign = Cue.textAlign.CENTER; - break; - case 7: - cue.displayAlign = Cue.displayAlign.BEFORE; - cue.textAlign = Cue.textAlign.END; - break; - case 9: - cue.displayAlign = Cue.displayAlign.CENTER; - cue.textAlign = Cue.textAlign.START; - break; - case 10: - cue.displayAlign = Cue.displayAlign.CENTER; - cue.textAlign = Cue.textAlign.CENTER; - break; - case 11: - cue.displayAlign = Cue.displayAlign.CENTER; - cue.textAlign = Cue.textAlign.END; - break; - } - } - const opacity = style['AlphaLevel']; - if (opacity) { - cue.opacity = parseFloat(opacity); - } - } - - /** - * Parses a SSA color . - * - * @param {string} colorString - * @return {?string} - * @private - */ - static parseSsaColor_(colorString) { - // The SSA V4+ color can be represented in hex (&HAABBGGRR) or in decimal - // format (byte order AABBGGRR) and in both cases the alpha channel's - // value needs to be inverted as in case of SSA the 0xFF alpha value means - // transparent and 0x00 means opaque - /** @type {number} */ - const abgr = parseInt(colorString.replace('&H', ''), 16); - if (abgr >= 0) { - const a = ((abgr >> 24) & 0xFF) ^ 0xFF; // Flip alpha. - const alpha = a / 255; - const b = (abgr >> 16) & 0xFF; - const g = (abgr >> 8) & 0xFF; - const r = abgr & 0xff; - return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')'; - } - return null; - } - - /** - * Parses a SSA time from the given parser. - * - * @param {string} string - * @return {number} - * @private - */ - static parseTime_(string) { - const SsaTextParser = shaka.text.SsaTextParser; - const match = SsaTextParser.timeFormat_.exec(string); - const hours = match[1] ? parseInt(match[1].replace(':', ''), 10) : 0; - const minutes = parseInt(match[2], 10); - const seconds = parseFloat(match[3]); - return hours * 3600 + minutes * 60 + seconds; - } -}; - -/** - * @const - * @private {!RegExp} - * @example [V4 Styles]\nFormat: Name\nStyle: DefaultVCD - */ -shaka.text.SsaTextParser.ssaContent_ = - /^\s*\[([^\]]+)\]\r?\n([\s\S]*)/; - -/** - * @const - * @private {!RegExp} - * @example Style: DefaultVCD,... - */ -shaka.text.SsaTextParser.lineParts_ = - /^\s*([^:]+):\s*(.*)/; - -/** - * @const - * @private {!RegExp} - * @example Style: DefaultVCD,... - */ -shaka.text.SsaTextParser.valuesFormat_ = /\s*,\s*/; - -/** - * @const - * @private {!RegExp} - * @example 0:00:01.1 or 0:00:01.18 or 0:00:01.180 - */ -shaka.text.SsaTextParser.timeFormat_ = - /^(\d+:)?(\d{1,2}):(\d{1,2}(?:[.]\d{1,3})?)?$/; - -shaka.text.TextEngine.registerParser( - 'text/x-ssa', () => new shaka.text.SsaTextParser()); diff --git a/shaka-player.uncompiled.js b/shaka-player.uncompiled.js index 1eefbd794..de014301c 100644 --- a/shaka-player.uncompiled.js +++ b/shaka-player.uncompiled.js @@ -61,14 +61,11 @@ goog.require('shaka.polyfill.VideoPlayPromise'); goog.require('shaka.polyfill.VideoPlaybackQuality'); goog.require('shaka.polyfill'); goog.require('shaka.text.Cue'); -goog.require('shaka.text.LrcTextParser'); goog.require('shaka.text.Mp4TtmlParser'); goog.require('shaka.text.Mp4VttParser'); goog.require('shaka.text.TextEngine'); -goog.require('shaka.text.SbvTextParser'); goog.require('shaka.text.SpeechToText'); goog.require('shaka.text.SrtTextParser'); -goog.require('shaka.text.SsaTextParser'); goog.require('shaka.text.TtmlTextParser'); goog.require('shaka.text.VttTextParser'); goog.require('shaka.text.WebVttGenerator'); diff --git a/test/text/lrc_text_parser_unit.js b/test/text/lrc_text_parser_unit.js deleted file mode 100644 index 3eacf22a4..000000000 --- a/test/text/lrc_text_parser_unit.js +++ /dev/null @@ -1,98 +0,0 @@ -/*! @license - * Shaka Player - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -describe('LrcTextParser', () => { - it('supports no cues', () => { - verifyHelper([], - '', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('handles a blank line at the start of the file', () => { - verifyHelper( - [ - {startTime: 0, endTime: 2, payload: 'Test'}, - ], - '\n\n' + - '[00:00.00]Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('handles a blank line at the end of the file', () => { - verifyHelper( - [ - {startTime: 0, endTime: 2, payload: 'Test'}, - ], - '[00:00.00]Test' + - '\n\n', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('handles no blank line at the end of the file', () => { - verifyHelper( - [ - {startTime: 0, endTime: 2, payload: 'Test'}, - ], - '[00:00.00]Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports multiple cues', () => { - verifyHelper( - [ - {startTime: 0, endTime: 10, payload: 'Test'}, - {startTime: 10, endTime: 20, payload: 'Test2'}, - {startTime: 20, endTime: 22, payload: 'Test3'}, - ], - '[00:00.00]Test\n' + - '[00:10.00]Test2\n' + - '[00:20.00]Test3', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports different time formats', () => { - verifyHelper( - [ - {startTime: 0.1, endTime: 10.001, payload: 'Test'}, - {startTime: 10.001, endTime: 20.02, payload: 'Test2'}, - {startTime: 20.02, endTime: 30.1, payload: 'Test3'}, - {startTime: 30.1, endTime: 40.001, payload: 'Test4'}, - {startTime: 40.001, endTime: 50.02, payload: 'Test5'}, - {startTime: 50.02, endTime: 52.02, payload: 'Test6'}, - ], - '[00:00.1]Test\n' + - '[00:10.001]Test2\n' + - '[00:20.02]Test3\n' + - '[00:30,1]Test4\n' + - '[00:40,001]Test5\n' + - '[00:50,02]Test6', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - /** - * @param {!Array} cues - * @param {string} text - * @param {shaka.extern.TextParser.TimeContext} time - */ - function verifyHelper(cues, text, time) { - const BufferUtils = shaka.util.BufferUtils; - const StringUtils = shaka.util.StringUtils; - - const data = BufferUtils.toUint8(StringUtils.toUTF8(text)); - - const parser = new shaka.text.LrcTextParser(); - const result = parser.parseMedia(data, time); - - const expected = cues.map((cue) => { - if (cue.nestedCues) { - cue.nestedCues = cue.nestedCues.map( - (nestedCue) => jasmine.objectContaining(nestedCue)); - } - return jasmine.objectContaining(cue); - }); - expect(result).toEqual(expected); - } -}); diff --git a/test/text/sbv_text_parser_unit.js b/test/text/sbv_text_parser_unit.js deleted file mode 100644 index bc89ee000..000000000 --- a/test/text/sbv_text_parser_unit.js +++ /dev/null @@ -1,81 +0,0 @@ -/*! @license - * Shaka Player - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -describe('SbvTextParser', () => { - it('supports no cues', () => { - verifyHelper([], - '', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('handles a blank line at the end of the file', () => { - verifyHelper( - [ - {startTime: 20, endTime: 40, payload: 'Test'}, - ], - '0:00:20.000,0:00:40.000\n' + - 'Test\n\n', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('handles no blank line at the end of the file', () => { - verifyHelper( - [ - {startTime: 20, endTime: 40, payload: 'Test'}, - ], - '0:00:20.000,0:00:40.000\n' + - 'Test\n', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('handles no newline after the final text payload', () => { - verifyHelper( - [ - {startTime: 20, endTime: 40, payload: 'Test'}, - ], - '0:00:20.000,0:00:40.000\n' + - 'Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports multiple cues', () => { - verifyHelper( - [ - {startTime: 20, endTime: 40, payload: 'Test'}, - {startTime: 40, endTime: 50, payload: 'Test2'}, - ], - '0:00:20.000,0:00:40.000\n' + - 'Test\n\n' + - '0:00:40.000,0:00:50.000\n' + - 'Test2', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - /** - * @param {!Array} cues - * @param {string} text - * @param {shaka.extern.TextParser.TimeContext} time - */ - function verifyHelper(cues, text, time) { - const BufferUtils = shaka.util.BufferUtils; - const StringUtils = shaka.util.StringUtils; - - const data = BufferUtils.toUint8(StringUtils.toUTF8(text)); - - const parser = new shaka.text.SbvTextParser(); - const result = parser.parseMedia(data, time); - - const expected = cues.map((cue) => { - if (cue.nestedCues) { - cue.nestedCues = cue.nestedCues.map( - (nestedCue) => jasmine.objectContaining(nestedCue), - ); - } - return jasmine.objectContaining(cue); - }); - expect(result).toEqual(expected); - } -}); diff --git a/test/text/ssa_text_parser_unit.js b/test/text/ssa_text_parser_unit.js deleted file mode 100644 index 928899131..000000000 --- a/test/text/ssa_text_parser_unit.js +++ /dev/null @@ -1,384 +0,0 @@ -/*! @license - * Shaka Player - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -// cspell:ignore FCFC - -describe('SsaTextParser', () => { - const Cue = shaka.text.Cue; - - it('supports no cues', () => { - verifyHelper([], - '', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('handles a blank line at the start of the file', () => { - verifyHelper( - [ - {startTime: 0, endTime: 2, payload: 'Test'}, - ], - '\n\n' + - '[Script Info]\n' + - 'Title: Foo\n\n' + - '[V4+ Styles]\n' + - 'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, ' + - 'OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ' + - 'ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, ' + - 'Alignment, MarginL, MarginR, MarginV, Encoding\n' + - 'Style: DefaultVCD, Arial,28,&H00B4FCFC,&H00B4FCFC,&H00000008,' + - '&H80000008,-1,0,0,0,100,100,0.00,0.00,1,1.00,2.00,2,30,30,30,0\n\n' + - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('handles a blank line at the end of the file', () => { - verifyHelper( - [ - {startTime: 0, endTime: 2, payload: 'Test'}, - ], - '[Script Info]\n' + - 'Title: Foo\n\n' + - '[V4+ Styles]\n' + - 'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, ' + - 'OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ' + - 'ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, ' + - 'Alignment, MarginL, MarginR, MarginV, Encoding\n' + - 'Style: DefaultVCD, Arial,28,&H00B4FCFC,&H00B4FCFC,&H00000008,' + - '&H80000008,-1,0,0,0,100,100,0.00,0.00,1,1.00,2.00,2,30,30,30,0\n\n' + - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test' + - '\n\n', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('handles no blank line at the end of the file', () => { - verifyHelper( - [ - {startTime: 0, endTime: 2, payload: 'Test'}, - ], - '[Script Info]\n' + - 'Title: Foo\n\n' + - '[V4+ Styles]\n' + - 'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, ' + - 'OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ' + - 'ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, ' + - 'Alignment, MarginL, MarginR, MarginV, Encoding\n' + - 'Style: DefaultVCD, Arial,28,&H00B4FCFC,&H00B4FCFC,&H00000008,' + - '&H80000008,-1,0,0,0,100,100,0.00,0.00,1,1.00,2.00,2,30,30,30,0\n\n' + - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports no styles', () => { - verifyHelper( - [ - {startTime: 0, endTime: 2, payload: 'Test'}, - ], - '[Script Info]\n' + - 'Title: Foo\n\n' + - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('support no script info', () => { - verifyHelper( - [ - {startTime: 0, endTime: 2, payload: 'Test'}, - ], - '[V4+ Styles]\n' + - 'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, ' + - 'OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ' + - 'ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, ' + - 'Alignment, MarginL, MarginR, MarginV, Encoding\n' + - 'Style: DefaultVCD, Arial,28,&H00B4FCFC,&H00B4FCFC,&H00000008,' + - '&H80000008,-1,0,0,0,100,100,0.00,0.00,1,1.00,2.00,2,30,30,30,0\n\n' + - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports only events', () => { - verifyHelper( - [ - {startTime: 0, endTime: 2, payload: 'Test'}, - ], - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports text with commas', () => { - verifyHelper( - [ - {startTime: 0, endTime: 2, payload: 'Test,1,Test2'}, - ], - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test,1,Test2', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports different time formats', () => { - verifyHelper( - [ - {startTime: 0, endTime: 2, payload: 'Test'}, - {startTime: 4.5, endTime: 6.1, payload: 'Test2'}, - {startTime: 8.01, endTime: 10.001, payload: 'Test3'}, - ], - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test\n' + - 'Dialogue: 0,0:00:04.5,0:00:06.1,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test2\n' + - 'Dialogue: 0,0:00:08.01,0:00:10.001,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test3', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports multiple cues', () => { - verifyHelper( - [ - {startTime: 0, endTime: 2, payload: 'Test'}, - {startTime: 4.5, endTime: 6.1, payload: 'Test2'}, - {startTime: 8.01, endTime: 10.1, payload: 'Test3'}, - ], - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test\n\n' + - 'Dialogue: 0,0:00:04.50,0:00:06.10,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test2\n\n' + - 'Dialogue: 0,0:00:08.01,0:00:10.10,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test3', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports fontFamily style', () => { - verifyHelper( - [ - { - startTime: 0, - endTime: 2, - payload: 'Test', - fontFamily: 'Arial', - }, - ], - '[V4+ Styles]\n' + - 'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, ' + - 'OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ' + - 'ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, ' + - 'Alignment, MarginL, MarginR, MarginV, Encoding\n' + - 'Style: DefaultVCD, Arial,28,&H00B4FCFC,&H00B4FCFC,&H00000008,' + - '&H80000008,-1,0,0,0,100,100,0.00,0.00,1,1.00,2.00,2,30,30,30,0\n\n' + - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports color & backgroundColor style', () => { - verifyHelper( - [ - { - startTime: 0, - endTime: 2, - payload: 'Test', - color: 'rgba(252,252,180,1)', - backgroundColor: 'rgba(8,0,0,0)', - }, - ], - '[V4+ Styles]\n' + - 'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, ' + - 'OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ' + - 'ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, ' + - 'Alignment, MarginL, MarginR, MarginV, Encoding\n' + - 'Style: DefaultVCD, Arial,28,&H00B4FCFC,&H00B4FCFC,&H00000008,' + - '&HFF000008,-1,0,0,0,100,100,0.00,0.00,1,1.00,2.00,2,30,30,30,0\n\n' + - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports bold style', () => { - verifyHelper( - [ - { - startTime: 0, - endTime: 2, - payload: 'Test', - fontWeight: Cue.fontWeight.BOLD, - }, - ], - '[V4+ Styles]\n' + - 'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, ' + - 'OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ' + - 'ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, ' + - 'Alignment, MarginL, MarginR, MarginV, Encoding\n' + - 'Style: DefaultVCD, Arial,28,&H00B4FCFC,&H00B4FCFC,&H00000008,' + - '&H80000008,-1,1,0,0,100,100,0.00,0.00,1,1.00,2.00,2,30,30,30,0\n\n' + - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports italic style', () => { - verifyHelper( - [ - { - startTime: 0, - endTime: 2, - payload: 'Test', - fontStyle: Cue.fontStyle.ITALIC, - }, - ], - '[V4+ Styles]\n' + - 'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, ' + - 'OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ' + - 'ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, ' + - 'Alignment, MarginL, MarginR, MarginV, Encoding\n' + - 'Style: DefaultVCD, Arial,28,&H00B4FCFC,&H00B4FCFC,&H00000008,' + - '&H80000008,-1,0,1,0,100,100,0.00,0.00,1,1.00,2.00,2,30,30,30,0\n\n' + - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports underline style', () => { - verifyHelper( - [ - { - startTime: 0, - endTime: 2, - payload: 'Test', - textDecoration: [Cue.textDecoration.UNDERLINE], - }, - ], - '[V4+ Styles]\n' + - 'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, ' + - 'OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ' + - 'ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, ' + - 'Alignment, MarginL, MarginR, MarginV, Encoding\n' + - 'Style: DefaultVCD, Arial,28,&H00B4FCFC,&H00B4FCFC,&H00000008,' + - '&H80000008,-1,0,0,1,100,100,0.00,0.00,1,1.00,2.00,2,30,30,30,0\n\n' + - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports letterSpacing style', () => { - verifyHelper( - [ - { - startTime: 0, - endTime: 2, - payload: 'Test', - letterSpacing: '2px', - }, - ], - '[V4+ Styles]\n' + - 'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, ' + - 'OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ' + - 'ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, ' + - 'Alignment, MarginL, MarginR, MarginV, Encoding\n' + - 'Style: DefaultVCD, Arial,28,&H00B4FCFC,&H00B4FCFC,&H00000008,' + - '&H80000008,-1,0,0,0,100,100,2,0.00,1,1.00,2.00,2,30,30,30,0\n\n' + - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - it('supports V4 style', () => { - verifyHelper( - [ - { - startTime: 0, - endTime: 2, - payload: 'Test', - fontFamily: 'Arial', - }, - ], - '[V4 Styles]\n' + - 'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, ' + - 'TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, ' + - 'Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, '+ - 'Encoding\n' + - 'Style: DefaultVCD, Arial,28,11861244,11861244,11861244,' + - '-2147483640,-1,0,1,1,2,2,30,30,30,0,0\n\n' + - '[Events]\n' + - 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, ' + - 'Effect, Text\n' + - 'Dialogue: 0,0:00:00.00,0:00:02.00,DefaultVCD, NTP,0000,0000,0000' + - ',,{\\pos(400,570)}Test', - {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0}); - }); - - /** - * @param {!Array} cues - * @param {string} text - * @param {shaka.extern.TextParser.TimeContext} time - */ - function verifyHelper(cues, text, time) { - const BufferUtils = shaka.util.BufferUtils; - const StringUtils = shaka.util.StringUtils; - - const data = BufferUtils.toUint8(StringUtils.toUTF8(text)); - - const parser = new shaka.text.SsaTextParser(); - const result = parser.parseMedia(data, time); - - const expected = cues.map((cue) => { - if (cue.nestedCues) { - cue.nestedCues = cue.nestedCues.map( - (nestedCue) => jasmine.objectContaining(nestedCue)); - } - return jasmine.objectContaining(cue); - }); - expect(result).toEqual(expected); - } -});