Source: lib/text/mp4_ttml_parser.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.text.Mp4TtmlParser');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.text.TextEngine');
  9. goog.require('shaka.text.TtmlTextParser');
  10. goog.require('shaka.util.BufferUtils');
  11. goog.require('shaka.util.Error');
  12. goog.require('shaka.util.Mp4BoxParsers');
  13. goog.require('shaka.util.Mp4Parser');
  14. goog.require('shaka.util.Uint8ArrayUtils');
  15. /**
  16. * @implements {shaka.extern.TextParser}
  17. * @export
  18. */
  19. shaka.text.Mp4TtmlParser = class {
  20. /** */
  21. constructor() {
  22. /**
  23. * @type {!shaka.extern.TextParser}
  24. * @private
  25. */
  26. this.parser_ = new shaka.text.TtmlTextParser();
  27. }
  28. /**
  29. * @override
  30. * @export
  31. */
  32. parseInit(data) {
  33. const Mp4Parser = shaka.util.Mp4Parser;
  34. let sawSTPP = false;
  35. new Mp4Parser()
  36. .box('moov', Mp4Parser.children)
  37. .box('trak', Mp4Parser.children)
  38. .box('mdia', Mp4Parser.children)
  39. .box('minf', Mp4Parser.children)
  40. .box('stbl', Mp4Parser.children)
  41. .fullBox('stsd', Mp4Parser.sampleDescription)
  42. .box('stpp', (box) => {
  43. sawSTPP = true;
  44. box.parser.stop();
  45. }).parse(data);
  46. if (!sawSTPP) {
  47. throw new shaka.util.Error(
  48. shaka.util.Error.Severity.CRITICAL,
  49. shaka.util.Error.Category.TEXT,
  50. shaka.util.Error.Code.INVALID_MP4_TTML);
  51. }
  52. }
  53. /**
  54. * @override
  55. * @export
  56. */
  57. setSequenceMode(sequenceMode) {
  58. // Unused.
  59. }
  60. /**
  61. * @override
  62. * @export
  63. */
  64. setManifestType(manifestType) {
  65. // Unused.
  66. }
  67. /**
  68. * @override
  69. * @export
  70. */
  71. parseMedia(data, time, uri) {
  72. const Mp4Parser = shaka.util.Mp4Parser;
  73. let payload = [];
  74. let defaultSampleSize = null;
  75. /** @type {!Array<Uint8Array>} */
  76. const mdats = [];
  77. /* @type {!Map<number,!Array<number>>} */
  78. const subSampleSizesPerSample = new Map();
  79. /** @type {!Array<number>} */
  80. const sampleSizes = [];
  81. const parser = new Mp4Parser()
  82. .box('moof', Mp4Parser.children)
  83. .box('traf', Mp4Parser.children)
  84. .fullBox('tfhd', (box) => {
  85. goog.asserts.assert(
  86. box.flags != null,
  87. 'A TFHD box should have a valid flags value');
  88. const parsedTFHDBox = shaka.util.Mp4BoxParsers.parseTFHD(
  89. box.reader, box.flags);
  90. defaultSampleSize = parsedTFHDBox.defaultSampleSize;
  91. })
  92. .fullBox('trun', (box) => {
  93. goog.asserts.assert(
  94. box.version != null,
  95. 'A TRUN box should have a valid version value');
  96. goog.asserts.assert(
  97. box.flags != null,
  98. 'A TRUN box should have a valid flags value');
  99. const parsedTRUNBox = shaka.util.Mp4BoxParsers.parseTRUN(
  100. box.reader, box.version, box.flags);
  101. for (const sample of parsedTRUNBox.sampleData) {
  102. const sampleSize =
  103. sample.sampleSize || defaultSampleSize || 0;
  104. sampleSizes.push(sampleSize);
  105. }
  106. })
  107. .fullBox('subs', (box) => {
  108. const reader = box.reader;
  109. const entryCount = reader.readUint32();
  110. let currentSampleNum = -1;
  111. for (let i = 0; i < entryCount; i++) {
  112. const sampleDelta = reader.readUint32();
  113. currentSampleNum += sampleDelta;
  114. const subsampleCount = reader.readUint16();
  115. const subsampleSizes = [];
  116. for (let j = 0; j < subsampleCount; j++) {
  117. if (box.version == 1) {
  118. subsampleSizes.push(reader.readUint32());
  119. } else {
  120. subsampleSizes.push(reader.readUint16());
  121. }
  122. reader.readUint8(); // priority
  123. reader.readUint8(); // discardable
  124. reader.readUint32(); // codec_specific_parameters
  125. }
  126. subSampleSizesPerSample.set(currentSampleNum, subsampleSizes);
  127. }
  128. })
  129. .box('mdat', Mp4Parser.allData((data) => {
  130. // We collect all of the mdats first, before parsing any of them.
  131. // This is necessary in case the mp4 has multiple mdats.
  132. mdats.push(data);
  133. }));
  134. parser.parse(data, /* partialOkay= */ false);
  135. if (mdats.length == 0) {
  136. throw new shaka.util.Error(
  137. shaka.util.Error.Severity.CRITICAL,
  138. shaka.util.Error.Category.TEXT,
  139. shaka.util.Error.Code.INVALID_MP4_TTML);
  140. }
  141. const fullData =
  142. shaka.util.Uint8ArrayUtils.concat(...mdats);
  143. let sampleOffset = 0;
  144. for (let sampleNum = 0; sampleNum < sampleSizes.length; sampleNum++) {
  145. const sampleData =
  146. shaka.util.BufferUtils.toUint8(fullData, sampleOffset,
  147. sampleSizes[sampleNum]);
  148. sampleOffset += sampleSizes[sampleNum];
  149. const subSampleSizes = subSampleSizesPerSample.get(sampleNum);
  150. if (subSampleSizes && subSampleSizes.length) {
  151. const contentData =
  152. shaka.util.BufferUtils.toUint8(sampleData, 0, subSampleSizes[0]);
  153. const images = [];
  154. let subOffset = subSampleSizes[0];
  155. for (let i = 1; i < subSampleSizes.length; i++) {
  156. const imageData =
  157. shaka.util.BufferUtils.toUint8(data, subOffset,
  158. subSampleSizes[i]);
  159. const raw =
  160. shaka.util.Uint8ArrayUtils.toStandardBase64(imageData);
  161. images.push('data:image/png;base64,' + raw);
  162. subOffset += subSampleSizes[i];
  163. }
  164. payload = payload.concat(
  165. this.parser_.parseMedia(contentData, time, uri, images));
  166. } else {
  167. payload = payload.concat(
  168. this.parser_.parseMedia(sampleData, time, uri,
  169. /* images= */ []));
  170. }
  171. }
  172. return payload;
  173. }
  174. };
  175. shaka.text.TextEngine.registerParser(
  176. 'application/mp4; codecs="stpp"', () => new shaka.text.Mp4TtmlParser());
  177. shaka.text.TextEngine.registerParser(
  178. 'application/mp4; codecs="stpp.ttml"',
  179. () => new shaka.text.Mp4TtmlParser());
  180. shaka.text.TextEngine.registerParser(
  181. 'application/mp4; codecs="stpp.ttml.im1i"',
  182. () => new shaka.text.Mp4TtmlParser());
  183. shaka.text.TextEngine.registerParser(
  184. 'application/mp4; codecs="stpp.ttml.im1t"',
  185. () => new shaka.text.Mp4TtmlParser());
  186. shaka.text.TextEngine.registerParser(
  187. 'application/mp4; codecs="stpp.ttml.im2i"',
  188. () => new shaka.text.Mp4TtmlParser());
  189. shaka.text.TextEngine.registerParser(
  190. 'application/mp4; codecs="stpp.ttml.im2t"',
  191. () => new shaka.text.Mp4TtmlParser());
  192. shaka.text.TextEngine.registerParser(
  193. 'application/mp4; codecs="stpp.ttml.etd1"',
  194. () => new shaka.text.Mp4TtmlParser());
  195. shaka.text.TextEngine.registerParser(
  196. 'application/mp4; codecs="stpp.ttml.etd1|im1t"',
  197. () => new shaka.text.Mp4TtmlParser());
  198. shaka.text.TextEngine.registerParser(
  199. 'application/mp4; codecs="stpp.ttml.im1t|etd1"',
  200. () => new shaka.text.Mp4TtmlParser());
  201. // Legacy codec string uses capital "TTML", i.e.: prior to HLS rfc8216bis:
  202. // Note that if a Variant Stream specifies one or more Renditions that
  203. // include IMSC subtitles, the CODECS attribute MUST indicate this with a
  204. // format identifier such as "stpp.ttml.im1t".
  205. // (https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.5.2)
  206. shaka.text.TextEngine.registerParser(
  207. 'application/mp4; codecs="stpp.TTML.im1t"',
  208. () => new shaka.text.Mp4TtmlParser());