mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-16 16:16:40 +03:00
374 lines
9.2 KiB
JavaScript
374 lines
9.2 KiB
JavaScript
/*! @license
|
||
* Shaka Player
|
||
* Copyright 2016 Google LLC
|
||
* SPDX-License-Identifier: Apache-2.0
|
||
*/
|
||
|
||
goog.provide('shaka.cea.Cea608Memory');
|
||
|
||
goog.require('shaka.cea.CeaUtils');
|
||
goog.require('shaka.text.Cue');
|
||
|
||
|
||
/**
|
||
* CEA-608 captions memory/buffer.
|
||
*/
|
||
shaka.cea.Cea608Memory = class {
|
||
/**
|
||
* @param {number} fieldNum Field number.
|
||
* @param {number} channelNum Channel number.
|
||
*/
|
||
constructor(fieldNum, channelNum) {
|
||
/**
|
||
* Buffer for storing decoded characters.
|
||
* @private @const {!Array<!Array<!shaka.cea.CeaUtils.StyledChar>>}
|
||
*/
|
||
this.rows_ = [];
|
||
|
||
/**
|
||
* Current row.
|
||
* @private {number}
|
||
*/
|
||
this.row_ = 1;
|
||
|
||
/**
|
||
* Number of rows in the scroll window. Used for rollup mode.
|
||
* @private {number}
|
||
*/
|
||
this.scrollRows_ = 0;
|
||
|
||
/**
|
||
* Field number.
|
||
* @private {number}
|
||
*/
|
||
this.fieldNum_ = fieldNum;
|
||
|
||
/**
|
||
* Channel number.
|
||
* @private {number}
|
||
*/
|
||
this.channelNum_ = channelNum;
|
||
|
||
/**
|
||
* @private {boolean}
|
||
*/
|
||
this.underline_ = false;
|
||
|
||
/**
|
||
* @private {boolean}
|
||
*/
|
||
this.italics_ = false;
|
||
|
||
/**
|
||
* @private {string}
|
||
*/
|
||
this.textColor_ = shaka.cea.CeaUtils.DEFAULT_TXT_COLOR;
|
||
|
||
/**
|
||
* @private {string}
|
||
*/
|
||
this.backgroundColor_ = shaka.cea.CeaUtils.DEFAULT_BG_COLOR;
|
||
|
||
/**
|
||
* @private {?number}
|
||
*/
|
||
this.offset_ = null;
|
||
|
||
/**
|
||
* @private {?number}
|
||
*/
|
||
this.indent_ = null;
|
||
|
||
this.reset();
|
||
}
|
||
|
||
/**
|
||
* Emits a closed caption based on the state of the buffer.
|
||
* @param {number} startTime Start time of the cue.
|
||
* @param {number} endTime End time of the cue.
|
||
* @return {?shaka.extern.ICaptionDecoder.ClosedCaption}
|
||
*/
|
||
forceEmit(startTime, endTime) {
|
||
const Cea608Memory = shaka.cea.Cea608Memory;
|
||
const stream = `CC${((this.fieldNum_<< 1) | this.channelNum_) + 1}`;
|
||
const topLevelCue = new shaka.text.Cue(
|
||
startTime, endTime, /* payload= */ '');
|
||
topLevelCue.lineInterpretation =
|
||
shaka.text.Cue.lineInterpretation.PERCENTAGE;
|
||
let line = Cea608Memory.ROW_TO_LINE_CONVERSION_.get(this.row_);
|
||
if (line) {
|
||
topLevelCue.line = line;
|
||
}
|
||
if (this.indent_ != null && this.offset_ != null) {
|
||
topLevelCue.position = 10 + Math.min(70, this.indent_ * 10) +
|
||
this.offset_ * 2.5;
|
||
}
|
||
const ret = shaka.cea.CeaUtils.getParsedCaption(
|
||
topLevelCue, stream, this.rows_, startTime, endTime);
|
||
// If the text and its lines are larger than what we can show on the
|
||
// screen, we move the lines up so that the text does not come out of the
|
||
// video.
|
||
if (ret && (this.row_ + ret.cue.nestedCues.length - 3) > 15) {
|
||
const newLinePosition = this.row_ + 3 - ret.cue.nestedCues.length;
|
||
line = Cea608Memory.ROW_TO_LINE_CONVERSION_.get(newLinePosition);
|
||
if (line) {
|
||
topLevelCue.line = line;
|
||
}
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* Resets the memory buffer.
|
||
*/
|
||
reset() {
|
||
this.resetAllRows();
|
||
this.row_ = 1;
|
||
}
|
||
|
||
/**
|
||
* @return {number}
|
||
*/
|
||
getRow() {
|
||
return this.row_;
|
||
}
|
||
|
||
/**
|
||
* @param {number} row
|
||
*/
|
||
setRow(row) {
|
||
this.row_ = row;
|
||
}
|
||
|
||
/**
|
||
* @return {number}
|
||
*/
|
||
getScrollSize() {
|
||
return this.scrollRows_;
|
||
}
|
||
|
||
/**
|
||
* @param {number} scrollRows
|
||
*/
|
||
setScrollSize(scrollRows) {
|
||
this.scrollRows_ = scrollRows;
|
||
}
|
||
|
||
/**
|
||
* Adds a character to the buffer.
|
||
* @param {!shaka.cea.Cea608Memory.CharSet} set Character set.
|
||
* @param {number} b CC byte to add.
|
||
*/
|
||
addChar(set, b) {
|
||
// Valid chars are in the range [0x20, 0x7f]
|
||
if (b < 0x20 || b > 0x7f) {
|
||
return;
|
||
}
|
||
|
||
let char = '';
|
||
switch (set) {
|
||
case shaka.cea.Cea608Memory.CharSet.BASIC_NORTH_AMERICAN:
|
||
if (shaka.cea.Cea608Memory.CharSet.BasicNorthAmericanChars.has(b)) {
|
||
char =
|
||
shaka.cea.Cea608Memory.CharSet.BasicNorthAmericanChars.get(b);
|
||
} else {
|
||
// Regular ASCII
|
||
char = String.fromCharCode(b);
|
||
}
|
||
break;
|
||
case shaka.cea.Cea608Memory.CharSet.SPECIAL_NORTH_AMERICAN:
|
||
char =
|
||
shaka.cea.Cea608Memory.CharSet.SpecialNorthAmericanChars.get(b);
|
||
break;
|
||
case shaka.cea.Cea608Memory.CharSet.SPANISH_FRENCH:
|
||
// Extended charset does a BS over preceding char, 6.4.2 EIA-608-B.
|
||
this.eraseChar();
|
||
char =
|
||
shaka.cea.Cea608Memory.CharSet.ExtendedSpanishFrench.get(b);
|
||
break;
|
||
case shaka.cea.Cea608Memory.CharSet.PORTUGUESE_GERMAN:
|
||
this.eraseChar();
|
||
char =
|
||
shaka.cea.Cea608Memory.CharSet.ExtendedPortugueseGerman.get(b);
|
||
break;
|
||
}
|
||
|
||
if (char) {
|
||
const styledChar = new shaka.cea.CeaUtils.StyledChar(
|
||
char, this.underline_, this.italics_,
|
||
this.backgroundColor_, this.textColor_);
|
||
this.rows_[this.row_].push(styledChar);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Erases a character from the buffer.
|
||
*/
|
||
eraseChar() {
|
||
this.rows_[this.row_].pop();
|
||
}
|
||
|
||
/**
|
||
* Moves rows of characters.
|
||
* @param {number} dst Destination row index.
|
||
* @param {number} src Source row index.
|
||
* @param {number} count Count of rows to move.
|
||
*/
|
||
moveRows(dst, src, count) {
|
||
if (src < 0 || dst < 0) {
|
||
return;
|
||
}
|
||
|
||
if (dst >= src) {
|
||
for (let i = count-1; i >= 0; i--) {
|
||
this.rows_[dst + i] = this.rows_[src + i].map((e) => e);
|
||
}
|
||
} else {
|
||
for (let i = 0; i < count; i++) {
|
||
this.rows_[dst + i] = this.rows_[src + i].map((e) => e);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Resets rows of characters.
|
||
* @param {number} idx Starting index.
|
||
* @param {number} count Count of rows to reset.
|
||
*/
|
||
resetRows(idx, count) {
|
||
for (let i = 0; i <= count; i++) {
|
||
this.rows_[idx + i] = [];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Resets the entire memory buffer.
|
||
*/
|
||
resetAllRows() {
|
||
this.resetRows(0, shaka.cea.Cea608Memory.CC_ROWS);
|
||
}
|
||
|
||
/**
|
||
* Erases entire memory buffer.
|
||
* Doesn't change scroll state or number of rows.
|
||
*/
|
||
eraseBuffer() {
|
||
this.row_ = (this.scrollRows_ > 0) ? this.scrollRows_ : 0;
|
||
this.resetAllRows();
|
||
}
|
||
|
||
/**
|
||
* @param {boolean} underline
|
||
*/
|
||
setUnderline(underline) {
|
||
this.underline_ = underline;
|
||
}
|
||
|
||
/**
|
||
* @param {boolean} italics
|
||
*/
|
||
setItalics(italics) {
|
||
this.italics_ = italics;
|
||
}
|
||
|
||
/**
|
||
* @param {string} color
|
||
*/
|
||
setTextColor(color) {
|
||
this.textColor_ = color;
|
||
}
|
||
|
||
/**
|
||
* @param {string} color
|
||
*/
|
||
setBackgroundColor(color) {
|
||
this.backgroundColor_ = color;
|
||
}
|
||
|
||
/**
|
||
* @param {number} offset
|
||
*/
|
||
setOffset(offset) {
|
||
this.offset_ = offset;
|
||
}
|
||
|
||
/**
|
||
* @param {?number} indent
|
||
*/
|
||
setIndent(indent) {
|
||
this.indent_ = indent;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Maximum number of rows in the buffer.
|
||
* @const {number}
|
||
*/
|
||
shaka.cea.Cea608Memory.CC_ROWS = 15;
|
||
|
||
/**
|
||
* Characters sets.
|
||
* @const @enum {number}
|
||
*/
|
||
shaka.cea.Cea608Memory.CharSet = {
|
||
BASIC_NORTH_AMERICAN: 0,
|
||
SPECIAL_NORTH_AMERICAN: 1,
|
||
SPANISH_FRENCH: 2,
|
||
PORTUGUESE_GERMAN: 3,
|
||
};
|
||
|
||
/**
|
||
* Basic North American char set deviates from ASCII with these exceptions.
|
||
* @private @const {!Map<number, string>}
|
||
*/
|
||
shaka.cea.Cea608Memory.CharSet.BasicNorthAmericanChars = new Map([
|
||
[0x27, '’'], [0x2a, 'á'], [0x5c, 'é'], [0x5c, 'é'], [0x5e, 'í'], [0x5f, 'ó'],
|
||
[0x60, 'ú'], [0x7b, 'ç'], [0x7c, '÷'], [0x7d, 'Ñ'], [0x7e, 'ñ'], [0x7f, '█'],
|
||
]);
|
||
|
||
/**
|
||
* Special North American char set.
|
||
* Note: Transparent Space is currently implemented as a regular space.
|
||
* @private @const {!Map<number, string>}
|
||
*/
|
||
shaka.cea.Cea608Memory.CharSet.SpecialNorthAmericanChars = new Map([
|
||
[0x30, '®'], [0x31, '°'], [0x32, '½'], [0x33, '¿'], [0x34, '™'], [0x35, '¢'],
|
||
[0x36, '£'], [0x37, '♪'], [0x38, 'à'], [0x39, ' '], [0x3a, 'è'], [0x3b, 'â'],
|
||
[0x3c, 'ê'], [0x3d, 'î'], [0x3e, 'ô'], [0x3f, 'û'],
|
||
]);
|
||
|
||
/**
|
||
* Extended Spanish/Misc/French char set.
|
||
* @private @const {!Map<number, string>}
|
||
*/
|
||
shaka.cea.Cea608Memory.CharSet.ExtendedSpanishFrench = new Map([
|
||
[0x20, 'Á'], [0x21, 'É'], [0x22, 'Ó'], [0x23, 'Ú'], [0x24, 'Ü'], [0x25, 'ü'],
|
||
[0x26, '‘'], [0x27, '¡'], [0x28, '*'], [0x29, '\''], [0x2a, '─'], [0x2b, '©'],
|
||
[0x2c, '℠'], [0x2d, '·'], [0x2e, '“'], [0x2f, '”'], [0x30, 'À'], [0x31, 'Â'],
|
||
[0x32, 'Ç'], [0x33, 'È'], [0x34, 'Ê'], [0x35, 'Ë'], [0x36, 'ë'], [0x37, 'Î'],
|
||
[0x38, 'Ï'], [0x39, 'ï'], [0x3a, 'Ô'], [0x3b, 'Ù'], [0x3c, 'ù'], [0x3d, 'Û'],
|
||
[0x3e, '«'], [0x3f, '»'],
|
||
]);
|
||
|
||
/**
|
||
* Extended Portuguese/German/Danish char set.
|
||
* @private @const {!Map<number, string>}
|
||
*/
|
||
shaka.cea.Cea608Memory.CharSet.ExtendedPortugueseGerman = new Map([
|
||
[0x20, 'Ã'], [0x21, 'ã'], [0x22, 'Í'], [0x23, 'Ì'], [0x24, 'ì'], [0x25, 'Ò'],
|
||
[0x26, 'ò'], [0x27, 'Õ'], [0x28, 'õ'], [0x29, '{'], [0x2a, '}'], [0x2b, '\\'],
|
||
[0x2c, '^'], [0x2d, '_'], [0x2e, '|'], [0x2f, '~'], [0x30, 'Ä'], [0x31, 'ä'],
|
||
[0x32, 'Ö'], [0x33, 'ö'], [0x34, 'ß'], [0x35, '¥'], [0x36, '¤'], [0x37, '│'],
|
||
[0x38, 'Å'], [0x39, 'å'], [0x3a, 'Ø'], [0x3b, 'ø'], [0x3c, '┌'], [0x3d, '┐'],
|
||
[0x3e, '└'], [0x3f, '┘'],
|
||
]);
|
||
|
||
/**
|
||
* @private @const {!Map<number, number>}
|
||
*/
|
||
shaka.cea.Cea608Memory.ROW_TO_LINE_CONVERSION_ = new Map([
|
||
[1, 10], [2, 15.33], [3, 20.66], [4, 26], [5, 31.33], [6, 36.66], [7, 42],
|
||
[8, 47.33], [9, 52.66], [10, 58], [11, 63.33], [12, 68.66], [13, 74],
|
||
[14, 79.33], [15, 84.66],
|
||
]);
|