Skip to main content

mxfuse

Rust-first MXF container primitives with Python and Node.js bindings, built on a statically linked bmx core.

Essence goes in and essence comes out. A frame is the KLV payload with the key and length stripped. You can reproduce any Generic Container mapping exactly — element key, BER length, descriptor class, and the writable descriptor fields — and read that mapping back: the coding UL and the descriptor survive a round trip. Pin product info, creation date, generation UID, and package UMIDs on write. The core is synchronous: one reader or writer per thread.

npm i mxfuse
uv add mxfuse

Wheels and napi prebuilds are the supported install path. They statically link bmx, libMXF, libMXF++, expat, and uriparser. On Linux the C++ runtime (libstdc++.so.6, libgcc_s.so.1) stays dynamic; both are on the manylinux allowlist. The Rust crate is not on crates.io yet; use a git dependency (cargo add --git https://github.com/patrickhulce/mxfuse mxfuse). That is a source build and needs CMake plus a C++ toolchain. uriparser, expat, and cmake-git-version-tracking are pre-vendored, and libuuid is replaced by a shim-provided uuid_generate, so a source build does not need git at configure time, network access, or uuid-dev.

v0.1.0 is an 0.x release: the API may change. Prebuilt artifacts cover linux x64/arm64 (glibc), macOS x64/arm64, and Windows x64. musl, Windows arm64, and other triples are source-build only.

Limitations

  • OP1a, frame-wrapped only for v1. No AS-02, IMF flavour, RDD 9, D-10, Avid OP-Atom, or clip wrapping as a write target.
  • No sub-descriptors. A private mapping that needs a registered sub-descriptor set (JPEG 2000, JPEG XS, or a future JXLPictureSubDescriptor) cannot write or read those items yet.
  • Display and sampled geometry follow stored width/height on write.
  • Essence in, essence out. A frame is the KLV payload with the key and length stripped. No image codec decode or encode.
  • Synchronous core. One reader or writer per thread.
  • Flavour.SINGLE_PASS needs a known duration and a constant-bytes-per-element codec. Python and Rust can write that flavour to a pipe; Node writeMxf accepts a filesystem path only.
  • WAVE_PCM is pinned to 48 kHz in OP1a.
  • Node custom ByteSource loads the whole file. Path and buffer opens use native range I/O. Remote / S3 range reads are a Python and Rust feature.
  • bmx and libMXF are BSD-3-Clause. mxfuse is MIT; the combined binary carries both. See THIRD_PARTY_NOTICES.md.

Usage

Python

from mxfuse import open_mxf, ReadOptions, TrackKind

options = ReadOptions(read_ahead=1 << 20, cache_bytes=64 << 20)

with open("input.mxf", "rb") as f:
    with open_mxf(f, options=options) as clip:
        print(clip.edit_rate, clip.duration)
        for track in clip.tracks:
            print(track.index, track.kind, track.essence_type, track.essence_container_ul)
        clip.select(t for t in clip.tracks if t.kind is TrackKind.PICTURE)
        clip.seek(0)
        for package in clip.read(count=1):
            for frame in package.frames:
                frame.data
                frame.element_key
                frame.file_position

A custom byte source is any object that implements read, seek, tell, and optionally size. Regular files infer size via seek(0, 2). Tune read_ahead and cache_bytes for remote sources: make bench on a 61 MB synthetic OP1a sought the last picture frame in 41,007 reads (127 KB) with both off, and 3 reads (1.2 MB) with the defaults. A 1 MB window overshoots into neighbouring interleaved essence; set both to 0 for payload-level track isolation.

from mxfuse import ClipSpec, EssenceType, Flavour, TrackSpec, write_mxf

spec = ClipSpec(
    edit_rate=(25, 1),
    flavour=Flavour.DEFAULT,
    duration=len(images),
    tracks=[
        TrackSpec(EssenceType.UNC_HD_1080P),
        TrackSpec(EssenceType.WAVE_PCM, sampling_rate=48000),
    ],
)

with open("output.mxf", "wb") as f, write_mxf(f, spec) as clip:
    for image, audio in zip(images, audios):
        clip.write_unit(image, audio)

A private Generic Container mapping supplies the element key, BER length, descriptor class, and descriptor fields. On read, track.coding_ul and the picture descriptor identify the mapping.

from mxfuse import ClipSpec, DescriptorKind, EssenceType, PixelComponent, TrackSpec, write_mxf

spec = ClipSpec(
    edit_rate=(24, 1),
    duration=len(images),
    system_item=True,
    tracks=[
        TrackSpec(
            EssenceType.OPAQUE_PICTURE,
            essence_container_ul="060e2b340401010d0d01030102700100",
            coding_ul="060e2b340401010d0401020270000000",
            stored_width=4096,
            stored_height=2160,
            element_type=0x70,
            element_llen=8,
            temporal_reordering=True,
            descriptor=DescriptorKind.RGBA,
            aspect_ratio=(16, 9),
            video_line_map=(1, 0),
            pixel_layout=(
                PixelComponent(code=ord("R"), depth=32),
                PixelComponent(code=ord("G"), depth=32),
                PixelComponent(code=ord("B"), depth=32),
            ),
        ),
    ],
)

with open("output.mxf", "wb") as f, write_mxf(f, spec) as clip:
    for image in images:
        clip.write_unit(image)

Node

import { openMxf } from "mxfuse";

const clip = await openMxf("input.mxf", { readAhead: 1 << 20, cacheBytes: 64 << 20 });
const info = await clip.info();
await clip.select(info.tracks.filter((track) => track.kind === "picture"));
await clip.seek(0);
for (const package_ of await clip.read(1)) {
  for (const frame of package_.frames) {
    frame.data;
    frame.elementKey;
    frame.filePosition;
  }
}
await clip.close();

Do not share one reader across concurrent tasks. openMxf on a custom ByteSource (not a path or Uint8Array) reads the entire file into memory.

import { EssenceType, Flavour, writeMxf } from "mxfuse";

const writer = await writeMxf("output.mxf", {
  editRate: [25, 1],
  flavour: Flavour.DEFAULT,
  duration: images.length,
  tracks: [
    { essenceType: EssenceType.UNC_HD_1080P },
    { essenceType: EssenceType.WAVE_PCM, samplingRate: 48000 },
  ],
});
for (let i = 0; i < images.length; i++) {
  await writer.write(0, images[i]);
  await writer.write(1, audios[i]);
}
await writer.finish();

Rust

use mxfuse::{open_mxf, ReadOptions, TrackKind};

let file = std::fs::File::open("input.mxf")?;
let mut clip = open_mxf(file, ReadOptions::default())?;
let picture: Vec<_> = clip
    .tracks()
    .iter()
    .filter(|track| track.kind == TrackKind::Picture)
    .cloned()
    .collect();
clip.select(picture.iter())?;
clip.seek(0)?;
for package in clip.read(1)? {
    for frame in package.frames {
        let _ = (frame.data, frame.element_key, frame.file_position);
    }
}
use mxfuse::{write_mxf, ClipSpec, EssenceType, Flavour, Rational, TrackSpec};

let file = std::fs::File::create("output.mxf")?;
let spec = ClipSpec {
    edit_rate: Rational { num: 25, den: 1 },
    flavour: Flavour::DEFAULT,
    duration: Some(images.len() as i64),
    tracks: vec![
        TrackSpec::new(EssenceType::UNC_HD_1080P),
        TrackSpec {
            sampling_rate: Some(48000),
            ..TrackSpec::new(EssenceType::WAVE_PCM)
        },
    ],
    xml: vec![],
    ..ClipSpec::default()
};
let mut writer = write_mxf(file, spec)?;
for (image, audio) in images.iter().zip(audios.iter()) {
    writer.write(0, image)?;
    writer.write(1, audio)?;
}
writer.finish()?;

file_position for frame-wrapped essence points at the KLV, with kl_size giving the header length. Clip-wrapped essence points at the sample data and kl_size is 0. Clip-level XML (ST 434 / generic stream) is ClipSpec.xml on write and clip.xml on read; it is not an essence track.

Development

Prerequisites

  • Rust (stable)
  • CMake ≥ 3.12 and a C++ toolchain
  • uv
  • pnpm 9+
  • Node.js 22+

Layout

src/
├── mxfuse-sys/      # CMake build of vendored bmx + C++ shim
├── rust-mxfuse/     # Core Rust library
├── python-mxfuse/   # PyO3 extension and Python API
└── node-mxfuse/     # napi-rs extension and TypeScript API
vendor/bmx/          # ebu/bmx v1.7 source release (offline, pristine)
patches/             # applied into gitignored src/mxfuse-sys/generated/bmx

Commands

make              # build, lint, typecheck, test
make build        # build all targets
make test         # run all tests
make fixtures     # generate tests/fixtures/sample_op1a.mxf
make bench        # print read/byte costs across ReadOptions
make examples     # JPEG XL Generic Container round-trip (needs .data/4KProRes.mov)
make package      # materialize generated/bmx and list crate contents

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mxfuse-0.1.0.tar.gz (1.5 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

mxfuse-0.1.0-cp310-abi3-win_amd64.whl (1.1 MB view details)

Uploaded CPython 3.10+Windows x86-64

mxfuse-0.1.0-cp310-abi3-manylinux_2_28_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ x86-64

mxfuse-0.1.0-cp310-abi3-manylinux_2_28_aarch64.whl (1.3 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ ARM64

mxfuse-0.1.0-cp310-abi3-macosx_11_0_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.10+macOS 11.0+ x86-64

mxfuse-0.1.0-cp310-abi3-macosx_11_0_arm64.whl (1.0 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

File details

Details for the file mxfuse-0.1.0.tar.gz.

File metadata

  • Download URL: mxfuse-0.1.0.tar.gz
  • Upload date:
  • Size: 1.5 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for mxfuse-0.1.0.tar.gz
Algorithm Hash digest
SHA256 eaf6a6c3e31f6b0c67b32ab486b5fb833eec7764775f10fb4e977d46b26fb52e
MD5 26f76c6d83df1d6d3c86dd514a630c70
BLAKE2b-256 1f749949b193039dafd286282a96ff7942feb852045e5e275b8a8474eb815df7

See more details on using hashes here.

Provenance

The following attestation bundles were made for mxfuse-0.1.0.tar.gz:

Publisher: release.yml on patrickhulce/mxfuse

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mxfuse-0.1.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: mxfuse-0.1.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 1.1 MB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for mxfuse-0.1.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 ac412353cd3a6701628b927890ef91ed805069571689133b75425fce96b7ff8e
MD5 1ac7012fb3ca3cf53eccd0dc7058ed8f
BLAKE2b-256 381f5a8fab188afcfe6126161b116fa742966beb009602d3416537a062f2a727

See more details on using hashes here.

Provenance

The following attestation bundles were made for mxfuse-0.1.0-cp310-abi3-win_amd64.whl:

Publisher: release.yml on patrickhulce/mxfuse

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mxfuse-0.1.0-cp310-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for mxfuse-0.1.0-cp310-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 cc1319f7ed135d52de579875c803b1aa758de4a69396e962513cdfb178c14d1a
MD5 f73152c796a356d2675e61cf2b1a4102
BLAKE2b-256 f19fb023298d162f053a2228c09577dad73935019de684191b601b26b1dd0920

See more details on using hashes here.

Provenance

The following attestation bundles were made for mxfuse-0.1.0-cp310-abi3-manylinux_2_28_x86_64.whl:

Publisher: release.yml on patrickhulce/mxfuse

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mxfuse-0.1.0-cp310-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for mxfuse-0.1.0-cp310-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 ffa74719af118e32554ef71ed69924b658497d3eefbefc0673ed9faeea26d254
MD5 e021097cea6dd0fb67f9089e4a4a237d
BLAKE2b-256 54c0a17683bb596421f61bf2702651a67cefae70555199c4863695d2e742b129

See more details on using hashes here.

Provenance

The following attestation bundles were made for mxfuse-0.1.0-cp310-abi3-manylinux_2_28_aarch64.whl:

Publisher: release.yml on patrickhulce/mxfuse

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mxfuse-0.1.0-cp310-abi3-macosx_11_0_x86_64.whl.

File metadata

File hashes

Hashes for mxfuse-0.1.0-cp310-abi3-macosx_11_0_x86_64.whl
Algorithm Hash digest
SHA256 6101f137ae7897c9b33dd6c81722a6d7594944a6c4ccfbc789016bd89faa0919
MD5 495ad7d295920c7fe9bc29ea727a6b9e
BLAKE2b-256 2e2652d637962a16a9fb499c2be5bed628c6a406f93f3d971fdf3aefb725ff1e

See more details on using hashes here.

Provenance

The following attestation bundles were made for mxfuse-0.1.0-cp310-abi3-macosx_11_0_x86_64.whl:

Publisher: release.yml on patrickhulce/mxfuse

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mxfuse-0.1.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for mxfuse-0.1.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 336103a850a64d7c38b23ab9b65098f90cd7cda8e8462d80fe82c30e81ff1be9
MD5 7b0449cb0af00cfa4b25a797dcc64048
BLAKE2b-256 caf5e0a4a0a8af05f9949013a67645164de18030a2150d1f23bad9bbf91e572d

See more details on using hashes here.

Provenance

The following attestation bundles were made for mxfuse-0.1.0-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on patrickhulce/mxfuse

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page