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