blazar-video 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- blazar_video-0.1.0/Cargo.toml +50 -0
- blazar_video-0.1.0/PKG-INFO +113 -0
- blazar_video-0.1.0/README.md +89 -0
- blazar_video-0.1.0/bindings/python/Cargo.lock +1164 -0
- blazar_video-0.1.0/bindings/python/Cargo.toml +45 -0
- blazar_video-0.1.0/bindings/python/README.md +89 -0
- blazar_video-0.1.0/bindings/python/build.rs +18 -0
- blazar_video-0.1.0/bindings/python/sglang_patch/README.md +29 -0
- blazar_video-0.1.0/bindings/python/sglang_patch/sitecustomize.py +121 -0
- blazar_video-0.1.0/bindings/python/src/lib.rs +1865 -0
- blazar_video-0.1.0/bindings/python/tests/test_acceptance.py +344 -0
- blazar_video-0.1.0/bindings/python/tests/test_audio.py +140 -0
- blazar_video-0.1.0/bindings/python/tests/test_backend_selection.py +109 -0
- blazar_video-0.1.0/bindings/python/tests/test_encoder.py +385 -0
- blazar_video-0.1.0/bindings/python/tests/test_hardware.py +110 -0
- blazar_video-0.1.0/bindings/python/tests/test_memory_sink.py +58 -0
- blazar_video-0.1.0/bindings/python/tests/test_preprocess.py +128 -0
- blazar_video-0.1.0/bindings/python/tests/test_sglang_interop.py +109 -0
- blazar_video-0.1.0/bindings/python/tests/test_submit_dispatch.py +135 -0
- blazar_video-0.1.0/crates/blazar-calibration/Cargo.toml +13 -0
- blazar_video-0.1.0/crates/blazar-calibration/src/index.rs +325 -0
- blazar_video-0.1.0/crates/blazar-calibration/src/lib.rs +18 -0
- blazar_video-0.1.0/crates/blazar-calibration/src/loader.rs +190 -0
- blazar_video-0.1.0/crates/blazar-calibration/src/schema.rs +232 -0
- blazar_video-0.1.0/crates/blazar-calibration/tests/default_files.rs +107 -0
- blazar_video-0.1.0/crates/blazar-calibration/tests/load_dir.rs +159 -0
- blazar_video-0.1.0/crates/blazar-core/Cargo.toml +10 -0
- blazar_video-0.1.0/crates/blazar-core/src/error.rs +26 -0
- blazar_video-0.1.0/crates/blazar-core/src/frame.rs +236 -0
- blazar_video-0.1.0/crates/blazar-core/src/identity.rs +54 -0
- blazar_video-0.1.0/crates/blazar-core/src/lib.rs +33 -0
- blazar_video-0.1.0/crates/blazar-core/src/stream_params.rs +144 -0
- blazar_video-0.1.0/crates/blazar-core/src/target.rs +67 -0
- blazar_video-0.1.0/crates/blazar-core/src/timebase.rs +132 -0
- blazar_video-0.1.0/crates/blazar-core/src/unit.rs +82 -0
- blazar_video-0.1.0/crates/blazar-encoder/Cargo.toml +65 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/av1.rs +445 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/config.rs +108 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/cpu_preprocess.rs +70 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/gpu_preprocess.rs +358 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/lib.rs +293 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/nal.rs +547 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/nvenc/config.rs +332 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/nvenc/cuda.rs +320 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/nvenc/keyframe.rs +97 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/nvenc/mod.rs +24 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/nvenc/nv12.rs +178 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/nvenc/pool.rs +168 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/nvenc/worker.rs +1532 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/opus.rs +252 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/svtav1.rs +246 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/videotoolbox/apple.rs +225 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/videotoolbox/mod.rs +610 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/vp9.rs +279 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/x264.rs +406 -0
- blazar_video-0.1.0/crates/blazar-encoder/src/x265.rs +392 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/av1_config.rs +91 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/fixtures/README.md +13 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/fixtures/h264.annexb +0 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/fixtures/hevc.annexb +0 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/fixtures/nvenc_av1_keyframe_head.bin +0 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/gpu_preprocess.rs +174 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/nvenc_cuda.rs +324 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/nvenc_hardware.rs +295 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/nvenc_host_convert.rs +259 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/svt_config_probe.rs +157 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/svtav1_backend.rs +78 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/videotoolbox.rs +245 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/vp9_backend.rs +66 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/x264_backend.rs +137 -0
- blazar_video-0.1.0/crates/blazar-encoder/tests/x265_backend.rs +117 -0
- blazar_video-0.1.0/crates/blazar-preprocess/Cargo.toml +11 -0
- blazar_video-0.1.0/crates/blazar-preprocess/src/lib.rs +1438 -0
- blazar_video-0.1.0/crates/blazar-preprocess/src/simd/avx2.rs +656 -0
- blazar_video-0.1.0/crates/blazar-preprocess/src/simd/neon.rs +573 -0
- blazar_video-0.1.0/crates/blazar-preprocess/src/simd.rs +160 -0
- blazar_video-0.1.0/crates/blazar-runtime/Cargo.toml +34 -0
- blazar_video-0.1.0/crates/blazar-runtime/examples/videotoolbox_encode.rs +129 -0
- blazar_video-0.1.0/crates/blazar-runtime/src/backend_selection.rs +464 -0
- blazar_video-0.1.0/crates/blazar-runtime/src/lib.rs +1081 -0
- blazar_video-0.1.0/crates/blazar-runtime/src/preprocess_placement.rs +326 -0
- blazar_video-0.1.0/crates/blazar-runtime/tests/nvenc_e2e.rs +199 -0
- blazar_video-0.1.0/crates/blazar-runtime/tests/pipeline.rs +351 -0
- blazar_video-0.1.0/crates/blazar-runtime/tests/videotoolbox_e2e.rs +184 -0
- blazar_video-0.1.0/crates/blazar-runtime/tests/x264_close.rs +126 -0
- blazar_video-0.1.0/crates/blazar-sink/Cargo.toml +13 -0
- blazar_video-0.1.0/crates/blazar-sink/src/fmp4.rs +501 -0
- blazar_video-0.1.0/crates/blazar-sink/src/lib.rs +309 -0
- blazar_video-0.1.0/crates/blazar-sink/tests/fmp4_sink.rs +240 -0
- blazar_video-0.1.0/crates/blazar-svtav1-sys/Cargo.toml +11 -0
- blazar_video-0.1.0/crates/blazar-svtav1-sys/build.rs +48 -0
- blazar_video-0.1.0/crates/blazar-svtav1-sys/src/lib.rs +87 -0
- blazar_video-0.1.0/crates/blazar-vpx-sys/Cargo.toml +17 -0
- blazar_video-0.1.0/crates/blazar-vpx-sys/build.rs +61 -0
- blazar_video-0.1.0/crates/blazar-vpx-sys/src/lib.rs +29 -0
- blazar_video-0.1.0/crates/blazar-vpx-sys/src/pregenerated.rs +4133 -0
- blazar_video-0.1.0/crates/blazar-x265-sys/Cargo.toml +16 -0
- blazar_video-0.1.0/crates/blazar-x265-sys/build.rs +78 -0
- blazar_video-0.1.0/crates/blazar-x265-sys/src/lib.rs +36 -0
- blazar_video-0.1.0/crates/blazar-x265-sys/src/pregenerated.rs +1213 -0
- blazar_video-0.1.0/pyproject.toml +50 -0
- blazar_video-0.1.0/python/blazar/__init__.py +23 -0
- blazar_video-0.1.0/python/blazar/__init__.pyi +89 -0
- blazar_video-0.1.0/python/blazar/py.typed +0 -0
- blazar_video-0.1.0/python/blazar/sglang_interop.py +195 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
# The Python binding is its own workspace: maturin builds it with
|
|
3
|
+
# `extension-module`, which breaks `cargo test` linking, so it must not be part of
|
|
4
|
+
# the portable test surface.
|
|
5
|
+
exclude = ["bindings/python"]
|
|
6
|
+
resolver = "2"
|
|
7
|
+
members = [
|
|
8
|
+
"crates/blazar-svtav1-sys",
|
|
9
|
+
"crates/blazar-vpx-sys",
|
|
10
|
+
"crates/blazar-x265-sys",
|
|
11
|
+
"crates/blazar-core",
|
|
12
|
+
"crates/blazar-calibration",
|
|
13
|
+
"crates/blazar-encoder",
|
|
14
|
+
"crates/blazar-sink",
|
|
15
|
+
"crates/blazar-preprocess",
|
|
16
|
+
"crates/blazar-runtime",
|
|
17
|
+
]
|
|
18
|
+
[workspace.package]
|
|
19
|
+
version = "0.1.0"
|
|
20
|
+
edition = "2021"
|
|
21
|
+
license = "Apache-2.0"
|
|
22
|
+
rust-version = "1.95"
|
|
23
|
+
|
|
24
|
+
# Shared dependency versions. Pinned (see project rules: exact/pinned versions).
|
|
25
|
+
[workspace.dependencies]
|
|
26
|
+
thiserror = "=2.0.21"
|
|
27
|
+
serde = { version = "=1.0.229", features = ["derive"] }
|
|
28
|
+
toml = "=1.1.8"
|
|
29
|
+
tracing = "=0.1.44"
|
|
30
|
+
# CPU preprocess is data-parallel over chroma rows; exact pin per project rules.
|
|
31
|
+
rayon = "=1.12.0"
|
|
32
|
+
oxideav-core = "=0.1.40"
|
|
33
|
+
oxideav-mp4 = { git = "https://github.com/jgh-/oxideav-mp4", rev = "9b6f7bb6a0dee7de72b8e593cb1feba5f14034e6" }
|
|
34
|
+
# NVENC (Phase 2). Fork pinned by rev: adds EncodePictureParams::encode_pic_flags
|
|
35
|
+
# (forced IDR) and Buffer::pitch() to the upstream 0.4.0 wrapper.
|
|
36
|
+
nvidia-video-codec-sdk = { git = "https://github.com/jgh-/nvidia-video-codec-sdk", rev = "84ac15e" }
|
|
37
|
+
# The SDK's published 0.4.0 pins cudarc 0.16.6, which rejects CUDA 13. The git
|
|
38
|
+
# revision depends on cudarc 0.19; pin it with the explicit toolkit feature.
|
|
39
|
+
cudarc = { version = "=0.19.10", default-features = false, features = ["cuda-13010", "driver", "dynamic-loading", "nvrtc"] }
|
|
40
|
+
|
|
41
|
+
# Internal crates
|
|
42
|
+
blazar-core = { path = "crates/blazar-core" }
|
|
43
|
+
blazar-calibration = { path = "crates/blazar-calibration" }
|
|
44
|
+
blazar-encoder = { path = "crates/blazar-encoder" }
|
|
45
|
+
blazar-sink = { path = "crates/blazar-sink" }
|
|
46
|
+
blazar-preprocess = { path = "crates/blazar-preprocess" }
|
|
47
|
+
|
|
48
|
+
[profile.release]
|
|
49
|
+
lto = "thin"
|
|
50
|
+
codegen-units = 1
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: blazar-video
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
6
|
+
Classifier: Programming Language :: Rust
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Topic :: Multimedia :: Video :: Conversion
|
|
11
|
+
Classifier: Topic :: Multimedia :: Video :: Non-Linear Editor
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Summary: Model-aware video encoding for generated video: zero-copy GPU ingest, per-model calibrations, and one submit for host or device frames
|
|
15
|
+
Keywords: video,encoding,nvenc,av1,h264,hevc,diffusion,torch
|
|
16
|
+
License: Apache-2.0
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
19
|
+
Project-URL: Changelog, https://github.com/blazar-video/blazar/blob/main/CHANGELOG.md
|
|
20
|
+
Project-URL: Documentation, https://github.com/blazar-video/blazar/blob/main/docs/python-api.md
|
|
21
|
+
Project-URL: Homepage, https://github.com/blazar-video/blazar
|
|
22
|
+
Project-URL: Issues, https://github.com/blazar-video/blazar/issues
|
|
23
|
+
|
|
24
|
+
# Python bindings
|
|
25
|
+
|
|
26
|
+
PyO3 bindings over `blazar-runtime`, built with maturin.
|
|
27
|
+
|
|
28
|
+
## Build
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
python3 -m venv .venv && . .venv/bin/activate
|
|
32
|
+
pip install maturin numpy pytest
|
|
33
|
+
|
|
34
|
+
# mock encoder (no CUDA): enough to exercise the binding and the muxer
|
|
35
|
+
maturin develop
|
|
36
|
+
|
|
37
|
+
# hardware AV1 on a CUDA host
|
|
38
|
+
maturin develop --features nvenc
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`bindings/python` is its own cargo workspace on purpose: `extension-module` does
|
|
42
|
+
not link libpython, which breaks `cargo test` linking, so the portable
|
|
43
|
+
`cargo test --workspace` must not see it.
|
|
44
|
+
|
|
45
|
+
## Test
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
pytest bindings/python/tests -v
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The suite uses the mock encoder and asserts container structure, not decodability:
|
|
52
|
+
the mock has no real codec, so it is given a plausible `video_extradata` and
|
|
53
|
+
writes synthetic payload bytes. Decode validation stays on hardware (see the
|
|
54
|
+
repo README, "Hardware validation").
|
|
55
|
+
|
|
56
|
+
## Use
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import numpy as np
|
|
60
|
+
import blazar
|
|
61
|
+
|
|
62
|
+
Calibrations = blazar.Calibrations.load("calibrations/default")
|
|
63
|
+
|
|
64
|
+
with blazar.Encoder(
|
|
65
|
+
calibrations=calibrations,
|
|
66
|
+
identity={"family": "wan", "version": "2.1", "quant": "bf16", "size": "1.3b"},
|
|
67
|
+
target="balanced",
|
|
68
|
+
backend="svtav1", # or "nvenc", or "auto" with backend_pool=
|
|
69
|
+
output="out.mp4",
|
|
70
|
+
queue_depth=4, # frames the encoder may be behind
|
|
71
|
+
) as stream:
|
|
72
|
+
for i, frame in enumerate(frames): # frame: (H, W, 3) float32 in [0, 1]
|
|
73
|
+
stream.submit(
|
|
74
|
+
frame, width=W, height=H, fps=24.0,
|
|
75
|
+
layout="thwc", range="unit01",
|
|
76
|
+
scene_cut=(i == 0),
|
|
77
|
+
)
|
|
78
|
+
for event in stream.poll(): # optional: non-blocking
|
|
79
|
+
if event["kind"] == "encoded" and event["keyframe"]:
|
|
80
|
+
pass # a fragment boundary was just closed
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`submit` copies the frame and enqueues it; a Rust worker thread does the
|
|
84
|
+
preprocess, encode and muxing. It blocks only when `queue_depth` frames are
|
|
85
|
+
already queued, and it releases the GIL while blocking, so other Python threads
|
|
86
|
+
keep running. Use `flush()` as a barrier (everything submitted before it is
|
|
87
|
+
durable) and `stats` for counters.
|
|
88
|
+
|
|
89
|
+
`poll()` returns dicts:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
{"kind": "encoded", "seq", "pts", "keyframe", "units", "bytes",
|
|
93
|
+
"encode_us", "queue_depth"}
|
|
94
|
+
{"kind": "failed", "seq", "message"}
|
|
95
|
+
{"kind": "closed"}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`keyframe: True` means the fMP4 sink closed a fragment at that frame, which is
|
|
99
|
+
the segment boundary a transport sink will publish.
|
|
100
|
+
|
|
101
|
+
Worker failures surface as a `failed` event, are re-raised by `submit`, and are
|
|
102
|
+
re-raised by `close()`. Nothing is swallowed.
|
|
103
|
+
|
|
104
|
+
Defaults synthesise PTS as `frame_index * 90000 / fps`; pass `pts_ticks=` to
|
|
105
|
+
place frames explicitly (vLLM-Omni chunks carry a cumulative frame index).
|
|
106
|
+
|
|
107
|
+
## Boundaries
|
|
108
|
+
|
|
109
|
+
- Video only. `submit_audio` does not exist on the Python object.
|
|
110
|
+
- The sink is a file. There is no WebSocket/streaming destination yet, so this
|
|
111
|
+
measures the encode path rather than delivering a live stream.
|
|
112
|
+
- Device tensors are Phase 2: the frame is copied to host and then into Rust.
|
|
113
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Python bindings
|
|
2
|
+
|
|
3
|
+
PyO3 bindings over `blazar-runtime`, built with maturin.
|
|
4
|
+
|
|
5
|
+
## Build
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
python3 -m venv .venv && . .venv/bin/activate
|
|
9
|
+
pip install maturin numpy pytest
|
|
10
|
+
|
|
11
|
+
# mock encoder (no CUDA): enough to exercise the binding and the muxer
|
|
12
|
+
maturin develop
|
|
13
|
+
|
|
14
|
+
# hardware AV1 on a CUDA host
|
|
15
|
+
maturin develop --features nvenc
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`bindings/python` is its own cargo workspace on purpose: `extension-module` does
|
|
19
|
+
not link libpython, which breaks `cargo test` linking, so the portable
|
|
20
|
+
`cargo test --workspace` must not see it.
|
|
21
|
+
|
|
22
|
+
## Test
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
pytest bindings/python/tests -v
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The suite uses the mock encoder and asserts container structure, not decodability:
|
|
29
|
+
the mock has no real codec, so it is given a plausible `video_extradata` and
|
|
30
|
+
writes synthetic payload bytes. Decode validation stays on hardware (see the
|
|
31
|
+
repo README, "Hardware validation").
|
|
32
|
+
|
|
33
|
+
## Use
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import numpy as np
|
|
37
|
+
import blazar
|
|
38
|
+
|
|
39
|
+
Calibrations = blazar.Calibrations.load("calibrations/default")
|
|
40
|
+
|
|
41
|
+
with blazar.Encoder(
|
|
42
|
+
calibrations=calibrations,
|
|
43
|
+
identity={"family": "wan", "version": "2.1", "quant": "bf16", "size": "1.3b"},
|
|
44
|
+
target="balanced",
|
|
45
|
+
backend="svtav1", # or "nvenc", or "auto" with backend_pool=
|
|
46
|
+
output="out.mp4",
|
|
47
|
+
queue_depth=4, # frames the encoder may be behind
|
|
48
|
+
) as stream:
|
|
49
|
+
for i, frame in enumerate(frames): # frame: (H, W, 3) float32 in [0, 1]
|
|
50
|
+
stream.submit(
|
|
51
|
+
frame, width=W, height=H, fps=24.0,
|
|
52
|
+
layout="thwc", range="unit01",
|
|
53
|
+
scene_cut=(i == 0),
|
|
54
|
+
)
|
|
55
|
+
for event in stream.poll(): # optional: non-blocking
|
|
56
|
+
if event["kind"] == "encoded" and event["keyframe"]:
|
|
57
|
+
pass # a fragment boundary was just closed
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`submit` copies the frame and enqueues it; a Rust worker thread does the
|
|
61
|
+
preprocess, encode and muxing. It blocks only when `queue_depth` frames are
|
|
62
|
+
already queued, and it releases the GIL while blocking, so other Python threads
|
|
63
|
+
keep running. Use `flush()` as a barrier (everything submitted before it is
|
|
64
|
+
durable) and `stats` for counters.
|
|
65
|
+
|
|
66
|
+
`poll()` returns dicts:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
{"kind": "encoded", "seq", "pts", "keyframe", "units", "bytes",
|
|
70
|
+
"encode_us", "queue_depth"}
|
|
71
|
+
{"kind": "failed", "seq", "message"}
|
|
72
|
+
{"kind": "closed"}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`keyframe: True` means the fMP4 sink closed a fragment at that frame, which is
|
|
76
|
+
the segment boundary a transport sink will publish.
|
|
77
|
+
|
|
78
|
+
Worker failures surface as a `failed` event, are re-raised by `submit`, and are
|
|
79
|
+
re-raised by `close()`. Nothing is swallowed.
|
|
80
|
+
|
|
81
|
+
Defaults synthesise PTS as `frame_index * 90000 / fps`; pass `pts_ticks=` to
|
|
82
|
+
place frames explicitly (vLLM-Omni chunks carry a cumulative frame index).
|
|
83
|
+
|
|
84
|
+
## Boundaries
|
|
85
|
+
|
|
86
|
+
- Video only. `submit_audio` does not exist on the Python object.
|
|
87
|
+
- The sink is a file. There is no WebSocket/streaming destination yet, so this
|
|
88
|
+
measures the encode path rather than delivering a live stream.
|
|
89
|
+
- Device tensors are Phase 2: the frame is copied to host and then into Rust.
|