Python binding
Status: ✅ verified — the
mediawaypackage inmediaway/is real (pure-Pythonctypesover the C ABI) and the examples inexamples/run against the native libraries: mux/demux roundtrip, real H.264 encode, real camera + mic capture. This README is the DX contract the package implements: context managers, exceptions, Rational-second timestamps, bytes for buffers.
See the C binding README for the underlying C ABI contract (status enums, ownership, thread confinement) — the Python package translates that ABI into idiomatic Python.
The native library behind that ABI is 100% Rust — no libav*/GPL codec
dependencies, memory-safe by construction on the native side. This package is
a thin ctypes wrapper, not a reimplementation.
Platforms: Windows x64 is the fully hardware-verified platform (device/pipeline capture and encode). Linux x64 is container-verified (mux/demux); device/pipeline capability on Linux is untested here.
What Mediaway is (the capabilities)
A streaming-first media stack. The C ABI currently covers three capabilities (full
detail in ../c/README.md and docs/spec/c-ffi.md):
- Container — mux + demux, all 8
mediaway-containerformats: MP4/WebM shareMuxer/Demuxer(format=ContainerFormat.MP4/.WEBM, typestated Open→Live viabegin(), never touches files — the caller owns byte I/O). Ogg/ADTS/FLV/MPEG-TS/MP3 get dedicated classes (OggMuxer/OggDemuxer,AdtsMuxer/AdtsDemuxer,FlvMuxer/FlvDemuxer,TsMuxer/TsDemuxer,Mp3Muxer/Mp3Demuxer) reflecting each format's own C ABI shape — see each module's (_container_*.py) top comment. WAV is mux-only (WavMuxer, consumingfinish()); demux is the one-shotwav_parse()function, not a class at all. These 6 formats useRawPacket(ABI-native integer pts/dts, notRationalseconds) since none of them have MP4's per-track time base to convert against. Fully real, all formats run-verified. Replay ring + MP4 payload placements (ABI 8, adr/container/0009):ReplayRing(anchor_stream_id, anchor_time_base, window)keeps the last N seconds ofPackets andclip_last(span)cuts aReplayClipat a keyframe, in decode order, with timestamps rebased to zero — up to one keyframe interval earlier than asked, never later. A clip is an owned snapshot: it stays valid after furtherpushcalls and after the ring is closed, andentry.payloadis a copy. Write it by pushingentry.to_packet()through aMuxer(there is deliberately no one-shot "save clip" call; track ids must match the ring's stream ids).payload="stored"rings hold onlyStoredPayload(file, offset, length)for a caller that already writes packets to a file, fed fromMuxer.create_with_placements()→LiveMuxer.poll_placements(). Limit: the C ABI has no video-packet source —EncodeSessionmuxes its encoder's packets internally — so feed the ring packets from aDemuxer, fromAudioEncoder, or from an encoder you drive yourself. Ring failures areUnknownStreamErrorandOutOfOrderPacketError(the packet was not added; drop it and carry on). - Pipeline — auto video encode → fMP4, plus decode: one call picks the best
available OS/GPU encoder for a config, wires it into an internal MP4 muxer;
finish()returns complete MP4 bytes. The audio encoder is separate (ABI v2, adr/0003):AudioEncoder.open()streams AAC (or Opus) packets for the caller's own muxer. Decode is the mirror shape (adr/0004, adr/pipeline/0006):DecodeSessionwraps the best available video decoder (CPU output only; Windows/WMF today),AudioDecodeSessiondecodes Opus (software, identical on every host) or AAC (codec=Codec.AAC, extra_data=<AudioSpecificConfig>; adr/pipeline/0007) — both single-step handles (the handle IS the decoder),NO_BACKENDraisesDecoderUnavailableErrorgracefully. AAC is the OS's own decoder (Media Foundation on Windows, AudioToolbox on Apple; unavailable elsewhere), so its samples can differ between hosts; the AudioSpecificConfig is required and only raw AAC is accepted (de-header ADTS first). The Apple arm is compile-checked but has not been run.EncodeSession.poll_bytes()streams the fMP4 out during a long recording (memory bounded by the poll cadence);finish()then returns only the unpolled tail.encoder_support(codec, width, height)/decoder_support(codec)probe what works on this machine before opening a session — both are costly (they open throwaway sessions), and encoder support depends on resolution. - Device — capture: camera (CPU frames), microphone/loopback (PCM), hotplug.
AudioCapture.open(source="process_loopback", process_id=…, include_target_process_tree=…)records that process tree, or withFalseeverything the desktop renders except it — Windows has no "target process alone" mode. Screen capture is real (GPU-backed, DXGI Desktop Duplication) via theGpuDevicefactory (adr/0007-gpu-device-factory.md) —VideoCapture.open(source= "screen")builds one internally, or share your own with an encoder. There is no CPU pixel readback path for Screen frames; real pixels only ever move throughEncodeSession.write_frame_from_desktop_capture(adr/pipeline/0005). Window capture is real on Windows (WGC, adr/device/0005):VideoCapture.open(source= "window", window=<HWND>, ...)takes the sameGpuDevice, pluscursor="included",border="hidden"(read the outcome fromcapture.border_hidden),dimensions="even_cropped"andregion=(x, y, w, h). A region away from the window's origin is one GPU copy per frame — not Zero-Copy — and a window that shrinks below it raisesRegionOutOfBoundsError. DXGI Screen capture cannot draw the pointer or crop, socursor="included"/region=onsource="screen"raiseCaptureUnsupportedError. Other platforms raiseCaptureUnsupportedErrorfor Window.
The real ABI beneath (what the wrapper wraps)
DLLs: mediaway_ffi, mediaway_ffi, mediaway_ffi (built
for x86_64-pc-windows-gnu, see the C README's build recipe). Headers
crates/mediaway-*-ffi/include/mediaway/{container,pipeline,device}.h are the
authoritative layout.
- Opaque handles, all thread-confined (no concurrent calls on one handle).
- Every status is a per-crate enum,
OK = 0; a caught Rust panic poisons the handle.NO_BACKEND/UNSUPPORTEDare expected outcomes, not errors. - Ownership: borrowed inputs valid for the call only (the wrapper must copy in);
owned outputs (
poll_bytesbuffers, demuxed packets/stream info, encodefinishbuffers, polled device frames) must be released via the matching_free— the wrapper's job is to make this automatic (context managers / finalizers). - Handle-consumption traps the wrapper MUST hide:
mediaway_encode_session_openconsumes the encoder unconditionally;mediaway_encode_session_finishconsumes the session. Python can hide this by foldingopenintoEncodeSessionconstruction and makingfinishterminal.
Ideal API — the DX contract
A single mediaway package, pure-Python ctypes glue + idiomatic wrappers.
snake_case everywhere (Python convention beats the C names): Rational,
VideoStreamInfo, AudioStreamInfo, Packet, Codec (enum), VideoFrame;
classes Muxer, Demuxer, EncodeSession, AutoVideoEncoder, VideoCapture,
AudioCapture, GpuDevice.
- Context managers:
with Muxer() as m:,with Demuxer() as d:,with EncodeSession(...) as s:—__exit__closes the underlying handle (and, for capture sessions, joins the backend worker thread). This is the primary lifecycle shape; explicit.close()exists for non-withusers. - Exceptions: a
MediawayErrorbase carrying the raw status code, with subclasses for the expected outcomes (EncoderUnavailableError,DeviceUnavailableError,CaptureUnsupportedError) so examples can catch-and-continue rather than crash on missing hardware. No status-code checking in example bodies. - Typestate as two classes (mirrors C++):
Muxer(Open:add_video_track/add_audio_track) →.begin()returnsLiveMuxer(push_packet / flush / poll_bytes). Calling track registration on aLiveMuxeris impossible, matching the ABI'sINVALID_STATE. - bytes for byte buffers:
poll_bytes() -> bytes;push_bytes(bytes);packet.payload -> bytes;frame.data -> bytes(NV12/BGRA8). The wrapper copies out of borrowed/owned native buffers — nomemoryviewleaking into the API. Rational(num, den)as a smalldataclass(frozen=True); info structs as dataclasses.EncodeSession(encoder)takes ownership of the encoder object;finish() -> bytesis terminal (noclose()after it).
Example scenarios
examples/ mirrors the Rust examples/ layout — sector subfolders, one file per
scenario (English comments only; each file's header comment states real vs.
aspirational):
| File | Capability | Real today? |
|---|---|---|
container/mux_roundtrip.py |
mux 90 fake video + audio packets → fMP4 → demux back, count packets | ✅ run verified |
container/replay_ring.py |
synthetic packets → ReplayRing → clip_last → Muxer → standalone MP4, demuxed back (ABI 8) |
✅ run verified |
pipeline/encode_to_mp4.py |
auto H.264 encode of 90 synthetic NV12 frames → out.mp4 |
✅ run verified |
pipeline/encode_audio.py |
auto AAC encode of 96 synthetic F32 stereo frames → audio-only fMP4 (ABI v2) | ✅ run verified |
pipeline/stream_encode.py |
streams 300 synthetic NV12 frames to a file with poll_bytes(), appends the finish() tail, demuxes the result |
✅ run verified |
pipeline/decode_roundtrip.py |
auto H.264 decode (encode→mux→demux→decode) + Opus audio decode round trip | ✅ run verified |
device/camera_record.py |
camera + mic → H.264 + AAC → ONE two-track MP4 (remuxed; audio track registered with the encoder's AudioSpecificConfig) | ✅ run verified on real hardware; video-only fallback without mic/audio backend |
device/capture_microphone.py |
microphone capture, raw PCM | ✅ run verified (real mic) |
pipeline/screen_record.py |
screen + mic → encode → MP4, via GpuDevice + the capture-to-encode bridge |
✅ run verified on real hardware (GPU-input encode gracefully skips as a known driver/encoder limitation, not a bug); mic PCM drained, not muxed — see camera_record.py for two-track remux |
device/capture_screen.py |
screen capture only, via GpuDevice |
✅ run verified on real hardware |
device/capture_window.py |
one window by HWND: cursor, hidden border, even-crop |
✅ run verified on real hardware (Windows 11) |
Rules
- English comments only.
- Map existing Rust surfaces; do not invent capabilities the Rust side doesn't have.
- Wrap the ABI fully: no
ctypestypes or raw handles visible in examples. - Not part of the Cargo workspace; durable API changes require an ADR (ADR-0004).
Metadata
Release files for mediaway 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mediaway-0.2.1.tar.gz | 666.0 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| mediaway-0.2.1-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| mediaway-0.2.1-py3-none-manylinux_2_39_x86_64.whl | Python 3 | none | Linux glibc 2.39+ x86-64 | Details |
| mediaway-0.2.1-py3-none-macosx_11_0_x86_64.whl | Python 3 | none | macOS 11.0+ x86-64 | Details |
| mediaway-0.2.1-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 7.2 MB
Release files / mediaway-0.2.1.tar.gz
| Download URL | mediaway-0.2.1.tar.gz |
|---|---|
| Size | 666.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
96643dae4b7c6de32f3b7a7126fe6e299ccf6f7d2eceb29274bcf70022116339
|
|
BLAKE2b-256 checksum How to use checksums |
afa678a26bc6df3afe49b2a0b71c1ad04507cd8ef24e6c9aebdff3b69313da0c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / mediaway-0.2.1-py3-none-win_amd64.whl
| Download URL | mediaway-0.2.1-py3-none-win_amd64.whl |
|---|---|
| Size | 4.2 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
073a2866dc11a85b29fa43d0a1230c2f74cf7ee6ca2e851ad5545264b6b0d76e
|
|
BLAKE2b-256 checksum How to use checksums |
fd653f5adbde4c86b0f931070794fb97f70f776561aae97c05948c749cbb48bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / mediaway-0.2.1-py3-none-manylinux_2_39_x86_64.whl
| Download URL | mediaway-0.2.1-py3-none-manylinux_2_39_x86_64.whl |
|---|---|
| Size | 1.0 MB |
| Tags | Linux glibc 2.39+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
e87b913e4aeba208d47ad0633d4756ee26dcc9be2fd1afe263eabc5ce9391119
|
|
BLAKE2b-256 checksum How to use checksums |
384f1120781969ae89761676f55d8eaca15afff9870fb024289574a2ba5fc1b9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / mediaway-0.2.1-py3-none-macosx_11_0_x86_64.whl
| Download URL | mediaway-0.2.1-py3-none-macosx_11_0_x86_64.whl |
|---|---|
| Size | 679.5 kB |
| Tags | Python 3 macOS 11.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
ed8a61f0659e7a7aac928555bfc91cb2f64fd58c0143e109e3e484c7a64b24c7
|
|
BLAKE2b-256 checksum How to use checksums |
1a2eb443378720bde3514a5d118c1db7cfeb019c6ab93f234d46fc12a30ad6dc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / mediaway-0.2.1-py3-none-macosx_11_0_arm64.whl
| Download URL | mediaway-0.2.1-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 658.5 kB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
5d04141a0c52d62d395f8f5b421806727e2ab4a553d3155f2019dc5895d4c06c
|
|
BLAKE2b-256 checksum How to use checksums |
eb0d3d17772538945c2ed9ccf2658eeaf37be23d3c781467aea1493650f3c05f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency log