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