mp4-to-mcap
v0.1.1
Published
Convert an MP4 video file to an MCAP file by repackaging its H.264/H.265 bitstream into foxglove.CompressedVideo messages — no re-encoding.
Readme
mp4-to-mcap
Convert an MP4 video file to an MCAP file — with no transcoding. Each
video frame is repackaged, byte-for-byte, into a foxglove.CompressedVideo message, so
the result plays back in Foxglove Studio or any other
Foxglove-schema-aware MCAP viewer.
npx mp4-to-mcap input.mp4Converting |████████████████████████████████████████| 100% | 300/300 frames | ETA: 0s
Wrote 300 h264 frames (1920x1080, 10.00s) to input.mcap (42.31 MB)Why "no transcoding"?
The video bitstream inside your MP4 is already H.264 or H.265 encoded. This tool doesn't
decode and re-encode it — it demuxes the MP4 container in pure JS (no ffmpeg, no
protoc), converts each sample from MP4's length-prefixed NAL format to the
Annex-B start-code format
foxglove.CompressedVideo expects, and writes one message per frame. The encoded pixels
never change.
On every keyframe, the track's SPS/PPS (and VPS, for H.265) parameter sets are prepended so the bitstream is self-decodable from that point — the same thing a camera-facing encoder does natively.
Install
npm install -g mp4-to-mcapOr run it once with npx mp4-to-mcap ... without installing.
Usage
mp4-to-mcap <input.mp4> [options]
Options:
-o, --output <path> output .mcap file path (default: <input> with .mcap extension)
-t, --topic <topic> MCAP topic for the video channel (default: "/camera/h264")
--frame-id <id> frame_id recorded in each CompressedVideo message (default: "")
-q, --quiet suppress the progress bar
-V, --version output the version number
-h, --help display helpAs a library
import { convertMp4ToMcap } from "mp4-to-mcap";
const result = await convertMp4ToMcap("input.mp4", "output.mcap", {
topic: "/camera/h264",
onProgress: (framesWritten, totalFrames) => {
console.log(`${framesWritten}/${totalFrames}`);
},
});Limitations
- Codecs: only H.264 (
avc1/avc3) and H.265 (hvc1/hev1) video tracks are supported — the whole point of this tool is repackaging an existing bitstream, so other codecs (VP9, AV1, ...) aren't handled. - B-frames: frames are written in decode order using each sample's decode timestamp.
For the common case (no B-frame reordering,
dts == cts) this is exactly correct. If the source uses B-frames, the CLI prints a warning — Foxglove's video panel does not support B-frame video streams (it has no lookahead), so playback may be inaccurate. This matches a documented limitation offoxglove.CompressedVideoitself. - Audio and other tracks in the source MP4 are ignored; only the first video track is converted.
- The whole input file is read into memory.
The foxglove.CompressedVideo convention
This isn't a bespoke format — it's the same schema and per-frame-message structure that
coScene's own MCAP video tooling (encoders that produce .mini.mcap files, OSS video
extraction pipelines) already uses. If you're working in that ecosystem, output from this
tool is drop-in compatible.
License
MIT
