habemus-papadum-vtenc (import pdum.vtenc)
macOS host NV12 → H.264 Annex B via Apple's VideoToolbox (VTCompressionSession),
with no PyAV and no ffmpeg. The companion encoder for
pdum.rfb (PyPI: habemus-papadum-rfb) on Apple Silicon — the
counterpart of habemus-papadum-nvenc on NVIDIA. A uv workspace
member of this repo. Design notes:
docs/mlx_metal_videotoolbox_encoder_design.md.
Why it exists:
- Hardware H.264 on macOS without PyAV. VideoToolbox is the Apple-Silicon hardware encoder; this binds it directly, so the GPU path needs no ffmpeg layer.
- MLX-friendly. Its
encode()takes any Python buffer-protocol object, so an evaluated MLXmx.array(Apple-Silicon unified memory) feeds it directly.
What's ours
Everything is ours — there is no vendored SDK (VideoToolbox/CoreVideo/CoreMedia are macOS system frameworks):
src/cpp/vtenc_ext.mm OURS — the only native code; a thin pybind11 binding over
VTCompressionSession (Objective-C++).
src/pdum/vtenc/__init__.py OURS — Python surface + single-extension loader.
CMakeLists.txt OURS — pybind11 3.0.4; -framework links; one _vtenc module.
build-wheel.sh OURS — self-contained wheel build (delocate).
Behaviour (matches the pdum.rfb invariants)
- NV12 in → H.264 Annex B out (start codes, in-band SPS/PPS on every IDR — what the
browser's WebCodecs
VideoDecoderwants). - Low-latency, no frame reordering (no B-frames ⇒ output order == input order) and
synchronous 1-in-1-out: each
encode()returns its own frame's access unit (CompleteFramesafter each submit) — required for correct seq attribution. - BT.601 limited range VUI (matches
pdum.rfb'sgpu.rgb_to_nv12kernel), so a browser decodes the color correctly. - Fixed-resolution, even dimensions; one
VTCompressionSessionper instance.
Usage
import numpy as np
from pdum.vtenc import VtEncoder
enc = VtEncoder(1920, 1080, fps=30, bitrate=12_000_000)
nv12 = np.zeros((1080 * 3 // 2, 1920), dtype=np.uint8) # contiguous NV12 (Y then UV)
# ... fill nv12 (e.g. from an evaluated MLX array) ...
annexb = enc.encode(nv12, force_idr=True) # bytes; H.264 Annex B
annexb += enc.flush()
print(enc.codec_string) # e.g. "avc1.420028" (from the SPS)
enc.close()
encode() accepts any contiguous (H*3//2, W) uint8 buffer-protocol object — numpy or
an evaluated MLX mx.array (call mx.eval(frame) first; MLX is lazy).
VtEncoder.codec_string is the avc1.PPCCLL string derived from the actual emitted
SPS (VideoToolbox picks the level from the resolution, so it is not a constant — 1080p
Baseline is avc1.420028, not avc1.42E01F). Empty until the first keyframe.
Build & test (local, CMake)
cmake -S . -B build -G Ninja
cmake --build build -j
Build wheels (maintainer)
./build-wheel.sh # cp314 -> dist/habemus_papadum_vtenc-*.whl
PYTHON_VERSIONS="3.12 3.13 3.14" ./build-wheel.sh
Requires only Xcode Command Line Tools (clang + the macOS SDK frameworks); the full
Metal toolchain is not needed for v1. The wheel bundles nothing beyond the extension —
the frameworks come from macOS, as they must. Publishing to PyPI is done by
scripts/publish.sh, not from CI.
Scope / caveats
- Fixed-resolution NV12 in, Annex B out, one encoder per instance. H.264 only (HEVC is a
follow-up). No
EncoderBackend/serve()wiring yet — that's thepdum.rfbintegration. - Input is a host-visible (CPU / unified-memory) NV12 buffer, memcpy'd into an
encoder-owned
CVPixelBuffer. Wrapping an MLX unified-memory buffer as theCVPixelBufferbacking directly (true zero-copy) is a follow-up.
Metadata
Release files for habemus-papadum-vtenc 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| habemus_papadum_vtenc-0.3.0-cp314-cp314-macosx_12_0_arm64.whl | CPython 3.14 | CPython 3.14 | macOS 12.0+ ARM64 | Details |
| habemus_papadum_vtenc-0.3.0-cp313-cp313-macosx_12_0_arm64.whl | CPython 3.13 | CPython 3.13 | macOS 12.0+ ARM64 | Details |
| habemus_papadum_vtenc-0.3.0-cp312-cp312-macosx_12_0_arm64.whl | CPython 3.12 | CPython 3.12 | macOS 12.0+ ARM64 | Details |
Total release size: 277.7 kB
Release files / habemus_papadum_vtenc-0.3.0-cp314-cp314-macosx_12_0_arm64.whl
| Download URL | habemus_papadum_vtenc-0.3.0-cp314-cp314-macosx_12_0_arm64.whl |
|---|---|
| Size | 92.6 kB |
| Tags | CPython 3.14 macOS 12.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
c85ffa478a6e803882feffdee3904f94fd6bd034dba7aa9005d47ac4008a0907
|
|
BLAKE2b-256 checksum How to use checksums |
61c9c21f2c4e56351f86534718526ee6cb5d91ce4c8c19ab998368c801a59cf5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Release files / habemus_papadum_vtenc-0.3.0-cp313-cp313-macosx_12_0_arm64.whl
| Download URL | habemus_papadum_vtenc-0.3.0-cp313-cp313-macosx_12_0_arm64.whl |
|---|---|
| Size | 92.6 kB |
| Tags | CPython 3.13 macOS 12.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
aada6be76c8aed64f1d319ea0d2ae7b2eb2143707049aed270f7ba2f224054fb
|
|
BLAKE2b-256 checksum How to use checksums |
922071422d427a1b409e4fde57689b00bcf9fc407b57928c43abc36f0d5a5d6d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Release files / habemus_papadum_vtenc-0.3.0-cp312-cp312-macosx_12_0_arm64.whl
| Download URL | habemus_papadum_vtenc-0.3.0-cp312-cp312-macosx_12_0_arm64.whl |
|---|---|
| Size | 92.5 kB |
| Tags | CPython 3.12 macOS 12.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
59f474b4f43866fcea3d16c3b0a3c85da16ed9fa9fc055b3beb4331d381aedb2
|
|
BLAKE2b-256 checksum How to use checksums |
829e8f524459497659498b8646c37e87cb16a7bd8a2973d7fd9923c760bb5d07
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|