zephyra-codec
v0.1.3
Published
Zephyra Core SDK — deterministic export-time layer preservation and byte-exact deconstruction for video files.
Maintainers
Readme
@zephyra/blackvideo-codec SDK
Standalone Node.js/TypeScript library implementing Zephyra's Exporter and Deconstructor. No UI, no host assumptions — this package only knows how to turn a base video + layer instructions into a normal, playable video file with the original source embedded inside it, and how to read that back out byte-for-byte.
Install
npm install zephyra-codec
pnpm install zephyra-codecRequires ffmpeg on PATH (or pass ffmpegPath explicitly). Node.js LTS
(>=18).
Usage
import { Exporter, Deconstructor } from "zephyra-codec";
const exporter = new Exporter();
const result = await exporter.export({
baseVideoPath: "./raw/interview.mp4",
layers: [
{
id: "lower-third",
type: "text",
startTime: 2,
endTime: 8,
transform: { x: 40, y: 400, scale: 1, rotation: 0 },
opacity: 1,
data: { text: "Jane Doe, CTO", fontsize: 28, fontcolor: "white" },
},
],
outputPath: "./out/interview.zephyra.mp4",
});
// result.outputPath is a completely normal, playable mp4.
const deconstructor = new Deconstructor();
const restored = await deconstructor.deconstruct({
inputPath: "./out/interview.zephyra.mp4",
outDir: "./restored",
});
// restored.baseVideoPath is byte-identical to ./raw/interview.mp4
// restored.timeline.layers is the original layers arrayHow it works
- Exporter renders a standard flattened video with ffmpeg, then
appends a single unknown-typed top-level box (
zeph) to the end of the ISO-BMFF (mp4) container. Standard players and demuxers iterate the box list and skip types they don't recognize — this is the same mechanism vendor metadata boxes use today, not a hack specific to this SDK. Appending at the end never shifts the byte offsets of anything already written, so nothing insidemoovneeds to be rewritten. - The
zephbox payload is the original base video's raw bytes plus the layer instructions, MessagePack-encoded and gzip-compressed. - Deconstructor never touches ffmpeg. It scans the box list, finds
zeph, decompresses and decodes it, and writes the base video bytes back out untouched. This is why restoration is byte-exact — the original bytes were never re-encoded, only carried along for the ride.
Design notes / deliberate scope decisions
- Zero runtime dependencies. The MessagePack codec
(
src/serialization/msgpack.ts) and the ffmpeg process wrapper (src/ffmpeg/ffmpegBinary.ts) are hand-rolled rather than pulled in viamsgpackr/fluent-ffmpeg. Both are wire/behavior compatible with their namesake libraries (MessagePack is a standard spec; the ffmpeg wrapper is justspawn+ arg building), so a host is free to swap them in later — but keeping the dependency tree empty removes the biggest source of friction when this package is eventually bundled into apkg/nexesidecar binary. - Core vs. I/O separation.
Exporter/Deconstructorare the only filesystem-touching pieces. Everything they call —encodePayload/decodePayload,appendBox/findTopLevelBox— is a pure function operating onBuffers in memory. These are re-exported fromsrc/index.tsspecifically so a future HTTP/WebSocket wrapper can call them directly against request bodies without going through disk. - No AI, no approximation in Deconstructor. Restoration is pure
ISO-BMFF box scanning + decompression. The round-trip test
(
test/roundtrip.test.ts) is the acceptance bar: export → deconstruct →Buffer.compareagainst the original source must be0, every time.
Scripts
npm run build # tsc -> dist/
npm test # runs the full test suite (node:test via tsx), including the round-trip test
npm run typecheck # tsc --noEmitPublic API
Exporter.export(options: ExportOptions): Promise<ExportResult>Deconstructor.deconstruct(options: DeconstructOptions): Promise<DeconstructResult>- Pure helpers:
encodePayload,decodePayload,appendBox,findTopLevelBox,readBoxPayload,listTopLevelBoxes,stripTopLevelBox,msgpackEncode,msgpackDecode - Types:
LayerInstruction,ZephyraTimeline,ZephyraPayload,ExportOptions,ExportResult,DeconstructOptions,DeconstructResult,ZephyraFormatError
See src/types.ts for full type definitions.
Out of scope (by design, per the project brief)
No UI, no Tauri, no desktop shell — this package assumes nothing about who calls it. It's meant to be imported directly by a Node host, compiled into a sidecar binary, or wrapped in an HTTP/WebSocket server, none of which are implemented here.
