Source: lib/cea/cea608_memory.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.cea.Cea608Memory');
  7. goog.require('shaka.cea.CeaUtils');
  8. goog.require('shaka.text.Cue');
  9. /**
  10. * CEA-608 captions memory/buffer.
  11. */
  12. shaka.cea.Cea608Memory = class {
  13. /**
  14. * @param {number} fieldNum Field number.
  15. * @param {number} channelNum Channel number.
  16. */
  17. constructor(fieldNum, channelNum) {
  18. /**
  19. * Buffer for storing decoded characters.
  20. * @private @const {!Array<!Array<!shaka.cea.CeaUtils.StyledChar>>}
  21. */
  22. this.rows_ = [];
  23. /**
  24. * Current row.
  25. * @private {number}
  26. */
  27. this.row_ = 1;
  28. /**
  29. * Number of rows in the scroll window. Used for rollup mode.
  30. * @private {number}
  31. */
  32. this.scrollRows_ = 0;
  33. /**
  34. * Field number.
  35. * @private {number}
  36. */
  37. this.fieldNum_ = fieldNum;
  38. /**
  39. * Channel number.
  40. * @private {number}
  41. */
  42. this.channelNum_ = channelNum;
  43. /**
  44. * @private {boolean}
  45. */
  46. this.underline_ = false;
  47. /**
  48. * @private {boolean}
  49. */
  50. this.italics_ = false;
  51. /**
  52. * @private {string}
  53. */
  54. this.textColor_ = shaka.cea.CeaUtils.DEFAULT_TXT_COLOR;
  55. /**
  56. * @private {string}
  57. */
  58. this.backgroundColor_ = shaka.cea.CeaUtils.DEFAULT_BG_COLOR;
  59. this.reset();
  60. }
  61. /**
  62. * Emits a closed caption based on the state of the buffer.
  63. * @param {number} startTime Start time of the cue.
  64. * @param {number} endTime End time of the cue.
  65. * @return {?shaka.extern.ICaptionDecoder.ClosedCaption}
  66. */
  67. forceEmit(startTime, endTime) {
  68. const Cea608Memory = shaka.cea.Cea608Memory;
  69. const stream = `CC${((this.fieldNum_<< 1) | this.channelNum_) + 1}`;
  70. const topLevelCue = new shaka.text.Cue(
  71. startTime, endTime, /* payload= */ '');
  72. topLevelCue.lineInterpretation =
  73. shaka.text.Cue.lineInterpretation.PERCENTAGE;
  74. let line = Cea608Memory.ROW_TO_LINE_CONVERSION_.get(this.row_);
  75. if (line) {
  76. topLevelCue.line = line;
  77. }
  78. const ret = shaka.cea.CeaUtils.getParsedCaption(
  79. topLevelCue, stream, this.rows_, startTime, endTime);
  80. // If the text and its lines are larger than what we can show on the
  81. // screen, we move the lines up so that the text does not come out of the
  82. // video.
  83. if (ret && (this.row_ + ret.cue.nestedCues.length - 3) > 15) {
  84. const newLinePosition = this.row_ + 3 - ret.cue.nestedCues.length;
  85. line = Cea608Memory.ROW_TO_LINE_CONVERSION_.get(newLinePosition);
  86. if (line) {
  87. topLevelCue.line = line;
  88. }
  89. }
  90. return ret;
  91. }
  92. /**
  93. * Resets the memory buffer.
  94. */
  95. reset() {
  96. this.resetAllRows();
  97. this.row_ = 1;
  98. }
  99. /**
  100. * @return {number}
  101. */
  102. getRow() {
  103. return this.row_;
  104. }
  105. /**
  106. * @param {number} row
  107. */
  108. setRow(row) {
  109. this.row_ = row;
  110. }
  111. /**
  112. * @return {number}
  113. */
  114. getScrollSize() {
  115. return this.scrollRows_;
  116. }
  117. /**
  118. * @param {number} scrollRows
  119. */
  120. setScrollSize(scrollRows) {
  121. this.scrollRows_ = scrollRows;
  122. }
  123. /**
  124. * Adds a character to the buffer.
  125. * @param {!shaka.cea.Cea608Memory.CharSet} set Character set.
  126. * @param {number} b CC byte to add.
  127. */
  128. addChar(set, b) {
  129. // Valid chars are in the range [0x20, 0x7f]
  130. if (b < 0x20 || b > 0x7f) {
  131. return;
  132. }
  133. let char = '';
  134. switch (set) {
  135. case shaka.cea.Cea608Memory.CharSet.BASIC_NORTH_AMERICAN:
  136. if (shaka.cea.Cea608Memory.CharSet.BasicNorthAmericanChars.has(b)) {
  137. char =
  138. shaka.cea.Cea608Memory.CharSet.BasicNorthAmericanChars.get(b);
  139. } else {
  140. // Regular ASCII
  141. char = String.fromCharCode(b);
  142. }
  143. break;
  144. case shaka.cea.Cea608Memory.CharSet.SPECIAL_NORTH_AMERICAN:
  145. char =
  146. shaka.cea.Cea608Memory.CharSet.SpecialNorthAmericanChars.get(b);
  147. break;
  148. case shaka.cea.Cea608Memory.CharSet.SPANISH_FRENCH:
  149. // Extended charset does a BS over preceding char, 6.4.2 EIA-608-B.
  150. this.eraseChar();
  151. char =
  152. shaka.cea.Cea608Memory.CharSet.ExtendedSpanishFrench.get(b);
  153. break;
  154. case shaka.cea.Cea608Memory.CharSet.PORTUGUESE_GERMAN:
  155. this.eraseChar();
  156. char =
  157. shaka.cea.Cea608Memory.CharSet.ExtendedPortugueseGerman.get(b);
  158. break;
  159. }
  160. if (char) {
  161. const styledChar = new shaka.cea.CeaUtils.StyledChar(
  162. char, this.underline_, this.italics_,
  163. this.backgroundColor_, this.textColor_);
  164. this.rows_[this.row_].push(styledChar);
  165. }
  166. }
  167. /**
  168. * Erases a character from the buffer.
  169. */
  170. eraseChar() {
  171. this.rows_[this.row_].pop();
  172. }
  173. /**
  174. * Moves rows of characters.
  175. * @param {number} dst Destination row index.
  176. * @param {number} src Source row index.
  177. * @param {number} count Count of rows to move.
  178. */
  179. moveRows(dst, src, count) {
  180. if (src < 0 || dst < 0) {
  181. return;
  182. }
  183. if (dst >= src) {
  184. for (let i = count-1; i >= 0; i--) {
  185. this.rows_[dst + i] = this.rows_[src + i].map((e) => e);
  186. }
  187. } else {
  188. for (let i = 0; i < count; i++) {
  189. this.rows_[dst + i] = this.rows_[src + i].map((e) => e);
  190. }
  191. }
  192. }
  193. /**
  194. * Resets rows of characters.
  195. * @param {number} idx Starting index.
  196. * @param {number} count Count of rows to reset.
  197. */
  198. resetRows(idx, count) {
  199. for (let i = 0; i <= count; i++) {
  200. this.rows_[idx + i] = [];
  201. }
  202. }
  203. /**
  204. * Resets the entire memory buffer.
  205. */
  206. resetAllRows() {
  207. this.resetRows(0, shaka.cea.Cea608Memory.CC_ROWS);
  208. }
  209. /**
  210. * Erases entire memory buffer.
  211. * Doesn't change scroll state or number of rows.
  212. */
  213. eraseBuffer() {
  214. this.row_ = (this.scrollRows_ > 0) ? this.scrollRows_ : 0;
  215. this.resetAllRows();
  216. }
  217. /**
  218. * @param {boolean} underline
  219. */
  220. setUnderline(underline) {
  221. this.underline_ = underline;
  222. }
  223. /**
  224. * @param {boolean} italics
  225. */
  226. setItalics(italics) {
  227. this.italics_ = italics;
  228. }
  229. /**
  230. * @param {string} color
  231. */
  232. setTextColor(color) {
  233. this.textColor_ = color;
  234. }
  235. /**
  236. * @param {string} color
  237. */
  238. setBackgroundColor(color) {
  239. this.backgroundColor_ = color;
  240. }
  241. };
  242. /**
  243. * Maximum number of rows in the buffer.
  244. * @const {number}
  245. */
  246. shaka.cea.Cea608Memory.CC_ROWS = 15;
  247. /**
  248. * Characters sets.
  249. * @const @enum {number}
  250. */
  251. shaka.cea.Cea608Memory.CharSet = {
  252. BASIC_NORTH_AMERICAN: 0,
  253. SPECIAL_NORTH_AMERICAN: 1,
  254. SPANISH_FRENCH: 2,
  255. PORTUGUESE_GERMAN: 3,
  256. };
  257. /**
  258. * Basic North American char set deviates from ASCII with these exceptions.
  259. * @private @const {!Map<number, string>}
  260. */
  261. shaka.cea.Cea608Memory.CharSet.BasicNorthAmericanChars = new Map([
  262. [0x27, '’'], [0x2a, 'á'], [0x5c, 'é'], [0x5c, 'é'], [0x5e, 'í'], [0x5f, 'ó'],
  263. [0x60, 'ú'], [0x7b, 'ç'], [0x7c, '÷'], [0x7d, 'Ñ'], [0x7e, 'ñ'], [0x7f, '█'],
  264. ]);
  265. /**
  266. * Special North American char set.
  267. * Note: Transparent Space is currently implemented as a regular space.
  268. * @private @const {!Map<number, string>}
  269. */
  270. shaka.cea.Cea608Memory.CharSet.SpecialNorthAmericanChars = new Map([
  271. [0x30, '®'], [0x31, '°'], [0x32, '½'], [0x33, '¿'], [0x34, '™'], [0x35, '¢'],
  272. [0x36, '£'], [0x37, '♪'], [0x38, 'à'], [0x39, ' '], [0x3a, 'è'], [0x3b, 'â'],
  273. [0x3c, 'ê'], [0x3d, 'î'], [0x3e, 'ô'], [0x3f, 'û'],
  274. ]);
  275. /**
  276. * Extended Spanish/Misc/French char set.
  277. * @private @const {!Map<number, string>}
  278. */
  279. shaka.cea.Cea608Memory.CharSet.ExtendedSpanishFrench = new Map([
  280. [0x20, 'Á'], [0x21, 'É'], [0x22, 'Ó'], [0x23, 'Ú'], [0x24, 'Ü'], [0x25, 'ü'],
  281. [0x26, '‘'], [0x27, '¡'], [0x28, '*'], [0x29, '\''], [0x2a, '─'], [0x2b, '©'],
  282. [0x2c, '℠'], [0x2d, '·'], [0x2e, '“'], [0x2f, '”'], [0x30, 'À'], [0x31, 'Â'],
  283. [0x32, 'Ç'], [0x33, 'È'], [0x34, 'Ê'], [0x35, 'Ë'], [0x36, 'ë'], [0x37, 'Î'],
  284. [0x38, 'Ï'], [0x39, 'ï'], [0x3a, 'Ô'], [0x3b, 'Ù'], [0x3c, 'ù'], [0x3d, 'Û'],
  285. [0x3e, '«'], [0x3f, '»'],
  286. ]);
  287. /**
  288. * Extended Portuguese/German/Danish char set.
  289. * @private @const {!Map<number, string>}
  290. */
  291. shaka.cea.Cea608Memory.CharSet.ExtendedPortugueseGerman = new Map([
  292. [0x20, 'Ã'], [0x21, 'ã'], [0x22, 'Í'], [0x23, 'Ì'], [0x24, 'ì'], [0x25, 'Ò'],
  293. [0x26, 'ò'], [0x27, 'Õ'], [0x28, 'õ'], [0x29, '{'], [0x2a, '}'], [0x2b, '\\'],
  294. [0x2c, '^'], [0x2d, '_'], [0x2e, '|'], [0x2f, '~'], [0x30, 'Ä'], [0x31, 'ä'],
  295. [0x32, 'Ö'], [0x33, 'ö'], [0x34, 'ß'], [0x35, '¥'], [0x36, '¤'], [0x37, '│'],
  296. [0x38, 'Å'], [0x39, 'å'], [0x3a, 'Ø'], [0x3b, 'ø'], [0x3c, '┌'], [0x3d, '┐'],
  297. [0x3e, '└'], [0x3f, '┘'],
  298. ]);
  299. /**
  300. * @private @const {!Map<number, number>}
  301. */
  302. shaka.cea.Cea608Memory.ROW_TO_LINE_CONVERSION_ = new Map([
  303. [1, 10], [2, 15.33], [3, 20.66], [4, 26], [5, 31.33], [6, 36.66], [7, 42],
  304. [8, 47.33], [9, 52.66], [10, 58], [11, 63.33], [12, 68.66], [13, 74],
  305. [14, 79.33], [15, 84.66],
  306. ]);