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('shaka.text.TextEngine');
  8. goog.require('shaka.text.TtmlTextParser');
  9. goog.require('shaka.util.BufferUtils');
  10. goog.require('shaka.util.Error');
  11. goog.require('shaka.util.Mp4Parser');
  12. goog.require('shaka.util.Uint8ArrayUtils');
  13. /**
  14. * @implements {shaka.extern.TextParser}
  15. * @export
  16. */
  17. shaka.text.Mp4TtmlParser = class {
  18. /** */
  19. constructor() {
  20. /**
  21. * @type {!shaka.extern.TextParser}
  22. * @private
  23. */
  24. this.parser_ = new shaka.text.TtmlTextParser();
  25. }
  26. /**
  27. * @override
  28. * @export
  29. */
  30. parseInit(data) {
  31. const Mp4Parser = shaka.util.Mp4Parser;
  32. let sawSTPP = false;
  33. new Mp4Parser()
  34. .box('moov', Mp4Parser.children)
  35. .box('trak', Mp4Parser.children)
  36. .box('mdia', Mp4Parser.children)
  37. .box('minf', Mp4Parser.children)
  38. .box('stbl', Mp4Parser.children)
  39. .fullBox('stsd', Mp4Parser.sampleDescription)
  40. .box('stpp', (box) => {
  41. sawSTPP = true;
  42. box.parser.stop();
  43. }).parse(data);
  44. if (!sawSTPP) {
  45. throw new shaka.util.Error(
  46. shaka.util.Error.Severity.CRITICAL,
  47. shaka.util.Error.Category.TEXT,
  48. shaka.util.Error.Code.INVALID_MP4_TTML);
  49. }
  50. }
  51. /**
  52. * @override
  53. * @export
  54. */
  55. setSequenceMode(sequenceMode) {
  56. // Unused.
  57. }
  58. /**
  59. * @override
  60. * @export
  61. */
  62. setManifestType(manifestType) {
  63. // Unused.
  64. }
  65. /**
  66. * @override
  67. * @export
  68. */
  69. parseMedia(data, time, uri) {
  70. const Mp4Parser = shaka.util.Mp4Parser;
  71. let sawMDAT = false;
  72. let payload = [];
  73. /** @type {!Array.<number>} */
  74. let subSizes = [];
  75. const parser = new Mp4Parser()
  76. .box('moof', Mp4Parser.children)
  77. .box('traf', Mp4Parser.children)
  78. .fullBox('subs', (box) => {
  79. subSizes = [];
  80. const reader = box.reader;
  81. const entryCount = reader.readUint32();
  82. for (let i = 0; i < entryCount; i++) {
  83. reader.readUint32(); // sample_delta
  84. const subsampleCount = reader.readUint16();
  85. for (let j = 0; j < subsampleCount; j++) {
  86. if (box.version == 1) {
  87. subSizes.push(reader.readUint32());
  88. } else {
  89. subSizes.push(reader.readUint16());
  90. }
  91. reader.readUint8(); // priority
  92. reader.readUint8(); // discardable
  93. reader.readUint32(); // codec_specific_parameters
  94. }
  95. }
  96. })
  97. .box('mdat', Mp4Parser.allData((data) => {
  98. sawMDAT = true;
  99. // Join this to any previous payload, in case the mp4 has multiple
  100. // mdats.
  101. if (subSizes.length) {
  102. const contentData =
  103. shaka.util.BufferUtils.toUint8(data, 0, subSizes[0]);
  104. const images = [];
  105. let offset = subSizes[0];
  106. for (let i = 1; i < subSizes.length; i++) {
  107. const imageData =
  108. shaka.util.BufferUtils.toUint8(data, offset, subSizes[i]);
  109. const raw =
  110. shaka.util.Uint8ArrayUtils.toStandardBase64(imageData);
  111. images.push('data:image/png;base64,' + raw);
  112. offset += subSizes[i];
  113. }
  114. payload = payload.concat(
  115. this.parser_.parseMedia(contentData, time, uri, images));
  116. } else {
  117. payload = payload.concat(
  118. this.parser_.parseMedia(data, time, uri, /* images= */ []));
  119. }
  120. }));
  121. parser.parse(data, /* partialOkay= */ false);
  122. if (!sawMDAT) {
  123. throw new shaka.util.Error(
  124. shaka.util.Error.Severity.CRITICAL,
  125. shaka.util.Error.Category.TEXT,
  126. shaka.util.Error.Code.INVALID_MP4_TTML);
  127. }
  128. return payload;
  129. }
  130. };
  131. shaka.text.TextEngine.registerParser(
  132. 'application/mp4; codecs="stpp"', () => new shaka.text.Mp4TtmlParser());
  133. shaka.text.TextEngine.registerParser(
  134. 'application/mp4; codecs="stpp.ttml"',
  135. () => new shaka.text.Mp4TtmlParser());
  136. shaka.text.TextEngine.registerParser(
  137. 'application/mp4; codecs="stpp.ttml.im1i"',
  138. () => new shaka.text.Mp4TtmlParser());
  139. shaka.text.TextEngine.registerParser(
  140. 'application/mp4; codecs="stpp.ttml.im1t"',
  141. () => new shaka.text.Mp4TtmlParser());
  142. shaka.text.TextEngine.registerParser(
  143. 'application/mp4; codecs="stpp.ttml.im2i"',
  144. () => new shaka.text.Mp4TtmlParser());
  145. shaka.text.TextEngine.registerParser(
  146. 'application/mp4; codecs="stpp.ttml.im2t"',
  147. () => new shaka.text.Mp4TtmlParser());
  148. shaka.text.TextEngine.registerParser(
  149. 'application/mp4; codecs="stpp.ttml.etd1"',
  150. () => new shaka.text.Mp4TtmlParser());
  151. shaka.text.TextEngine.registerParser(
  152. 'application/mp4; codecs="stpp.ttml.etd1|im1t"',
  153. () => new shaka.text.Mp4TtmlParser());
  154. shaka.text.TextEngine.registerParser(
  155. 'application/mp4; codecs="stpp.ttml.im1t|etd1"',
  156. () => new shaka.text.Mp4TtmlParser());
  157. // Legacy codec string uses capital "TTML", i.e.: prior to HLS rfc8216bis:
  158. // Note that if a Variant Stream specifies one or more Renditions that
  159. // include IMSC subtitles, the CODECS attribute MUST indicate this with a
  160. // format identifier such as "stpp.ttml.im1t".
  161. // (https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.5.2)
  162. shaka.text.TextEngine.registerParser(
  163. 'application/mp4; codecs="stpp.TTML.im1t"',
  164. () => new shaka.text.Mp4TtmlParser());