reactor-runtime 2.0.0__tar.gz → 2.0.2__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.
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/PKG-INFO +1 -1
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/pyproject.toml +1 -1
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/commands/init.py +2 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/commands/run.py +6 -9
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtime_api.py +8 -1
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtimes/http/http_runtime.py +13 -1
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/config.py +2 -11
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/h264.py +1 -11
- reactor_runtime-2.0.2/src/reactor_runtime/utils/launch.py +103 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/utils/log.py +43 -7
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime.egg-info/PKG-INFO +1 -1
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime.egg-info/SOURCES.txt +1 -0
- reactor_runtime-2.0.2/src/template/README.md +45 -0
- reactor_runtime-2.0.2/src/template/config.yml +3 -0
- reactor_runtime-2.0.2/src/template/model.py +68 -0
- reactor_runtime-2.0.2/src/template/pipeline.py +125 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/template/reactor.yaml +2 -2
- reactor_runtime-2.0.2/src/template/requirements.txt +2 -0
- reactor_runtime-2.0.0/src/reactor_runtime/utils/launch.py +0 -64
- reactor_runtime-2.0.0/src/template/README.md +0 -34
- reactor_runtime-2.0.0/src/template/config.yml +0 -3
- reactor_runtime-2.0.0/src/template/model.py +0 -88
- reactor_runtime-2.0.0/src/template/requirements.txt +0 -1
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/README.md +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/setup.cfg +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/api/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/commands/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/commands/capabilities.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/main.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/utils/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/utils/config.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/utils/runtime.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_cli/utils/version.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/capabilities.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/config.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/events/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/events/connected.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/events/event.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/events/messages.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/internal/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/internal/input_buffer.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/internal/output_buffer.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/internal/reactor_core.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/model/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/model/decorators.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/model/handlers.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/model/input_fields.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/model/reactor_model.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/pipeline/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/pipeline/idle.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/pipeline/input_state.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/pipeline/reactor_pipeline.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/tracks/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/tracks/descriptors.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/tracks/input.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/interface/tracks/output.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/model_state.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/backends/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/backends/base.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/backends/file.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/backends/otlp.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/helpers.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/plotting/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/plotting/plot_profiling.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/profiler.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/profiling/singleton.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtimes/headless/config.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtimes/headless/headless_runtime.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtimes/headless/input_feeder.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtimes/http/config.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtimes/http/types.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/aiortc/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/aiortc/audio_track.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/aiortc/client.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/aiortc/frame_conversion.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/aiortc/ice_connection.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/aiortc/video_track.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/events.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/client.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/av1.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/base.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/factory.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/h265.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/vp8.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/decoders/vp9.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/av1.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/base.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/factory.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/h264.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/h265.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/opus.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/vp8.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/encoders/vp9.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/gst.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/gst_helpers.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/receiver/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/receiver/audio.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/receiver/base.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/receiver/video.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sdp/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sdp/bundle.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sdp/codec.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sdp/extmap.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sdp/ice.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sender/__init__.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sender/audio.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sender/base.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/sender/video.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/settings.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/gstreamer/signals.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/ice_uris.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/interface.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/media.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/transports/types.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/utils/loader.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/utils/messages.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/utils/schema.py +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime.egg-info/dependency_links.txt +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime.egg-info/entry_points.txt +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime.egg-info/requires.txt +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime.egg-info/top_level.txt +0 -0
- {reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/template/__init__.py +0 -0
|
@@ -6,6 +6,7 @@ import importlib.resources
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
_TEMPLATE_FILES = {
|
|
9
|
+
"pipeline.py": "pipeline.py",
|
|
9
10
|
"model.py": "model.py",
|
|
10
11
|
"reactor.yaml": "reactor.yaml",
|
|
11
12
|
"config.yml": "config.yml",
|
|
@@ -85,4 +86,5 @@ class InitCommand:
|
|
|
85
86
|
|
|
86
87
|
print(f"\nReactor workspace '{model_name}' created.")
|
|
87
88
|
print(f"\n cd {model_name}")
|
|
89
|
+
print(" pip install -r requirements.txt")
|
|
88
90
|
print(" reactor run\n")
|
|
@@ -12,7 +12,6 @@ from reactor_cli.utils.config import (
|
|
|
12
12
|
_valid_port,
|
|
13
13
|
)
|
|
14
14
|
from reactor_cli.utils.runtime import load_runtime
|
|
15
|
-
from reactor_runtime import __version__
|
|
16
15
|
from reactor_runtime.config import ReactorConfig
|
|
17
16
|
from reactor_runtime.transports.config import (
|
|
18
17
|
GSTREAMER_INSTALL_URL,
|
|
@@ -207,14 +206,13 @@ class RunCommand:
|
|
|
207
206
|
_RESET = "\033[0m"
|
|
208
207
|
if runtime_kwargs.get("transport_type") is None:
|
|
209
208
|
runtime_kwargs["transport_type"] = resolve_default_transport()
|
|
210
|
-
print(
|
|
211
|
-
f"{_ORANGE}Transport: "
|
|
212
|
-
f"{runtime_kwargs['transport_type'].name.lower()} "
|
|
213
|
-
f"(auto-detected){_RESET}"
|
|
214
|
-
)
|
|
215
209
|
else:
|
|
216
210
|
try:
|
|
217
211
|
validate_transport_available(runtime_kwargs["transport_type"])
|
|
212
|
+
print(
|
|
213
|
+
f"{_ORANGE}Transport: "
|
|
214
|
+
f"{runtime_kwargs['transport_type'].name.lower()}{_RESET}"
|
|
215
|
+
)
|
|
218
216
|
except ImportError as exc:
|
|
219
217
|
name = runtime_kwargs["transport_type"].name.lower()
|
|
220
218
|
print(
|
|
@@ -244,9 +242,7 @@ class RunCommand:
|
|
|
244
242
|
# Run
|
|
245
243
|
# ---------------------------------------------------------
|
|
246
244
|
log_level = "DEBUG" if self.args.verbose else "INFO"
|
|
247
|
-
|
|
248
|
-
print(f"Starting reactor runtime v{__version__} ({runtime_name})")
|
|
249
|
-
print(f"Model: {reactor_config.name} ({reactor_config.model})")
|
|
245
|
+
detailed_logs = runtime_kwargs.pop("detailed_logs", False)
|
|
250
246
|
|
|
251
247
|
try:
|
|
252
248
|
run_reactor_runtime(
|
|
@@ -254,6 +250,7 @@ class RunCommand:
|
|
|
254
250
|
reactor_config=reactor_config,
|
|
255
251
|
model_root=str(model_path),
|
|
256
252
|
log_level=log_level,
|
|
253
|
+
show_timestamps=detailed_logs,
|
|
257
254
|
**runtime_kwargs,
|
|
258
255
|
)
|
|
259
256
|
except KeyboardInterrupt:
|
|
@@ -124,6 +124,13 @@ class RuntimeConfig:
|
|
|
124
124
|
argparse.ArgumentParser with runtime-specific arguments
|
|
125
125
|
"""
|
|
126
126
|
parser = argparse.ArgumentParser(add_help=False)
|
|
127
|
+
parser.add_argument(
|
|
128
|
+
"--detailed-logs",
|
|
129
|
+
action=argparse.BooleanOptionalAction,
|
|
130
|
+
default=False,
|
|
131
|
+
dest="detailed_logs",
|
|
132
|
+
help="Show timestamps and full log detail. Default: off.",
|
|
133
|
+
)
|
|
127
134
|
parser.add_argument(
|
|
128
135
|
"--orphan-timeout",
|
|
129
136
|
type=float,
|
|
@@ -236,7 +243,7 @@ class Runtime(ModelStateMachine, ABC):
|
|
|
236
243
|
)
|
|
237
244
|
self.model_thread.start()
|
|
238
245
|
|
|
239
|
-
logger.
|
|
246
|
+
logger.debug(
|
|
240
247
|
"Model loaded, emission started, run() thread spawned",
|
|
241
248
|
model=self.config.reactor_config.model,
|
|
242
249
|
)
|
{reactor_runtime-2.0.0 → reactor_runtime-2.0.2}/src/reactor_runtime/runtimes/http/http_runtime.py
RENAMED
|
@@ -22,6 +22,7 @@ Other:
|
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
24
|
import asyncio
|
|
25
|
+
import os
|
|
25
26
|
from typing import Optional
|
|
26
27
|
import numpy as np
|
|
27
28
|
from fastapi import FastAPI, HTTPException
|
|
@@ -510,6 +511,16 @@ class HttpRuntime(Runtime):
|
|
|
510
511
|
if self._server is not None:
|
|
511
512
|
self._server.should_exit = True
|
|
512
513
|
|
|
514
|
+
# -----------------------------------------------------------------
|
|
515
|
+
# State hooks
|
|
516
|
+
# -----------------------------------------------------------------
|
|
517
|
+
|
|
518
|
+
def on_before_initialization_success(self, **kwargs) -> None:
|
|
519
|
+
aqua, reset = "\033[96m", "\033[0m"
|
|
520
|
+
url = f"http://{self.config.host}:{self.config.port}"
|
|
521
|
+
logger.info("Started runtime process", pid=os.getpid())
|
|
522
|
+
logger.info(f"Reactor runtime running on {aqua}{url}{reset}")
|
|
523
|
+
|
|
513
524
|
# -----------------------------------------------------------------
|
|
514
525
|
# run()
|
|
515
526
|
# -----------------------------------------------------------------
|
|
@@ -522,7 +533,8 @@ class HttpRuntime(Runtime):
|
|
|
522
533
|
app=self.app,
|
|
523
534
|
host=self.config.host,
|
|
524
535
|
port=self.config.port,
|
|
525
|
-
log_level="
|
|
536
|
+
log_level="warning",
|
|
537
|
+
access_log=False,
|
|
526
538
|
)
|
|
527
539
|
self._server = uvicorn.Server(uvicorn_config)
|
|
528
540
|
|
|
@@ -254,25 +254,16 @@ def resolve_default_transport() -> TransportType:
|
|
|
254
254
|
"""Determine the best available transport when none is explicitly specified.
|
|
255
255
|
|
|
256
256
|
Tries GStreamer first (by importing the gst helper module which initializes
|
|
257
|
-
GStreamer), falling back to aiortc
|
|
257
|
+
GStreamer), falling back silently to aiortc if anything goes wrong.
|
|
258
258
|
"""
|
|
259
259
|
|
|
260
260
|
try:
|
|
261
261
|
from reactor_runtime.transports.gstreamer.gst import Gst # noqa: F401
|
|
262
|
-
except Exception
|
|
263
|
-
print(
|
|
264
|
-
f"{_ORANGE}GStreamer failed to initialize, falling back to aiortc. Error: {e}{_RESET}"
|
|
265
|
-
)
|
|
262
|
+
except Exception:
|
|
266
263
|
return TransportType.AIORTC
|
|
267
264
|
|
|
268
265
|
missing = _check_gstreamer_elements()
|
|
269
266
|
if missing:
|
|
270
|
-
formatted = ", ".join(missing)
|
|
271
|
-
print(
|
|
272
|
-
f"{_ORANGE}GStreamer is missing required elements: {formatted}\n"
|
|
273
|
-
f"Falling back to aiortc. "
|
|
274
|
-
f"Install the missing plugins: {GSTREAMER_INSTALL_URL}{_RESET}"
|
|
275
|
-
)
|
|
276
267
|
return TransportType.AIORTC
|
|
277
268
|
|
|
278
269
|
return TransportType.GSTREAMER
|
|
@@ -92,7 +92,6 @@ class H264DecoderBin(BaseRTPDecoderBin):
|
|
|
92
92
|
|
|
93
93
|
1) NVIDIA hardware decoders (GPU accelerated)
|
|
94
94
|
2) avdec_h264 (libav software decoder)
|
|
95
|
-
3) openh264dec (generic fallback)
|
|
96
95
|
|
|
97
96
|
Hardware decoder names vary depending on:
|
|
98
97
|
- Distribution
|
|
@@ -121,15 +120,6 @@ class H264DecoderBin(BaseRTPDecoderBin):
|
|
|
121
120
|
)
|
|
122
121
|
return "avdec_h264"
|
|
123
122
|
|
|
124
|
-
# ---------------------------------------------------------
|
|
125
|
-
# Generic fallback (if present in build)
|
|
126
|
-
# ---------------------------------------------------------
|
|
127
|
-
if Gst.ElementFactory.find("openh264dec") is not None:
|
|
128
|
-
logger.info(
|
|
129
|
-
"H264DecoderBin: using software (SW) decoder: openh264dec",
|
|
130
|
-
)
|
|
131
|
-
return "openh264dec"
|
|
132
|
-
|
|
133
123
|
raise RuntimeError(
|
|
134
|
-
"No H264 decoder available (tried: nvh264dec, nvdec_h264, nvv4l2decoder, avdec_h264
|
|
124
|
+
"No H264 decoder available (tried: nvh264dec, nvdec_h264, nvv4l2decoder, avdec_h264)"
|
|
135
125
|
)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
|
|
2
|
+
"""Entry point for running the Reactor runtime."""
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Callable, List, Optional
|
|
9
|
+
|
|
10
|
+
from reactor_runtime import __version__
|
|
11
|
+
from reactor_runtime.config import ReactorConfig
|
|
12
|
+
from reactor_runtime.utils.log import (
|
|
13
|
+
LOG_DATEFMT,
|
|
14
|
+
LOG_FORMAT,
|
|
15
|
+
LOG_FORMAT_BRIEF,
|
|
16
|
+
ColorFormatter,
|
|
17
|
+
get_logger,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def add_import_paths(paths: List[str]) -> None:
|
|
24
|
+
"""Prepend provided directories to sys.path if they exist."""
|
|
25
|
+
for p in paths:
|
|
26
|
+
if not p:
|
|
27
|
+
continue
|
|
28
|
+
ap = os.path.abspath(p)
|
|
29
|
+
if os.path.isdir(ap) and ap not in sys.path:
|
|
30
|
+
sys.path.insert(0, ap)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def configure_logging(level: str, show_timestamps: bool = True) -> None:
|
|
34
|
+
fmt = LOG_FORMAT if show_timestamps else LOG_FORMAT_BRIEF
|
|
35
|
+
handler = logging.StreamHandler()
|
|
36
|
+
handler.setFormatter(ColorFormatter(fmt, datefmt=LOG_DATEFMT))
|
|
37
|
+
logging.root.addHandler(handler)
|
|
38
|
+
logging.root.setLevel(getattr(logging, level.upper(), logging.INFO))
|
|
39
|
+
logging.getLogger("aiortc").setLevel(logging.WARNING)
|
|
40
|
+
logging.getLogger("av").setLevel(logging.WARNING)
|
|
41
|
+
logging.getLogger("aioice").setLevel(logging.WARNING)
|
|
42
|
+
logging.getLogger("uvicorn").setLevel(logging.WARNING)
|
|
43
|
+
logging.getLogger("uvicorn.error").setLevel(logging.WARNING)
|
|
44
|
+
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _infer_runtime_name(serve_fn: Callable) -> str:
|
|
48
|
+
"""Derive a short runtime label from the serve function's module path.
|
|
49
|
+
|
|
50
|
+
e.g. ``reactor_runtime.runtimes.http.http_runtime`` -> ``http``
|
|
51
|
+
``reactor_runtime.runtimes._redis._redis_runtime`` -> ``redis``
|
|
52
|
+
"""
|
|
53
|
+
parts = getattr(serve_fn, "__module__", "").split(".")
|
|
54
|
+
if len(parts) >= 3:
|
|
55
|
+
return parts[2].lstrip("_")
|
|
56
|
+
return "unknown"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def run_reactor_runtime(
|
|
60
|
+
runtime_serve_fn: Callable,
|
|
61
|
+
reactor_config: ReactorConfig,
|
|
62
|
+
model_root: Optional[str] = None,
|
|
63
|
+
log_level: str = "INFO",
|
|
64
|
+
show_timestamps: bool = True,
|
|
65
|
+
**runtime_kwargs,
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Run the Reactor runtime.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
runtime_serve_fn: The ``serve()`` function from the runtime module.
|
|
71
|
+
reactor_config: Parsed ``reactor.yaml`` (with ``config`` resolved
|
|
72
|
+
to an absolute path and ``config_overrides`` populated).
|
|
73
|
+
model_root: Directory to add to sys.path for model imports.
|
|
74
|
+
log_level: Logging level.
|
|
75
|
+
show_timestamps: Include timestamps in log output (disabled for local
|
|
76
|
+
HTTP runtime where output is watched interactively).
|
|
77
|
+
**runtime_kwargs: Runtime-specific arguments passed to serve().
|
|
78
|
+
"""
|
|
79
|
+
configure_logging(log_level, show_timestamps=show_timestamps)
|
|
80
|
+
|
|
81
|
+
runtime_name = _infer_runtime_name(runtime_serve_fn)
|
|
82
|
+
logger.info(
|
|
83
|
+
"Starting reactor runtime",
|
|
84
|
+
version=__version__,
|
|
85
|
+
runtime=runtime_name,
|
|
86
|
+
)
|
|
87
|
+
model_kw: dict = {
|
|
88
|
+
"name": reactor_config.name,
|
|
89
|
+
"entrypoint": reactor_config.model,
|
|
90
|
+
}
|
|
91
|
+
if reactor_config.config:
|
|
92
|
+
model_kw["config"] = reactor_config.config
|
|
93
|
+
logger.info("Model detected", **model_kw)
|
|
94
|
+
|
|
95
|
+
if model_root is not None:
|
|
96
|
+
add_import_paths([model_root])
|
|
97
|
+
|
|
98
|
+
asyncio.run(
|
|
99
|
+
runtime_serve_fn(
|
|
100
|
+
reactor_config=reactor_config,
|
|
101
|
+
**runtime_kwargs,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
@@ -20,13 +20,46 @@ from typing import Any, Optional
|
|
|
20
20
|
session_id_var: ContextVar[Optional[str]] = ContextVar("session_id", default=None)
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s: %(message)s"
|
|
24
|
+
LOG_FORMAT_BRIEF = "%(levelname)s %(message)s"
|
|
25
25
|
LOG_DATEFMT = "%Y-%m-%dT%H:%M:%S%z"
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
_RESET = "\033[0m"
|
|
28
|
+
|
|
29
|
+
_LEVEL_COLORS: dict[int, str] = {
|
|
30
|
+
logging.DEBUG: "\033[37m", # gray
|
|
31
|
+
logging.INFO: "\033[32m", # green
|
|
32
|
+
logging.WARNING: "\033[33m", # yellow
|
|
33
|
+
logging.ERROR: "\033[31m", # red
|
|
34
|
+
logging.CRITICAL: "\033[1;31m", # bold red
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
_RUNTIME_PREFIXES = ("reactor_runtime.", "reactor_cli.")
|
|
39
|
+
_MODEL_COLOR = "\033[36m"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ColorFormatter(logging.Formatter):
|
|
43
|
+
"""Formatter that colorizes log levels and tags model logs."""
|
|
44
|
+
|
|
45
|
+
_COL = 12 # visual width: fits "CRITICAL MDL"
|
|
46
|
+
|
|
47
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
48
|
+
orig = record.levelname
|
|
49
|
+
color = _LEVEL_COLORS.get(record.levelno, "")
|
|
50
|
+
|
|
51
|
+
if record.name.startswith(_RUNTIME_PREFIXES):
|
|
52
|
+
pad = self._COL - len(orig)
|
|
53
|
+
record.levelname = f"{color}{orig}{_RESET}{' ' * pad}"
|
|
54
|
+
else:
|
|
55
|
+
pad = self._COL - len(orig) - 4 # 4 = len(" MDL")
|
|
56
|
+
record.levelname = (
|
|
57
|
+
f"{color}{orig}{_RESET} {_MODEL_COLOR}MDL{_RESET}{' ' * max(pad, 0)}"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
result = super().format(record)
|
|
61
|
+
record.levelname = orig
|
|
62
|
+
return result
|
|
30
63
|
|
|
31
64
|
|
|
32
65
|
class StructuredLogger:
|
|
@@ -39,15 +72,18 @@ class StructuredLogger:
|
|
|
39
72
|
|
|
40
73
|
# -- internal helpers --------------------------------------------------
|
|
41
74
|
|
|
75
|
+
_BLUE = "\033[38;5;25m"
|
|
76
|
+
_RESET = "\033[0m"
|
|
77
|
+
|
|
42
78
|
@staticmethod
|
|
43
79
|
def _fmt(msg: str, kw: dict[str, Any]) -> str:
|
|
44
|
-
# Auto-inject session_id from ContextVar if not explicitly provided.
|
|
45
80
|
sid = session_id_var.get()
|
|
46
81
|
if sid and "session_id" not in kw:
|
|
47
82
|
kw["session_id"] = sid
|
|
48
83
|
if not kw:
|
|
49
84
|
return msg
|
|
50
|
-
|
|
85
|
+
b, r = StructuredLogger._BLUE, StructuredLogger._RESET
|
|
86
|
+
pairs = " ".join(f"{b}{k}={r}{v}" for k, v in kw.items())
|
|
51
87
|
return f"{msg} {pairs}"
|
|
52
88
|
|
|
53
89
|
# -- public API --------------------------------------------------------
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# My Reactor Model
|
|
2
|
+
|
|
3
|
+
A real-time video model built with Reactor Runtime.
|
|
4
|
+
|
|
5
|
+
## Setup and run
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -r requirements.txt
|
|
9
|
+
reactor run
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
This starts the model locally. Connect with a WebRTC client at the URL printed by the runtime.
|
|
13
|
+
|
|
14
|
+
## Project structure
|
|
15
|
+
|
|
16
|
+
| File | Purpose |
|
|
17
|
+
|------|---------|
|
|
18
|
+
| `pipeline.py` | **ReactorPipeline** — generator pattern with auto state management (default) |
|
|
19
|
+
| `model.py` | **ReactorModel** — manual run loop with explicit events and lifecycle |
|
|
20
|
+
| `reactor.yaml` | Tells the runtime where to find your model class |
|
|
21
|
+
| `config.yml` | Model configuration (passed to `load()` as a dict) |
|
|
22
|
+
| `requirements.txt` | Extra Python dependencies |
|
|
23
|
+
|
|
24
|
+
## How it works
|
|
25
|
+
|
|
26
|
+
- **`MyOutput`** declares the video track the model sends to clients.
|
|
27
|
+
- **`MyState`** declares parameters clients can change in real-time. Each field auto-generates a `set_<field>` event — no handler code needed.
|
|
28
|
+
- **`inference()`** is a generator that yields frames in batches. Read `self.state` to pick up the latest client values.
|
|
29
|
+
- **`load()`** runs once at startup — put weight loading here.
|
|
30
|
+
|
|
31
|
+
## Switching to the ReactorModel version
|
|
32
|
+
|
|
33
|
+
Edit `reactor.yaml` and change the model entry:
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
model: model:MyModel
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then run again. The ReactorModel version gives you full control over the run loop, events, and session lifecycle.
|
|
40
|
+
|
|
41
|
+
## Next steps
|
|
42
|
+
|
|
43
|
+
- Add `Input` tracks to receive webcam video from the client
|
|
44
|
+
- Add `@event` handlers for custom logic (e.g. encoding a prompt)
|
|
45
|
+
- Add `ModelMessage` subclasses to send structured data back to the client
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
|
|
2
|
+
"""
|
|
3
|
+
My first Reactor model — ReactorModel version.
|
|
4
|
+
|
|
5
|
+
Same model as pipeline.py, but using the lower-level ReactorModel base
|
|
6
|
+
class where you manage the run loop, events, and session lifecycle
|
|
7
|
+
explicitly.
|
|
8
|
+
|
|
9
|
+
Run with: reactor run (after updating reactor.yaml to point here)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from reactor_runtime.interface import (
|
|
16
|
+
InputField,
|
|
17
|
+
ReactorModel,
|
|
18
|
+
connected,
|
|
19
|
+
disconnected,
|
|
20
|
+
event,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from pipeline import MyOutput, render_batch
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# -- Model ---------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MyModel(ReactorModel):
|
|
30
|
+
fps = 30
|
|
31
|
+
buffer_size = 8
|
|
32
|
+
|
|
33
|
+
def load(self, config: dict[str, Any]) -> None:
|
|
34
|
+
self.width = config.get("width", 640)
|
|
35
|
+
self.height = config.get("height", 480)
|
|
36
|
+
self.chunk_size = config.get("chunk_size", 4)
|
|
37
|
+
self.brightness = 1.0
|
|
38
|
+
|
|
39
|
+
@connected
|
|
40
|
+
def on_connect(self):
|
|
41
|
+
self.brightness = 1.0
|
|
42
|
+
|
|
43
|
+
@disconnected
|
|
44
|
+
def on_disconnect(self):
|
|
45
|
+
self.output_buffer.flush()
|
|
46
|
+
|
|
47
|
+
@event(name="set_brightness", description="Brightness multiplier")
|
|
48
|
+
def set_brightness(
|
|
49
|
+
self,
|
|
50
|
+
brightness: float = InputField(default=1.0, ge=0.0, le=2.0),
|
|
51
|
+
):
|
|
52
|
+
self.brightness = brightness
|
|
53
|
+
|
|
54
|
+
async def run(self) -> None:
|
|
55
|
+
while True:
|
|
56
|
+
await self.connected.wait()
|
|
57
|
+
frame_idx = 0
|
|
58
|
+
|
|
59
|
+
while self.connected.is_set():
|
|
60
|
+
# Sync work is fine here — events dispatch at await points
|
|
61
|
+
batch, frame_idx = render_batch(
|
|
62
|
+
self.width,
|
|
63
|
+
self.height,
|
|
64
|
+
self.chunk_size,
|
|
65
|
+
frame_idx,
|
|
66
|
+
self.brightness,
|
|
67
|
+
)
|
|
68
|
+
await self.emit(MyOutput(main_video=batch))
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
|
|
2
|
+
"""
|
|
3
|
+
My first Reactor model — ReactorPipeline version.
|
|
4
|
+
|
|
5
|
+
Generates animated gradient frames with a client-controllable brightness
|
|
6
|
+
multiplier. Frames are produced in configurable chunks to demonstrate
|
|
7
|
+
batch yielding.
|
|
8
|
+
|
|
9
|
+
Run with: reactor run
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Any
|
|
14
|
+
import time
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
from PIL import Image, ImageDraw
|
|
18
|
+
|
|
19
|
+
from reactor_runtime.interface import (
|
|
20
|
+
InputField,
|
|
21
|
+
InputState,
|
|
22
|
+
Output,
|
|
23
|
+
ReactorPipeline,
|
|
24
|
+
Video,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# -- Tracks --------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class MyOutput(Output):
|
|
33
|
+
main_video: Video
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# -- State ---------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class MyState(InputState):
|
|
41
|
+
brightness: float = InputField(
|
|
42
|
+
default=1.0, ge=0.0, le=2.0, description="Brightness multiplier"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# -- Model ---------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class MyModel(ReactorPipeline):
|
|
50
|
+
state: MyState
|
|
51
|
+
buffer_size = 8
|
|
52
|
+
|
|
53
|
+
def load(self, config: dict[str, Any]) -> None:
|
|
54
|
+
self.width = config.get("width", 640)
|
|
55
|
+
self.height = config.get("height", 480)
|
|
56
|
+
self.chunk_size = config.get("chunk_size", 4)
|
|
57
|
+
|
|
58
|
+
def inference(self):
|
|
59
|
+
frame_idx = 0
|
|
60
|
+
while True:
|
|
61
|
+
batch, frame_idx = render_batch(
|
|
62
|
+
self.width,
|
|
63
|
+
self.height,
|
|
64
|
+
self.chunk_size,
|
|
65
|
+
frame_idx,
|
|
66
|
+
self.state.brightness,
|
|
67
|
+
)
|
|
68
|
+
yield MyOutput(main_video=batch)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# -- Rendering -----------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
_FRAME_BUDGET = 1 / 30
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def render_batch(
|
|
78
|
+
width: int,
|
|
79
|
+
height: int,
|
|
80
|
+
chunk_size: int,
|
|
81
|
+
start_idx: int,
|
|
82
|
+
brightness: float,
|
|
83
|
+
) -> tuple[np.ndarray, int]:
|
|
84
|
+
"""Generate a batch of animated gradient frames with brightness applied.
|
|
85
|
+
|
|
86
|
+
Returns the batch as ``(N, H, W, 3)`` uint8 and the next frame index.
|
|
87
|
+
"""
|
|
88
|
+
frames = []
|
|
89
|
+
for i in range(chunk_size):
|
|
90
|
+
idx = start_idx + i
|
|
91
|
+
t0 = time.perf_counter()
|
|
92
|
+
|
|
93
|
+
img = np.zeros((height, width, 3), dtype=np.float32)
|
|
94
|
+
ys = np.linspace(0, 1, height, dtype=np.float32)[:, None]
|
|
95
|
+
img[:, :, 0] = ys * 128
|
|
96
|
+
img[:, :, 2] = (1 - ys) * 128
|
|
97
|
+
|
|
98
|
+
bar_x = (idx * 4) % width
|
|
99
|
+
bar_w = max(1, width // 20)
|
|
100
|
+
img[:, bar_x : min(width, bar_x + bar_w), 1] = 128
|
|
101
|
+
|
|
102
|
+
img = np.clip(img * brightness, 0, 255).astype(np.uint8)
|
|
103
|
+
img = draw_text(img, f"Frame {idx} | Brightness {brightness:.1f}")
|
|
104
|
+
frames.append(img)
|
|
105
|
+
|
|
106
|
+
remaining = _FRAME_BUDGET - (time.perf_counter() - t0)
|
|
107
|
+
if remaining > 0:
|
|
108
|
+
time.sleep(remaining)
|
|
109
|
+
|
|
110
|
+
return np.stack(frames), start_idx + chunk_size
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def draw_text(
|
|
114
|
+
frame: np.ndarray,
|
|
115
|
+
text: str,
|
|
116
|
+
position: tuple = (16, 30),
|
|
117
|
+
) -> np.ndarray:
|
|
118
|
+
"""Draw white text with a dark outline onto a frame."""
|
|
119
|
+
img = Image.fromarray(frame)
|
|
120
|
+
draw = ImageDraw.Draw(img)
|
|
121
|
+
x, y = position
|
|
122
|
+
for dx, dy in [(-1, -1), (-1, 1), (1, -1), (1, 1)]:
|
|
123
|
+
draw.text((x + dx, y + dy), text, fill=(0, 0, 0))
|
|
124
|
+
draw.text((x, y), text, fill=(255, 255, 255))
|
|
125
|
+
return np.array(img)
|