Repository: mafintosh/mp4-stream
Branch: master
Commit: 2bdf2aad5fcc
Files: 10
Total size: 14.7 KB
Directory structure:
gitextract_vw7dh3fz/
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── decode.js
├── encode.js
├── index.js
├── package.json
└── test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
node_modules
sandbox
================================================
FILE: .npmignore
================================================
test.js
.travis.yml
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- '6'
- '8'
- '10'
- '12'
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 Mathias Buus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
# mp4-stream
Streaming mp4 encoder and decoder
```
npm install mp4-stream
```
[](http://travis-ci.org/mafintosh/mp4-stream)
## Usage
``` js
var mp4 = require('mp4-stream')
var fs = require('fs')
var decode = mp4.decode()
fs.createReadStream('video.mp4')
.pipe(decode)
.on('box', function (headers) {
console.log('found box (' + headers.type + ') (' + headers.length + ')')
if (headers.type === 'mdat') {
// you can get the contents as a stream
console.log('box has stream data (consume stream to continue)')
decode.stream().resume()
} else if (headers.type === 'moof') {
// you can ignore some boxes
decode.ignore()
} else {
// or you can fully decode them
decode.decode(function (box) {
console.log('box contents:', box)
})
}
}
})
```
All boxes have a type thats a 4 char string with a type name.
## API
#### `var stream = mp4.decode()`
Create a new decoder.
The decoder is a writable stream you should write a mp4 file to. It emits the following additional events:
* `on('box', headers)` - emitted when a new box is found.
Each time the `box` event fires, you must call one of these three functions:
* `stream.ignore()` - ignore the entire box and continue parsing after its end
* `stream.stream()` - get a readable stream of the box contents
* `stream.decode(callback)` - decode the box, including all childeren in the case of containers, and pass
the resulting box object to the callback
``` js
var fs = require('fs')
var stream = mp4.decode()
stream.on('box', function (headers) {
console.log('found new box:', headers)
})
fs.createReadStream('my-video.mp4').pipe(stream)
```
#### `var stream = mp4.encode()`
Create a new encoder.
The encoder is a readable stream you can use to generate a mp4 file. It has the following API:
* `stream.box(box, [callback])` - adds a new mp4 box to the stream.
* `var ws = stream.mediaData(size)` - helper that adds an `mdat` box. write the media content to this stream.
* `stream.finalize()` - finalizes the mp4 stream. call this when you're done.
``` js
var fs = require('fs')
var stream = mp4.encode()
stream.pipe(fs.createWriteStream('my-new-video.mp4'))
stream.box(anMP4Box, function (err) {
// box flushed
var content = stream.mediaData(lengthOfStream, function () {
// wrote media data
stream.finalize()
})
someContent.pipe(content)
})
```
## Decode and encode a file
To decode and encode an mp4 file with this module do
``` js
var encoder = mp4.encode()
var decoder = mp4.decode()
decoder.on('box', function (headers) {
decoder.decode(function (box) {
encoder.box(box, next)
})
})
fs.createReadStream('my-movie.mp4').pipe(decoder)
encoder.pipe(fs.createWriteStream('my-movie-copy.mp4'))
```
## Boxes
Mp4 supports a wide range of boxes, implemented in
[mp4-box-encoding](https://github.com/jhiesey/mp4-box-encoding).
## License
MIT
================================================
FILE: decode.js
================================================
var stream = require('readable-stream')
var nextEvent = require('next-event')
var Box = require('mp4-box-encoding')
var EMPTY = Buffer.alloc(0)
class Decoder extends stream.Writable {
constructor (opts) {
super(opts)
this.destroyed = false
this._pending = 0
this._missing = 0
this._ignoreEmpty = false
this._buf = null
this._str = null
this._cb = null
this._ondrain = null
this._writeBuffer = null
this._writeCb = null
this._ondrain = null
this._kick()
}
destroy (err) {
if (this.destroyed) return
this.destroyed = true
if (err) this.emit('error', err)
this.emit('close')
}
_write (data, enc, next) {
if (this.destroyed) return
var drained = !this._str || !this._str._writableState.needDrain
while (data.length && !this.destroyed) {
if (!this._missing && !this._ignoreEmpty) {
this._writeBuffer = data
this._writeCb = next
return
}
var consumed = data.length < this._missing ? data.length : this._missing
if (this._buf) data.copy(this._buf, this._buf.length - this._missing)
else if (this._str) drained = this._str.write(consumed === data.length ? data : data.slice(0, consumed))
this._missing -= consumed
if (!this._missing) {
var buf = this._buf
var cb = this._cb
var stream = this._str
this._buf = this._cb = this._str = this._ondrain = null
drained = true
this._ignoreEmpty = false
if (stream) stream.end()
if (cb) cb(buf)
}
data = consumed === data.length ? EMPTY : data.slice(consumed)
}
if (this._pending && !this._missing) {
this._writeBuffer = data
this._writeCb = next
return
}
if (drained) next()
else this._ondrain(next)
}
_buffer (size, cb) {
this._missing = size
this._buf = Buffer.alloc(size)
this._cb = cb
}
_stream (size, cb) {
this._missing = size
this._str = new MediaData(this)
this._ondrain = nextEvent(this._str, 'drain')
this._pending++
this._str.on('end', () => {
this._pending--
this._kick()
})
this._cb = cb
return this._str
}
_readBox () {
const bufferHeaders = (len, buf) => {
this._buffer(len, additionalBuf => {
if (buf) {
buf = Buffer.concat([buf, additionalBuf])
} else {
buf = additionalBuf
}
var headers = Box.readHeaders(buf)
if (typeof headers === 'number') {
bufferHeaders(headers - buf.length, buf)
} else {
this._pending++
this._headers = headers
this.emit('box', headers)
}
})
}
bufferHeaders(8)
}
stream () {
if (!this._headers) throw new Error('this function can only be called once after \'box\' is emitted')
var headers = this._headers
this._headers = null
return this._stream(headers.contentLen, () => {
this._pending--
this._kick()
})
}
decode (cb) {
if (!this._headers) throw new Error('this function can only be called once after \'box\' is emitted')
var headers = this._headers
this._headers = null
this._buffer(headers.contentLen, buf => {
var box = Box.decodeWithoutHeaders(headers, buf)
cb(box)
this._pending--
this._kick()
})
}
ignore () {
if (!this._headers) throw new Error('this function can only be called once after \'box\' is emitted')
var headers = this._headers
this._headers = null
this._missing = headers.contentLen
if (this._missing === 0) {
this._ignoreEmpty = true
}
this._cb = () => {
this._pending--
this._kick()
}
}
_kick () {
if (this._pending) return
if (!this._buf && !this._str) this._readBox()
if (this._writeBuffer) {
var next = this._writeCb
var buffer = this._writeBuffer
this._writeBuffer = null
this._writeCb = null
this._write(buffer, null, next)
}
}
}
class MediaData extends stream.PassThrough {
constructor (parent) {
super()
this._parent = parent
this.destroyed = false
}
destroy (err) {
if (this.destroyed) return
this.destroyed = true
this._parent.destroy(err)
if (err) this.emit('error', err)
this.emit('close')
}
}
module.exports = Decoder
================================================
FILE: encode.js
================================================
var stream = require('readable-stream')
var Box = require('mp4-box-encoding')
var queueMicrotask = require('queue-microtask')
function noop () {}
class Encoder extends stream.Readable {
constructor (opts) {
super(opts)
this.destroyed = false
this._finalized = false
this._reading = false
this._stream = null
this._drain = null
this._want = false
this._onreadable = () => {
if (!this._want) return
this._want = false
this._read()
}
this._onend = () => {
this._stream = null
}
}
mdat (size, cb) {
this.mediaData(size, cb)
}
mediaData (size, cb) {
var stream = new MediaData(this)
this.box({ type: 'mdat', contentLength: size, encodeBufferLen: 8, stream: stream }, cb)
return stream
}
box (box, cb) {
if (!cb) cb = noop
if (this.destroyed) return cb(new Error('Encoder is destroyed'))
var buf
if (box.encodeBufferLen) {
buf = Buffer.alloc(box.encodeBufferLen)
}
if (box.stream) {
box.buffer = null
buf = Box.encode(box, buf)
this.push(buf)
this._stream = box.stream
this._stream.on('readable', this._onreadable)
this._stream.on('end', this._onend)
this._stream.on('end', cb)
this._forward()
} else {
buf = Box.encode(box, buf)
var drained = this.push(buf)
if (drained) return queueMicrotask(cb)
this._drain = cb
}
}
destroy (err) {
if (this.destroyed) return
this.destroyed = true
if (this._stream && this._stream.destroy) this._stream.destroy()
this._stream = null
if (this._drain) {
var cb = this._drain
this._drain = null
cb(err)
}
if (err) this.emit('error', err)
this.emit('close')
}
finalize () {
this._finalized = true
if (!this._stream && !this._drain) {
this.push(null)
}
}
_forward () {
if (!this._stream) return
while (!this.destroyed) {
var buf = this._stream.read()
if (!buf) {
this._want = !!this._stream
return
}
if (!this.push(buf)) return
}
}
_read () {
if (this._reading || this.destroyed) return
this._reading = true
if (this._stream) this._forward()
if (this._drain) {
var drain = this._drain
this._drain = null
drain()
}
this._reading = false
if (this._finalized) {
this.push(null)
}
}
}
class MediaData extends stream.PassThrough {
constructor (parent) {
super()
this._parent = parent
this.destroyed = false
}
destroy (err) {
if (this.destroyed) return
this.destroyed = true
this._parent.destroy(err)
if (err) this.emit('error', err)
this.emit('close')
}
}
module.exports = Encoder
================================================
FILE: index.js
================================================
const Decoder = require('./decode')
const Encoder = require('./encode')
exports.decode = opts => new Decoder(opts)
exports.encode = opts => new Encoder(opts)
================================================
FILE: package.json
================================================
{
"name": "mp4-stream",
"version": "3.1.3",
"description": "Streaming mp4 encoder and decoder",
"main": "index.js",
"dependencies": {
"mp4-box-encoding": "^1.3.0",
"next-event": "^1.0.0",
"queue-microtask": "^1.2.2",
"readable-stream": "^3.0.6"
},
"devDependencies": {
"standard": "^12.0.1",
"tape": "^4.9.1"
},
"scripts": {
"test": "standard && tape test.js"
},
"repository": {
"type": "git",
"url": "https://github.com/mafintosh/mp4-stream.git"
},
"author": "Mathias Buus (@mafintosh)",
"license": "MIT",
"bugs": {
"url": "https://github.com/mafintosh/mp4-stream/issues"
},
"homepage": "https://github.com/mafintosh/mp4-stream"
}
================================================
FILE: test.js
================================================
var tape = require('tape')
var mp4 = require('./')
tape('all boxes are decoded', function (t) {
var encode = mp4.encode()
var decode = mp4.decode()
var count = 0
decode.on('box', function () {
if (count === 0) {
decode.decode(function () { })
} else if (count === 1) {
decode.stream().on('data', function () { })
} else {
decode.ignore()
}
count++
})
decode.on('finish', function () {
t.same(count, 4)
t.end()
})
for (let i = 0; i < 4; i++) {
encode.box({
type: 'ftyp',
brand: 'mafi',
brandVersion: 1
})
}
encode.finalize()
encode.pipe(decode)
})
tape('generates and parses', function (t) {
var encode = mp4.encode()
var decode = mp4.decode()
decode.on('box', function (headers) {
if (headers.type === 'ftyp') {
decode.decode(function (box) {
t.same(box.type, 'ftyp')
t.same(box.brand, 'mafi')
t.same(box.brandVersion, 1)
})
} else if (headers.type === 'mdat') {
t.same(headers.type, 'mdat')
t.same(headers.length, 8 + 11)
var buffer = []
var stream = decode.stream()
stream.on('data', function (data) {
buffer.push(data)
})
stream.on('end', function () {
t.same(Buffer.concat(buffer).toString(), 'hello world')
t.end()
})
} else {
t.fail('unexpected box')
}
})
encode.box({
type: 'ftyp',
brand: 'mafi',
brandVersion: 1
})
var stream = encode.mediaData(11)
stream.end('hello world')
encode.finalize()
encode.pipe(decode)
})
tape('generates and parses with decoder/encoder in between', function (t) {
var encode = mp4.encode()
var decode = mp4.decode()
var encode2 = mp4.encode()
var decode2 = mp4.decode()
decode.on('box', function (headers) {
if (headers.type === 'ftyp') {
decode.decode(function (box) {
t.same(box.type, 'ftyp')
t.same(box.brand, 'mafi')
t.same(box.brandVersion, 1)
})
} else if (headers.type === 'mdat') {
t.same(headers.type, 'mdat')
t.same(headers.length, 8 + 11)
var buffer = []
var stream = decode.stream()
stream.on('data', function (data) {
buffer.push(data)
})
stream.on('end', function () {
t.same(Buffer.concat(buffer).toString(), 'hello world')
t.end()
})
} else {
t.fail('unexpected box')
}
})
encode.box({
type: 'ftyp',
brand: 'mafi',
brandVersion: 1
})
var stream = encode.mediaData(11)
stream.end('hello world')
encode.finalize()
encode.pipe(decode2)
decode2.on('box', function (headers) {
decode2.decode(function (box) {
encode2.box(box)
})
})
decode2.on('end', function () {
encode2.finalize()
})
encode2.pipe(decode)
})
gitextract_vw7dh3fz/ ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── decode.js ├── encode.js ├── index.js ├── package.json └── test.js
SYMBOL INDEX (27 symbols across 2 files)
FILE: decode.js
class Decoder (line 7) | class Decoder extends stream.Writable {
method constructor (line 8) | constructor (opts) {
method destroy (line 27) | destroy (err) {
method _write (line 34) | _write (data, enc, next) {
method _buffer (line 77) | _buffer (size, cb) {
method _stream (line 83) | _stream (size, cb) {
method _readBox (line 96) | _readBox () {
method stream (line 118) | stream () {
method decode (line 129) | decode (cb) {
method ignore (line 142) | ignore () {
method _kick (line 157) | _kick () {
class MediaData (line 170) | class MediaData extends stream.PassThrough {
method constructor (line 171) | constructor (parent) {
method destroy (line 177) | destroy (err) {
FILE: encode.js
function noop (line 5) | function noop () {}
class Encoder (line 7) | class Encoder extends stream.Readable {
method constructor (line 8) | constructor (opts) {
method mdat (line 30) | mdat (size, cb) {
method mediaData (line 34) | mediaData (size, cb) {
method box (line 40) | box (box, cb) {
method destroy (line 65) | destroy (err) {
method finalize (line 79) | finalize () {
method _forward (line 86) | _forward () {
method _read (line 101) | _read () {
class MediaData (line 119) | class MediaData extends stream.PassThrough {
method constructor (line 120) | constructor (parent) {
method destroy (line 126) | destroy (err) {
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (16K chars).
[
{
"path": ".gitignore",
"chars": 21,
"preview": "node_modules\nsandbox\n"
},
{
"path": ".npmignore",
"chars": 20,
"preview": "test.js\n.travis.yml\n"
},
{
"path": ".travis.yml",
"chars": 61,
"preview": "language: node_js\nnode_js:\n - '6'\n - '8'\n - '10'\n - '12'\n"
},
{
"path": "LICENSE",
"chars": 1079,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Mathias Buus\n\nPermission is hereby granted, free of charge, to any person obta"
},
{
"path": "README.md",
"chars": 3005,
"preview": "# mp4-stream\n\nStreaming mp4 encoder and decoder\n\n```\nnpm install mp4-stream\n```\n\n[\nvar nextEvent = require('next-event')\nvar Box = require('mp4-box-encoding')\n\nvar"
},
{
"path": "encode.js",
"chars": 2765,
"preview": "var stream = require('readable-stream')\nvar Box = require('mp4-box-encoding')\nvar queueMicrotask = require('queue-microt"
},
{
"path": "index.js",
"chars": 159,
"preview": "const Decoder = require('./decode')\nconst Encoder = require('./encode')\n\nexports.decode = opts => new Decoder(opts)\nexpo"
},
{
"path": "package.json",
"chars": 706,
"preview": "{\n \"name\": \"mp4-stream\",\n \"version\": \"3.1.3\",\n \"description\": \"Streaming mp4 encoder and decoder\",\n \"main\": \"index.j"
},
{
"path": "test.js",
"chars": 2822,
"preview": "var tape = require('tape')\nvar mp4 = require('./')\n\ntape('all boxes are decoded', function (t) {\n var encode = mp4.enco"
}
]
About this extraction
This page contains the full source code of the mafintosh/mp4-stream GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (14.7 KB), approximately 4.3k tokens, and a symbol index with 27 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.