Skip to main content

Python binding

Status: ✅ verified — the mediaway package in mediaway/ is real (pure-Python ctypes over the C ABI) and the examples in examples/ 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):

  1. Container — mux + demux, all 8 mediaway-container formats: MP4/WebM share Muxer/Demuxer (format=ContainerFormat.MP4/.WEBM, typestated Open→Live via begin(), 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, consuming finish()); demux is the one-shot wav_parse() function, not a class at all. These 6 formats use RawPacket (ABI-native integer pts/dts, not Rational seconds) 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 of Packets and clip_last(span) cuts a ReplayClip at 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 further push calls and after the ring is closed, and entry.payload is a copy. Write it by pushing entry.to_packet() through a Muxer (there is deliberately no one-shot "save clip" call; track ids must match the ring's stream ids). payload="stored" rings hold only StoredPayload(file, offset, length) for a caller that already writes packets to a file, fed from Muxer.create_with_placements() → LiveMuxer.poll_placements(). Limit: the C ABI has no video-packet source — EncodeSession muxes its encoder's packets internally — so feed the ring packets from a Demuxer, from AudioEncoder, or from an encoder you drive yourself. Ring failures are UnknownStreamError and OutOfOrderPacketError (the packet was not added; drop it and carry on).
  2. 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): DecodeSession wraps the best available video decoder (CPU output only; Windows/WMF today), AudioDecodeSession decodes 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_BACKEND raises DecoderUnavailableError gracefully. 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.
  3. 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 with False everything the desktop renders except it — Windows has no "target process alone" mode. Screen capture is real (GPU-backed, DXGI Desktop Duplication) via the GpuDevice factory (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 through EncodeSession.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 same GpuDevice, plus cursor="included", border="hidden" (read the outcome from capture.border_hidden), dimensions="even_cropped" and region=(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 raises RegionOutOfBoundsError. DXGI Screen capture cannot draw the pointer or crop, so cursor="included" / region= on source="screen" raise CaptureUnsupportedError. Other platforms raise CaptureUnsupportedError for 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 / UNSUPPORTED are expected outcomes, not errors.
  • Ownership: borrowed inputs valid for the call only (the wrapper must copy in); owned outputs (poll_bytes buffers, demuxed packets/stream info, encode finish buffers, 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_open consumes the encoder unconditionally; mediaway_encode_session_finish consumes the session. Python can hide this by folding open into EncodeSession construction and making finish terminal.

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-with users.
  • Exceptions: a MediawayError base 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() returns LiveMuxer (push_packet / flush / poll_bytes). Calling track registration on a LiveMuxer is impossible, matching the ABI's INVALID_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 — no memoryview leaking into the API.
  • Rational(num, den) as a small dataclass(frozen=True); info structs as dataclasses.
  • EncodeSession(encoder) takes ownership of the encoder object; finish() -> bytes is terminal (no close() 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 ctypes types 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)

Source distribution for mediaway 0.2.1
File Size Uploaded
mediaway-0.2.1.tar.gz 666.0 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for mediaway 0.2.1
File
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 log

Release 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 log

Release 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 log

Release 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 log

Release 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

Release history Release notifications | RSS feed

This release

0.2.1 This release

5 release files

0.1.8

5 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page