mp4-inspector.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. /**
  2. * mux.js
  3. *
  4. * Copyright (c) Brightcove
  5. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  6. *
  7. * Parse the internal MP4 structure into an equivalent javascript
  8. * object.
  9. */
  10. 'use strict';
  11. var numberHelpers = require('../utils/numbers.js');
  12. var MAX_UINT32 = numberHelpers.MAX_UINT32;
  13. var getUint64 = numberHelpers.getUint64;
  14. var inspectMp4,
  15. _textifyMp,
  16. parseMp4Date = function parseMp4Date(seconds) {
  17. return new Date(seconds * 1000 - 2082844800000);
  18. },
  19. parseType = require('../mp4/parse-type'),
  20. findBox = require('../mp4/find-box'),
  21. nalParse = function nalParse(avcStream) {
  22. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  23. result = [],
  24. i,
  25. length;
  26. for (i = 0; i + 4 < avcStream.length; i += length) {
  27. length = avcView.getUint32(i);
  28. i += 4; // bail if this doesn't appear to be an H264 stream
  29. if (length <= 0) {
  30. result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
  31. continue;
  32. }
  33. switch (avcStream[i] & 0x1F) {
  34. case 0x01:
  35. result.push('slice_layer_without_partitioning_rbsp');
  36. break;
  37. case 0x05:
  38. result.push('slice_layer_without_partitioning_rbsp_idr');
  39. break;
  40. case 0x06:
  41. result.push('sei_rbsp');
  42. break;
  43. case 0x07:
  44. result.push('seq_parameter_set_rbsp');
  45. break;
  46. case 0x08:
  47. result.push('pic_parameter_set_rbsp');
  48. break;
  49. case 0x09:
  50. result.push('access_unit_delimiter_rbsp');
  51. break;
  52. default:
  53. result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
  54. break;
  55. }
  56. }
  57. return result;
  58. },
  59. // registry of handlers for individual mp4 box types
  60. parse = {
  61. // codingname, not a first-class box type. stsd entries share the
  62. // same format as real boxes so the parsing infrastructure can be
  63. // shared
  64. avc1: function avc1(data) {
  65. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  66. return {
  67. dataReferenceIndex: view.getUint16(6),
  68. width: view.getUint16(24),
  69. height: view.getUint16(26),
  70. horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
  71. vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
  72. frameCount: view.getUint16(40),
  73. depth: view.getUint16(74),
  74. config: inspectMp4(data.subarray(78, data.byteLength))
  75. };
  76. },
  77. avcC: function avcC(data) {
  78. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  79. result = {
  80. configurationVersion: data[0],
  81. avcProfileIndication: data[1],
  82. profileCompatibility: data[2],
  83. avcLevelIndication: data[3],
  84. lengthSizeMinusOne: data[4] & 0x03,
  85. sps: [],
  86. pps: []
  87. },
  88. numOfSequenceParameterSets = data[5] & 0x1f,
  89. numOfPictureParameterSets,
  90. nalSize,
  91. offset,
  92. i; // iterate past any SPSs
  93. offset = 6;
  94. for (i = 0; i < numOfSequenceParameterSets; i++) {
  95. nalSize = view.getUint16(offset);
  96. offset += 2;
  97. result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  98. offset += nalSize;
  99. } // iterate past any PPSs
  100. numOfPictureParameterSets = data[offset];
  101. offset++;
  102. for (i = 0; i < numOfPictureParameterSets; i++) {
  103. nalSize = view.getUint16(offset);
  104. offset += 2;
  105. result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  106. offset += nalSize;
  107. }
  108. return result;
  109. },
  110. btrt: function btrt(data) {
  111. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  112. return {
  113. bufferSizeDB: view.getUint32(0),
  114. maxBitrate: view.getUint32(4),
  115. avgBitrate: view.getUint32(8)
  116. };
  117. },
  118. edts: function edts(data) {
  119. return {
  120. boxes: inspectMp4(data)
  121. };
  122. },
  123. elst: function elst(data) {
  124. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  125. result = {
  126. version: view.getUint8(0),
  127. flags: new Uint8Array(data.subarray(1, 4)),
  128. edits: []
  129. },
  130. entryCount = view.getUint32(4),
  131. i;
  132. for (i = 8; entryCount; entryCount--) {
  133. if (result.version === 0) {
  134. result.edits.push({
  135. segmentDuration: view.getUint32(i),
  136. mediaTime: view.getInt32(i + 4),
  137. mediaRate: view.getUint16(i + 8) + view.getUint16(i + 10) / (256 * 256)
  138. });
  139. i += 12;
  140. } else {
  141. result.edits.push({
  142. segmentDuration: getUint64(data.subarray(i)),
  143. mediaTime: getUint64(data.subarray(i + 8)),
  144. mediaRate: view.getUint16(i + 16) + view.getUint16(i + 18) / (256 * 256)
  145. });
  146. i += 20;
  147. }
  148. }
  149. return result;
  150. },
  151. esds: function esds(data) {
  152. return {
  153. version: data[0],
  154. flags: new Uint8Array(data.subarray(1, 4)),
  155. esId: data[6] << 8 | data[7],
  156. streamPriority: data[8] & 0x1f,
  157. decoderConfig: {
  158. objectProfileIndication: data[11],
  159. streamType: data[12] >>> 2 & 0x3f,
  160. bufferSize: data[13] << 16 | data[14] << 8 | data[15],
  161. maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
  162. avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
  163. decoderConfigDescriptor: {
  164. tag: data[24],
  165. length: data[25],
  166. audioObjectType: data[26] >>> 3 & 0x1f,
  167. samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
  168. channelConfiguration: data[27] >>> 3 & 0x0f
  169. }
  170. }
  171. };
  172. },
  173. ftyp: function ftyp(data) {
  174. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  175. result = {
  176. majorBrand: parseType(data.subarray(0, 4)),
  177. minorVersion: view.getUint32(4),
  178. compatibleBrands: []
  179. },
  180. i = 8;
  181. while (i < data.byteLength) {
  182. result.compatibleBrands.push(parseType(data.subarray(i, i + 4)));
  183. i += 4;
  184. }
  185. return result;
  186. },
  187. dinf: function dinf(data) {
  188. return {
  189. boxes: inspectMp4(data)
  190. };
  191. },
  192. dref: function dref(data) {
  193. return {
  194. version: data[0],
  195. flags: new Uint8Array(data.subarray(1, 4)),
  196. dataReferences: inspectMp4(data.subarray(8))
  197. };
  198. },
  199. hdlr: function hdlr(data) {
  200. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  201. result = {
  202. version: view.getUint8(0),
  203. flags: new Uint8Array(data.subarray(1, 4)),
  204. handlerType: parseType(data.subarray(8, 12)),
  205. name: ''
  206. },
  207. i = 8; // parse out the name field
  208. for (i = 24; i < data.byteLength; i++) {
  209. if (data[i] === 0x00) {
  210. // the name field is null-terminated
  211. i++;
  212. break;
  213. }
  214. result.name += String.fromCharCode(data[i]);
  215. } // decode UTF-8 to javascript's internal representation
  216. // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
  217. result.name = decodeURIComponent(escape(result.name));
  218. return result;
  219. },
  220. mdat: function mdat(data) {
  221. return {
  222. byteLength: data.byteLength,
  223. nals: nalParse(data)
  224. };
  225. },
  226. mdhd: function mdhd(data) {
  227. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  228. i = 4,
  229. language,
  230. result = {
  231. version: view.getUint8(0),
  232. flags: new Uint8Array(data.subarray(1, 4)),
  233. language: ''
  234. };
  235. if (result.version === 1) {
  236. i += 4;
  237. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  238. i += 8;
  239. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  240. i += 4;
  241. result.timescale = view.getUint32(i);
  242. i += 8;
  243. result.duration = view.getUint32(i); // truncating top 4 bytes
  244. } else {
  245. result.creationTime = parseMp4Date(view.getUint32(i));
  246. i += 4;
  247. result.modificationTime = parseMp4Date(view.getUint32(i));
  248. i += 4;
  249. result.timescale = view.getUint32(i);
  250. i += 4;
  251. result.duration = view.getUint32(i);
  252. }
  253. i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
  254. // each field is the packed difference between its ASCII value and 0x60
  255. language = view.getUint16(i);
  256. result.language += String.fromCharCode((language >> 10) + 0x60);
  257. result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
  258. result.language += String.fromCharCode((language & 0x1f) + 0x60);
  259. return result;
  260. },
  261. mdia: function mdia(data) {
  262. return {
  263. boxes: inspectMp4(data)
  264. };
  265. },
  266. mfhd: function mfhd(data) {
  267. return {
  268. version: data[0],
  269. flags: new Uint8Array(data.subarray(1, 4)),
  270. sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  271. };
  272. },
  273. minf: function minf(data) {
  274. return {
  275. boxes: inspectMp4(data)
  276. };
  277. },
  278. // codingname, not a first-class box type. stsd entries share the
  279. // same format as real boxes so the parsing infrastructure can be
  280. // shared
  281. mp4a: function mp4a(data) {
  282. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  283. result = {
  284. // 6 bytes reserved
  285. dataReferenceIndex: view.getUint16(6),
  286. // 4 + 4 bytes reserved
  287. channelcount: view.getUint16(16),
  288. samplesize: view.getUint16(18),
  289. // 2 bytes pre_defined
  290. // 2 bytes reserved
  291. samplerate: view.getUint16(24) + view.getUint16(26) / 65536
  292. }; // if there are more bytes to process, assume this is an ISO/IEC
  293. // 14496-14 MP4AudioSampleEntry and parse the ESDBox
  294. if (data.byteLength > 28) {
  295. result.streamDescriptor = inspectMp4(data.subarray(28))[0];
  296. }
  297. return result;
  298. },
  299. moof: function moof(data) {
  300. return {
  301. boxes: inspectMp4(data)
  302. };
  303. },
  304. moov: function moov(data) {
  305. return {
  306. boxes: inspectMp4(data)
  307. };
  308. },
  309. mvex: function mvex(data) {
  310. return {
  311. boxes: inspectMp4(data)
  312. };
  313. },
  314. mvhd: function mvhd(data) {
  315. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  316. i = 4,
  317. result = {
  318. version: view.getUint8(0),
  319. flags: new Uint8Array(data.subarray(1, 4))
  320. };
  321. if (result.version === 1) {
  322. i += 4;
  323. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  324. i += 8;
  325. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  326. i += 4;
  327. result.timescale = view.getUint32(i);
  328. i += 8;
  329. result.duration = view.getUint32(i); // truncating top 4 bytes
  330. } else {
  331. result.creationTime = parseMp4Date(view.getUint32(i));
  332. i += 4;
  333. result.modificationTime = parseMp4Date(view.getUint32(i));
  334. i += 4;
  335. result.timescale = view.getUint32(i);
  336. i += 4;
  337. result.duration = view.getUint32(i);
  338. }
  339. i += 4; // convert fixed-point, base 16 back to a number
  340. result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
  341. i += 4;
  342. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  343. i += 2;
  344. i += 2;
  345. i += 2 * 4;
  346. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  347. i += 9 * 4;
  348. i += 6 * 4;
  349. result.nextTrackId = view.getUint32(i);
  350. return result;
  351. },
  352. pdin: function pdin(data) {
  353. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  354. return {
  355. version: view.getUint8(0),
  356. flags: new Uint8Array(data.subarray(1, 4)),
  357. rate: view.getUint32(4),
  358. initialDelay: view.getUint32(8)
  359. };
  360. },
  361. sdtp: function sdtp(data) {
  362. var result = {
  363. version: data[0],
  364. flags: new Uint8Array(data.subarray(1, 4)),
  365. samples: []
  366. },
  367. i;
  368. for (i = 4; i < data.byteLength; i++) {
  369. result.samples.push({
  370. dependsOn: (data[i] & 0x30) >> 4,
  371. isDependedOn: (data[i] & 0x0c) >> 2,
  372. hasRedundancy: data[i] & 0x03
  373. });
  374. }
  375. return result;
  376. },
  377. sidx: require('./parse-sidx.js'),
  378. smhd: function smhd(data) {
  379. return {
  380. version: data[0],
  381. flags: new Uint8Array(data.subarray(1, 4)),
  382. balance: data[4] + data[5] / 256
  383. };
  384. },
  385. stbl: function stbl(data) {
  386. return {
  387. boxes: inspectMp4(data)
  388. };
  389. },
  390. ctts: function ctts(data) {
  391. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  392. result = {
  393. version: view.getUint8(0),
  394. flags: new Uint8Array(data.subarray(1, 4)),
  395. compositionOffsets: []
  396. },
  397. entryCount = view.getUint32(4),
  398. i;
  399. for (i = 8; entryCount; i += 8, entryCount--) {
  400. result.compositionOffsets.push({
  401. sampleCount: view.getUint32(i),
  402. sampleOffset: view[result.version === 0 ? 'getUint32' : 'getInt32'](i + 4)
  403. });
  404. }
  405. return result;
  406. },
  407. stss: function stss(data) {
  408. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  409. result = {
  410. version: view.getUint8(0),
  411. flags: new Uint8Array(data.subarray(1, 4)),
  412. syncSamples: []
  413. },
  414. entryCount = view.getUint32(4),
  415. i;
  416. for (i = 8; entryCount; i += 4, entryCount--) {
  417. result.syncSamples.push(view.getUint32(i));
  418. }
  419. return result;
  420. },
  421. stco: function stco(data) {
  422. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  423. result = {
  424. version: data[0],
  425. flags: new Uint8Array(data.subarray(1, 4)),
  426. chunkOffsets: []
  427. },
  428. entryCount = view.getUint32(4),
  429. i;
  430. for (i = 8; entryCount; i += 4, entryCount--) {
  431. result.chunkOffsets.push(view.getUint32(i));
  432. }
  433. return result;
  434. },
  435. stsc: function stsc(data) {
  436. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  437. entryCount = view.getUint32(4),
  438. result = {
  439. version: data[0],
  440. flags: new Uint8Array(data.subarray(1, 4)),
  441. sampleToChunks: []
  442. },
  443. i;
  444. for (i = 8; entryCount; i += 12, entryCount--) {
  445. result.sampleToChunks.push({
  446. firstChunk: view.getUint32(i),
  447. samplesPerChunk: view.getUint32(i + 4),
  448. sampleDescriptionIndex: view.getUint32(i + 8)
  449. });
  450. }
  451. return result;
  452. },
  453. stsd: function stsd(data) {
  454. return {
  455. version: data[0],
  456. flags: new Uint8Array(data.subarray(1, 4)),
  457. sampleDescriptions: inspectMp4(data.subarray(8))
  458. };
  459. },
  460. stsz: function stsz(data) {
  461. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  462. result = {
  463. version: data[0],
  464. flags: new Uint8Array(data.subarray(1, 4)),
  465. sampleSize: view.getUint32(4),
  466. entries: []
  467. },
  468. i;
  469. for (i = 12; i < data.byteLength; i += 4) {
  470. result.entries.push(view.getUint32(i));
  471. }
  472. return result;
  473. },
  474. stts: function stts(data) {
  475. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  476. result = {
  477. version: data[0],
  478. flags: new Uint8Array(data.subarray(1, 4)),
  479. timeToSamples: []
  480. },
  481. entryCount = view.getUint32(4),
  482. i;
  483. for (i = 8; entryCount; i += 8, entryCount--) {
  484. result.timeToSamples.push({
  485. sampleCount: view.getUint32(i),
  486. sampleDelta: view.getUint32(i + 4)
  487. });
  488. }
  489. return result;
  490. },
  491. styp: function styp(data) {
  492. return parse.ftyp(data);
  493. },
  494. tfdt: require('./parse-tfdt.js'),
  495. tfhd: require('./parse-tfhd.js'),
  496. tkhd: function tkhd(data) {
  497. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  498. i = 4,
  499. result = {
  500. version: view.getUint8(0),
  501. flags: new Uint8Array(data.subarray(1, 4))
  502. };
  503. if (result.version === 1) {
  504. i += 4;
  505. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  506. i += 8;
  507. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  508. i += 4;
  509. result.trackId = view.getUint32(i);
  510. i += 4;
  511. i += 8;
  512. result.duration = view.getUint32(i); // truncating top 4 bytes
  513. } else {
  514. result.creationTime = parseMp4Date(view.getUint32(i));
  515. i += 4;
  516. result.modificationTime = parseMp4Date(view.getUint32(i));
  517. i += 4;
  518. result.trackId = view.getUint32(i);
  519. i += 4;
  520. i += 4;
  521. result.duration = view.getUint32(i);
  522. }
  523. i += 4;
  524. i += 2 * 4;
  525. result.layer = view.getUint16(i);
  526. i += 2;
  527. result.alternateGroup = view.getUint16(i);
  528. i += 2; // convert fixed-point, base 16 back to a number
  529. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  530. i += 2;
  531. i += 2;
  532. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  533. i += 9 * 4;
  534. result.width = view.getUint16(i) + view.getUint16(i + 2) / 65536;
  535. i += 4;
  536. result.height = view.getUint16(i) + view.getUint16(i + 2) / 65536;
  537. return result;
  538. },
  539. traf: function traf(data) {
  540. return {
  541. boxes: inspectMp4(data)
  542. };
  543. },
  544. trak: function trak(data) {
  545. return {
  546. boxes: inspectMp4(data)
  547. };
  548. },
  549. trex: function trex(data) {
  550. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  551. return {
  552. version: data[0],
  553. flags: new Uint8Array(data.subarray(1, 4)),
  554. trackId: view.getUint32(4),
  555. defaultSampleDescriptionIndex: view.getUint32(8),
  556. defaultSampleDuration: view.getUint32(12),
  557. defaultSampleSize: view.getUint32(16),
  558. sampleDependsOn: data[20] & 0x03,
  559. sampleIsDependedOn: (data[21] & 0xc0) >> 6,
  560. sampleHasRedundancy: (data[21] & 0x30) >> 4,
  561. samplePaddingValue: (data[21] & 0x0e) >> 1,
  562. sampleIsDifferenceSample: !!(data[21] & 0x01),
  563. sampleDegradationPriority: view.getUint16(22)
  564. };
  565. },
  566. trun: require('./parse-trun.js'),
  567. 'url ': function url(data) {
  568. return {
  569. version: data[0],
  570. flags: new Uint8Array(data.subarray(1, 4))
  571. };
  572. },
  573. vmhd: function vmhd(data) {
  574. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  575. return {
  576. version: data[0],
  577. flags: new Uint8Array(data.subarray(1, 4)),
  578. graphicsmode: view.getUint16(4),
  579. opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
  580. };
  581. }
  582. };
  583. /**
  584. * Return a javascript array of box objects parsed from an ISO base
  585. * media file.
  586. * @param data {Uint8Array} the binary data of the media to be inspected
  587. * @return {array} a javascript array of potentially nested box objects
  588. */
  589. inspectMp4 = function inspectMp4(data) {
  590. var i = 0,
  591. result = [],
  592. view,
  593. size,
  594. type,
  595. end,
  596. box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
  597. var ab = new ArrayBuffer(data.length);
  598. var v = new Uint8Array(ab);
  599. for (var z = 0; z < data.length; ++z) {
  600. v[z] = data[z];
  601. }
  602. view = new DataView(ab);
  603. while (i < data.byteLength) {
  604. // parse box data
  605. size = view.getUint32(i);
  606. type = parseType(data.subarray(i + 4, i + 8));
  607. end = size > 1 ? i + size : data.byteLength; // parse type-specific data
  608. box = (parse[type] || function (data) {
  609. return {
  610. data: data
  611. };
  612. })(data.subarray(i + 8, end));
  613. box.size = size;
  614. box.type = type; // store this box and move to the next
  615. result.push(box);
  616. i = end;
  617. }
  618. return result;
  619. };
  620. /**
  621. * Returns a textual representation of the javascript represtentation
  622. * of an MP4 file. You can use it as an alternative to
  623. * JSON.stringify() to compare inspected MP4s.
  624. * @param inspectedMp4 {array} the parsed array of boxes in an MP4
  625. * file
  626. * @param depth {number} (optional) the number of ancestor boxes of
  627. * the elements of inspectedMp4. Assumed to be zero if unspecified.
  628. * @return {string} a text representation of the parsed MP4
  629. */
  630. _textifyMp = function textifyMp4(inspectedMp4, depth) {
  631. var indent;
  632. depth = depth || 0;
  633. indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
  634. return inspectedMp4.map(function (box, index) {
  635. // list the box type first at the current indentation level
  636. return indent + box.type + '\n' + // the type is already included and handle child boxes separately
  637. Object.keys(box).filter(function (key) {
  638. return key !== 'type' && key !== 'boxes'; // output all the box properties
  639. }).map(function (key) {
  640. var prefix = indent + ' ' + key + ': ',
  641. value = box[key]; // print out raw bytes as hexademical
  642. if (value instanceof Uint8Array || value instanceof Uint32Array) {
  643. var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (byte) {
  644. return ' ' + ('00' + byte.toString(16)).slice(-2);
  645. }).join('').match(/.{1,24}/g);
  646. if (!bytes) {
  647. return prefix + '<>';
  648. }
  649. if (bytes.length === 1) {
  650. return prefix + '<' + bytes.join('').slice(1) + '>';
  651. }
  652. return prefix + '<\n' + bytes.map(function (line) {
  653. return indent + ' ' + line;
  654. }).join('\n') + '\n' + indent + ' >';
  655. } // stringify generic objects
  656. return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
  657. if (index === 0) {
  658. return line;
  659. }
  660. return indent + ' ' + line;
  661. }).join('\n');
  662. }).join('\n') + ( // recursively textify the child boxes
  663. box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : '');
  664. }).join('\n');
  665. };
  666. module.exports = {
  667. inspect: inspectMp4,
  668. textify: _textifyMp,
  669. parseType: parseType,
  670. findBox: findBox,
  671. parseTraf: parse.traf,
  672. parseTfdt: parse.tfdt,
  673. parseHdlr: parse.hdlr,
  674. parseTfhd: parse.tfhd,
  675. parseTrun: parse.trun,
  676. parseSidx: parse.sidx
  677. };