[
  {
    "path": ".gitignore",
    "content": "node_modules\nsandbox\n"
  },
  {
    "path": ".npmignore",
    "content": "test.js\n.travis.yml\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - '6'\n  - '8'\n  - '10'\n  - '12'\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Mathias Buus\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# mp4-stream\n\nStreaming mp4 encoder and decoder\n\n```\nnpm install mp4-stream\n```\n\n[![build status](http://img.shields.io/travis/mafintosh/mp4-stream.svg?style=flat)](http://travis-ci.org/mafintosh/mp4-stream)\n\n## Usage\n\n``` js\nvar mp4 = require('mp4-stream')\nvar fs = require('fs')\n\nvar decode = mp4.decode()\n\nfs.createReadStream('video.mp4')\n  .pipe(decode)\n  .on('box', function (headers) {\n    console.log('found box (' + headers.type + ') (' + headers.length + ')')\n    if (headers.type === 'mdat') {\n      // you can get the contents as a stream\n      console.log('box has stream data (consume stream to continue)')\n      decode.stream().resume()\n    } else if (headers.type === 'moof') {\n      // you can ignore some boxes\n      decode.ignore()\n    } else {\n      // or you can fully decode them\n      decode.decode(function (box) {\n        console.log('box contents:', box)\n      })\n    }\n  }\n  })\n```\n\nAll boxes have a type thats a 4 char string with a type name.\n\n## API\n\n#### `var stream = mp4.decode()`\n\nCreate a new decoder.\n\nThe decoder is a writable stream you should write a mp4 file to. It emits the following additional events:\n\n* `on('box', headers)` - emitted when a new box is found.\n\nEach time the `box` event fires, you must call one of these three functions:\n\n* `stream.ignore()` - ignore the entire box and continue parsing after its end\n* `stream.stream()` - get a readable stream of the box contents\n* `stream.decode(callback)` - decode the box, including all childeren in the case of containers, and pass\nthe resulting box object to the callback\n\n``` js\nvar fs = require('fs')\nvar stream = mp4.decode()\n\nstream.on('box', function (headers) {\n  console.log('found new box:', headers)\n})\n\nfs.createReadStream('my-video.mp4').pipe(stream)\n```\n\n#### `var stream = mp4.encode()`\n\nCreate a new encoder.\n\nThe encoder is a readable stream you can use to generate a mp4 file. It has the following API:\n\n* `stream.box(box, [callback])` - adds a new mp4 box to the stream.\n* `var ws = stream.mediaData(size)` - helper that adds an `mdat` box. write the media content to this stream.\n* `stream.finalize()` - finalizes the mp4 stream. call this when you're done.\n\n``` js\nvar fs = require('fs')\nvar stream = mp4.encode()\n\nstream.pipe(fs.createWriteStream('my-new-video.mp4'))\n\nstream.box(anMP4Box, function (err) {\n  // box flushed\n\n  var content = stream.mediaData(lengthOfStream, function () {\n    // wrote media data\n    stream.finalize()\n  })\n\n  someContent.pipe(content)\n})\n\n```\n\n## Decode and encode a file\n\nTo decode and encode an mp4 file with this module do\n\n``` js\nvar encoder = mp4.encode()\nvar decoder = mp4.decode()\n\ndecoder.on('box', function (headers) {\n  decoder.decode(function (box) {\n    encoder.box(box, next)\n  })\n})\n\nfs.createReadStream('my-movie.mp4').pipe(decoder)\nencoder.pipe(fs.createWriteStream('my-movie-copy.mp4'))\n```\n\n## Boxes\n\nMp4 supports a wide range of boxes, implemented in\n[mp4-box-encoding](https://github.com/jhiesey/mp4-box-encoding).\n\n## License\n\nMIT\n"
  },
  {
    "path": "decode.js",
    "content": "var stream = require('readable-stream')\nvar nextEvent = require('next-event')\nvar Box = require('mp4-box-encoding')\n\nvar EMPTY = Buffer.alloc(0)\n\nclass Decoder extends stream.Writable {\n  constructor (opts) {\n    super(opts)\n\n    this.destroyed = false\n\n    this._pending = 0\n    this._missing = 0\n    this._ignoreEmpty = false\n    this._buf = null\n    this._str = null\n    this._cb = null\n    this._ondrain = null\n    this._writeBuffer = null\n    this._writeCb = null\n\n    this._ondrain = null\n    this._kick()\n  }\n\n  destroy (err) {\n    if (this.destroyed) return\n    this.destroyed = true\n    if (err) this.emit('error', err)\n    this.emit('close')\n  }\n\n  _write (data, enc, next) {\n    if (this.destroyed) return\n    var drained = !this._str || !this._str._writableState.needDrain\n\n    while (data.length && !this.destroyed) {\n      if (!this._missing && !this._ignoreEmpty) {\n        this._writeBuffer = data\n        this._writeCb = next\n        return\n      }\n\n      var consumed = data.length < this._missing ? data.length : this._missing\n      if (this._buf) data.copy(this._buf, this._buf.length - this._missing)\n      else if (this._str) drained = this._str.write(consumed === data.length ? data : data.slice(0, consumed))\n\n      this._missing -= consumed\n\n      if (!this._missing) {\n        var buf = this._buf\n        var cb = this._cb\n        var stream = this._str\n\n        this._buf = this._cb = this._str = this._ondrain = null\n        drained = true\n\n        this._ignoreEmpty = false\n        if (stream) stream.end()\n        if (cb) cb(buf)\n      }\n\n      data = consumed === data.length ? EMPTY : data.slice(consumed)\n    }\n\n    if (this._pending && !this._missing) {\n      this._writeBuffer = data\n      this._writeCb = next\n      return\n    }\n\n    if (drained) next()\n    else this._ondrain(next)\n  }\n\n  _buffer (size, cb) {\n    this._missing = size\n    this._buf = Buffer.alloc(size)\n    this._cb = cb\n  }\n\n  _stream (size, cb) {\n    this._missing = size\n    this._str = new MediaData(this)\n    this._ondrain = nextEvent(this._str, 'drain')\n    this._pending++\n    this._str.on('end', () => {\n      this._pending--\n      this._kick()\n    })\n    this._cb = cb\n    return this._str\n  }\n\n  _readBox () {\n    const bufferHeaders = (len, buf) => {\n      this._buffer(len, additionalBuf => {\n        if (buf) {\n          buf = Buffer.concat([buf, additionalBuf])\n        } else {\n          buf = additionalBuf\n        }\n        var headers = Box.readHeaders(buf)\n        if (typeof headers === 'number') {\n          bufferHeaders(headers - buf.length, buf)\n        } else {\n          this._pending++\n          this._headers = headers\n          this.emit('box', headers)\n        }\n      })\n    }\n\n    bufferHeaders(8)\n  }\n\n  stream () {\n    if (!this._headers) throw new Error('this function can only be called once after \\'box\\' is emitted')\n    var headers = this._headers\n    this._headers = null\n\n    return this._stream(headers.contentLen, () => {\n      this._pending--\n      this._kick()\n    })\n  }\n\n  decode (cb) {\n    if (!this._headers) throw new Error('this function can only be called once after \\'box\\' is emitted')\n    var headers = this._headers\n    this._headers = null\n\n    this._buffer(headers.contentLen, buf => {\n      var box = Box.decodeWithoutHeaders(headers, buf)\n      cb(box)\n      this._pending--\n      this._kick()\n    })\n  }\n\n  ignore () {\n    if (!this._headers) throw new Error('this function can only be called once after \\'box\\' is emitted')\n    var headers = this._headers\n    this._headers = null\n\n    this._missing = headers.contentLen\n    if (this._missing === 0) {\n      this._ignoreEmpty = true\n    }\n    this._cb = () => {\n      this._pending--\n      this._kick()\n    }\n  }\n\n  _kick () {\n    if (this._pending) return\n    if (!this._buf && !this._str) this._readBox()\n    if (this._writeBuffer) {\n      var next = this._writeCb\n      var buffer = this._writeBuffer\n      this._writeBuffer = null\n      this._writeCb = null\n      this._write(buffer, null, next)\n    }\n  }\n}\n\nclass MediaData extends stream.PassThrough {\n  constructor (parent) {\n    super()\n    this._parent = parent\n    this.destroyed = false\n  }\n\n  destroy (err) {\n    if (this.destroyed) return\n    this.destroyed = true\n    this._parent.destroy(err)\n    if (err) this.emit('error', err)\n    this.emit('close')\n  }\n}\n\nmodule.exports = Decoder\n"
  },
  {
    "path": "encode.js",
    "content": "var stream = require('readable-stream')\nvar Box = require('mp4-box-encoding')\nvar queueMicrotask = require('queue-microtask')\n\nfunction noop () {}\n\nclass Encoder extends stream.Readable {\n  constructor (opts) {\n    super(opts)\n\n    this.destroyed = false\n\n    this._finalized = false\n    this._reading = false\n    this._stream = null\n    this._drain = null\n    this._want = false\n\n    this._onreadable = () => {\n      if (!this._want) return\n      this._want = false\n      this._read()\n    }\n\n    this._onend = () => {\n      this._stream = null\n    }\n  }\n\n  mdat (size, cb) {\n    this.mediaData(size, cb)\n  }\n\n  mediaData (size, cb) {\n    var stream = new MediaData(this)\n    this.box({ type: 'mdat', contentLength: size, encodeBufferLen: 8, stream: stream }, cb)\n    return stream\n  }\n\n  box (box, cb) {\n    if (!cb) cb = noop\n    if (this.destroyed) return cb(new Error('Encoder is destroyed'))\n\n    var buf\n    if (box.encodeBufferLen) {\n      buf = Buffer.alloc(box.encodeBufferLen)\n    }\n    if (box.stream) {\n      box.buffer = null\n      buf = Box.encode(box, buf)\n      this.push(buf)\n      this._stream = box.stream\n      this._stream.on('readable', this._onreadable)\n      this._stream.on('end', this._onend)\n      this._stream.on('end', cb)\n      this._forward()\n    } else {\n      buf = Box.encode(box, buf)\n      var drained = this.push(buf)\n      if (drained) return queueMicrotask(cb)\n      this._drain = cb\n    }\n  }\n\n  destroy (err) {\n    if (this.destroyed) return\n    this.destroyed = true\n    if (this._stream && this._stream.destroy) this._stream.destroy()\n    this._stream = null\n    if (this._drain) {\n      var cb = this._drain\n      this._drain = null\n      cb(err)\n    }\n    if (err) this.emit('error', err)\n    this.emit('close')\n  }\n\n  finalize () {\n    this._finalized = true\n    if (!this._stream && !this._drain) {\n      this.push(null)\n    }\n  }\n\n  _forward () {\n    if (!this._stream) return\n\n    while (!this.destroyed) {\n      var buf = this._stream.read()\n\n      if (!buf) {\n        this._want = !!this._stream\n        return\n      }\n\n      if (!this.push(buf)) return\n    }\n  }\n\n  _read () {\n    if (this._reading || this.destroyed) return\n    this._reading = true\n\n    if (this._stream) this._forward()\n    if (this._drain) {\n      var drain = this._drain\n      this._drain = null\n      drain()\n    }\n\n    this._reading = false\n    if (this._finalized) {\n      this.push(null)\n    }\n  }\n}\n\nclass MediaData extends stream.PassThrough {\n  constructor (parent) {\n    super()\n    this._parent = parent\n    this.destroyed = false\n  }\n\n  destroy (err) {\n    if (this.destroyed) return\n    this.destroyed = true\n    this._parent.destroy(err)\n    if (err) this.emit('error', err)\n    this.emit('close')\n  }\n}\n\nmodule.exports = Encoder\n"
  },
  {
    "path": "index.js",
    "content": "const Decoder = require('./decode')\nconst Encoder = require('./encode')\n\nexports.decode = opts => new Decoder(opts)\nexports.encode = opts => new Encoder(opts)\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"mp4-stream\",\n  \"version\": \"3.1.3\",\n  \"description\": \"Streaming mp4 encoder and decoder\",\n  \"main\": \"index.js\",\n  \"dependencies\": {\n    \"mp4-box-encoding\": \"^1.3.0\",\n    \"next-event\": \"^1.0.0\",\n    \"queue-microtask\": \"^1.2.2\",\n    \"readable-stream\": \"^3.0.6\"\n  },\n  \"devDependencies\": {\n    \"standard\": \"^12.0.1\",\n    \"tape\": \"^4.9.1\"\n  },\n  \"scripts\": {\n    \"test\": \"standard && tape test.js\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/mafintosh/mp4-stream.git\"\n  },\n  \"author\": \"Mathias Buus (@mafintosh)\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mafintosh/mp4-stream/issues\"\n  },\n  \"homepage\": \"https://github.com/mafintosh/mp4-stream\"\n}\n"
  },
  {
    "path": "test.js",
    "content": "var tape = require('tape')\nvar mp4 = require('./')\n\ntape('all boxes are decoded', function (t) {\n  var encode = mp4.encode()\n  var decode = mp4.decode()\n\n  var count = 0\n  decode.on('box', function () {\n    if (count === 0) {\n      decode.decode(function () { })\n    } else if (count === 1) {\n      decode.stream().on('data', function () { })\n    } else {\n      decode.ignore()\n    }\n    count++\n  })\n\n  decode.on('finish', function () {\n    t.same(count, 4)\n    t.end()\n  })\n\n  for (let i = 0; i < 4; i++) {\n    encode.box({\n      type: 'ftyp',\n      brand: 'mafi',\n      brandVersion: 1\n    })\n  }\n\n  encode.finalize()\n  encode.pipe(decode)\n})\n\ntape('generates and parses', function (t) {\n  var encode = mp4.encode()\n  var decode = mp4.decode()\n\n  decode.on('box', function (headers) {\n    if (headers.type === 'ftyp') {\n      decode.decode(function (box) {\n        t.same(box.type, 'ftyp')\n        t.same(box.brand, 'mafi')\n        t.same(box.brandVersion, 1)\n      })\n    } else if (headers.type === 'mdat') {\n      t.same(headers.type, 'mdat')\n      t.same(headers.length, 8 + 11)\n      var buffer = []\n      var stream = decode.stream()\n      stream.on('data', function (data) {\n        buffer.push(data)\n      })\n      stream.on('end', function () {\n        t.same(Buffer.concat(buffer).toString(), 'hello world')\n        t.end()\n      })\n    } else {\n      t.fail('unexpected box')\n    }\n  })\n\n  encode.box({\n    type: 'ftyp',\n    brand: 'mafi',\n    brandVersion: 1\n  })\n\n  var stream = encode.mediaData(11)\n  stream.end('hello world')\n\n  encode.finalize()\n  encode.pipe(decode)\n})\n\ntape('generates and parses with decoder/encoder in between', function (t) {\n  var encode = mp4.encode()\n  var decode = mp4.decode()\n  var encode2 = mp4.encode()\n  var decode2 = mp4.decode()\n\n  decode.on('box', function (headers) {\n    if (headers.type === 'ftyp') {\n      decode.decode(function (box) {\n        t.same(box.type, 'ftyp')\n        t.same(box.brand, 'mafi')\n        t.same(box.brandVersion, 1)\n      })\n    } else if (headers.type === 'mdat') {\n      t.same(headers.type, 'mdat')\n      t.same(headers.length, 8 + 11)\n      var buffer = []\n      var stream = decode.stream()\n      stream.on('data', function (data) {\n        buffer.push(data)\n      })\n      stream.on('end', function () {\n        t.same(Buffer.concat(buffer).toString(), 'hello world')\n        t.end()\n      })\n    } else {\n      t.fail('unexpected box')\n    }\n  })\n\n  encode.box({\n    type: 'ftyp',\n    brand: 'mafi',\n    brandVersion: 1\n  })\n\n  var stream = encode.mediaData(11)\n  stream.end('hello world')\n\n  encode.finalize()\n  encode.pipe(decode2)\n  decode2.on('box', function (headers) {\n    decode2.decode(function (box) {\n      encode2.box(box)\n    })\n  })\n  decode2.on('end', function () {\n    encode2.finalize()\n  })\n  encode2.pipe(decode)\n})\n"
  }
]