Metadata-Version: 2.4
Name: mediaway
Version: 0.1.4
Summary: Idiomatic Python binding over Mediaway's C ABI (mux/demux, auto video/audio encode, camera/mic capture)
Author: Mediaway contributors
License: MIT OR Apache-2.0
Project-URL: Repository, https://github.com/nyxways/mediaway
Project-URL: Documentation, https://nyxways.github.io/mediaway/
Keywords: media,video,audio,mp4,h264,aac,ffi,mediaway
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Video
Classifier: Topic :: Multimedia :: Sound/Audio
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# 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](../c/README.md) for the underlying C ABI contract (status
enums, ownership, thread confinement) — the Python package translates that ABI into
idiomatic Python.

## What Mediaway is (the capabilities)

A streaming-first media stack. The C ABI currently covers three capabilities (full
detail in [`../c/README.md`](../c/README.md) and `docs/spec/c-ffi.md`):

1. **Container — mux + demux**: sans-io fragmented-MP4 muxer (register video/audio
   tracks, `begin()` → live, push packets, flush, `poll_bytes()`; the muxer never
   touches files — the caller owns byte I/O) and demuxer (`push_bytes`, `streams()`,
   `poll_packet()`, optional ClearKey key). Fully real.
2. **Pipeline — auto video encode → fMP4**: one call picks the best available OS/GPU
   encoder for a config, wires it into an internal MP4 muxer; `finish()` returns
   complete MP4 bytes. **Video only** — the audio encoder is separate (ABI v2,
   adr/0003): `AudioEncoder.open()` streams AAC packets for the caller's own muxer.
3. **Device — capture**: camera (CPU frames), microphone/loopback (PCM), hotplug.
   **Screen capture is `UNSUPPORTED` from C today** (needs a GPU device handle with no
   C representation yet) — an honest gap, not a bug.

## 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`.

- **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 |
| `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 (96 packets → 27372 bytes fMP4) |
| `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 (47 frames + 80 AAC packets → ~251 KB two-track MP4); 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 | 🚧 aspirational — `VideoCapture.open(source="screen")` raises `CaptureUnsupportedError` today |
| `device/capture_screen.py` | screen capture only | 🚧 same gap, capture-only |

## Testing

The release pipeline stages the built `mediaway_ffi.dll` at
`mediaway/_native/mediaway_ffi.dll` (the wheel's native directory). The
round-trip binding check validates that DLL against the documented ABI
contract:

```
python tests/test_mux_roundtrip.py
```

Run from `bindings/python/`. Pure std-lib (no pytest), assert-based: it muxes
90 synthetic H.264 video + 90 synthetic AAC audio packets into a fragmented
MP4, demuxes the bytes back, and asserts the 1:1 packet round-trip plus the
recovered stream metadata (video codec/dimensions/frame rate, audio codec). A
failed assertion exits nonzero, which is the CI job's failure signal. Pure CPU
— no hardware required.

## 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).
