c64cast 0.1.0__py3-none-any.whl
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.
- c64cast/__init__.py +15 -0
- c64cast/__main__.py +5 -0
- c64cast/_native_io.py +43 -0
- c64cast/_pollthread.py +78 -0
- c64cast/api.py +1423 -0
- c64cast/asid.py +231 -0
- c64cast/asid_player.py +789 -0
- c64cast/asid_scene.py +742 -0
- c64cast/asid_sidmap.py +432 -0
- c64cast/audio.py +3114 -0
- c64cast/audio_features.py +397 -0
- c64cast/audio_marker.py +119 -0
- c64cast/audio_source.py +759 -0
- c64cast/backend.py +732 -0
- c64cast/backgrounds.py +478 -0
- c64cast/bitmap_text.py +136 -0
- c64cast/c64.py +505 -0
- c64cast/camera.py +238 -0
- c64cast/cli.py +1917 -0
- c64cast/config.py +5613 -0
- c64cast/config_serialize.py +303 -0
- c64cast/connect.py +203 -0
- c64cast/control_plane.py +240 -0
- c64cast/dac_calibration.py +1485 -0
- c64cast/dac_curves.py +351 -0
- c64cast/data/c64cast.schema.json +2594 -0
- c64cast/dither.py +360 -0
- c64cast/doctor.py +1954 -0
- c64cast/dsp.py +497 -0
- c64cast/effects.py +428 -0
- c64cast/ensemble.py +175 -0
- c64cast/examples/audio-reactive-input.toml +54 -0
- c64cast/examples/audio-reactive-listen.toml +56 -0
- c64cast/examples/c64cast.example.toml +1467 -0
- c64cast/examples/color-dither.toml +41 -0
- c64cast/examples/color-force-palette.toml +44 -0
- c64cast/examples/effect-chain.toml +64 -0
- c64cast/examples/effect-reactive.toml +52 -0
- c64cast/examples/effect-trails.toml +29 -0
- c64cast/examples/ensemble/left.toml +55 -0
- c64cast/examples/ensemble/master.toml +33 -0
- c64cast/examples/ensemble/middle.toml +44 -0
- c64cast/examples/ensemble/right.toml +57 -0
- c64cast/examples/hello.toml +49 -0
- c64cast/examples/menu.toml +45 -0
- c64cast/examples/overlay-big_text.toml +43 -0
- c64cast/examples/overlay-callsign.toml +28 -0
- c64cast/examples/overlay-clock.toml +30 -0
- c64cast/examples/overlay-countdown.toml +31 -0
- c64cast/examples/overlay-logo.toml +32 -0
- c64cast/examples/overlay-marquee.toml +29 -0
- c64cast/examples/overlay-network.toml +30 -0
- c64cast/examples/overlay-obs_status.toml +39 -0
- c64cast/examples/overlay-rss.toml +30 -0
- c64cast/examples/overlay-scrolling_text.toml +35 -0
- c64cast/examples/overlay-spectrum_bitmap.toml +54 -0
- c64cast/examples/overlay-spectrum_petscii.toml +38 -0
- c64cast/examples/overlay-weather.toml +39 -0
- c64cast/examples/performance-clips.toml +88 -0
- c64cast/examples/scene-asid.toml +74 -0
- c64cast/examples/scene-blank.toml +32 -0
- c64cast/examples/scene-generative-colored-bursts.toml +26 -0
- c64cast/examples/scene-generative-dna.toml +30 -0
- c64cast/examples/scene-generative-dotswarm.toml +27 -0
- c64cast/examples/scene-generative-drift.toml +25 -0
- c64cast/examples/scene-generative-epicycle.toml +30 -0
- c64cast/examples/scene-generative-fire.toml +44 -0
- c64cast/examples/scene-generative-fireworks.toml +32 -0
- c64cast/examples/scene-generative-game-of-life.toml +25 -0
- c64cast/examples/scene-generative-halo.toml +28 -0
- c64cast/examples/scene-generative-hiphotic.toml +27 -0
- c64cast/examples/scene-generative-hopalong.toml +31 -0
- c64cast/examples/scene-generative-lissajous.toml +27 -0
- c64cast/examples/scene-generative-mandelbrot.toml +34 -0
- c64cast/examples/scene-generative-metaballs.toml +28 -0
- c64cast/examples/scene-generative-moire2.toml +29 -0
- c64cast/examples/scene-generative-petscii.toml +24 -0
- c64cast/examples/scene-generative-plasma.toml +30 -0
- c64cast/examples/scene-generative-rorschach.toml +30 -0
- c64cast/examples/scene-generative-rotozoomer.toml +33 -0
- c64cast/examples/scene-generative-sid.toml +45 -0
- c64cast/examples/scene-generative-soap.toml +32 -0
- c64cast/examples/scene-launcher.toml +53 -0
- c64cast/examples/scene-midi.toml +64 -0
- c64cast/examples/scene-slideshow.toml +54 -0
- c64cast/examples/scene-video-sampler.toml +48 -0
- c64cast/examples/scene-video.toml +48 -0
- c64cast/examples/scene-waveform.toml +49 -0
- c64cast/examples/scene-webcam-audio.toml +40 -0
- c64cast/examples/scene-webcam-hires.toml +23 -0
- c64cast/examples/scene-webcam-hires_edges.toml +23 -0
- c64cast/examples/scene-webcam-mcm.toml +26 -0
- c64cast/examples/scene-webcam-mhires.toml +30 -0
- c64cast/examples/scene-webcam-petscii.toml +27 -0
- c64cast/examples/scene-wled.toml +37 -0
- c64cast/examples/teensyrom-blank.toml +51 -0
- c64cast/examples/vision-gesture.toml +48 -0
- c64cast/examples/vision-modes.toml +77 -0
- c64cast/examples/wled-control.toml +198 -0
- c64cast/frame_source.py +63 -0
- c64cast/framebuffer.py +267 -0
- c64cast/generators.py +1756 -0
- c64cast/interstitial.py +190 -0
- c64cast/introspect.py +847 -0
- c64cast/keyboard.py +293 -0
- c64cast/midi_control.py +1149 -0
- c64cast/midi_scene.py +991 -0
- c64cast/midi_setup.py +471 -0
- c64cast/modes.py +3362 -0
- c64cast/modulation.py +164 -0
- c64cast/music_features.py +246 -0
- c64cast/orchestrator.py +213 -0
- c64cast/orchestrators/__init__.py +11 -0
- c64cast/orchestrators/big_text_span.py +156 -0
- c64cast/overlays/__init__.py +300 -0
- c64cast/overlays/_spectrum.py +215 -0
- c64cast/overlays/big_text.py +766 -0
- c64cast/overlays/callsign.py +31 -0
- c64cast/overlays/clock.py +40 -0
- c64cast/overlays/corner_text.py +122 -0
- c64cast/overlays/countdown.py +74 -0
- c64cast/overlays/logo.py +157 -0
- c64cast/overlays/marquee.py +112 -0
- c64cast/overlays/menu.py +366 -0
- c64cast/overlays/network.py +127 -0
- c64cast/overlays/obs_status.py +148 -0
- c64cast/overlays/rss.py +112 -0
- c64cast/overlays/scrolling_text.py +169 -0
- c64cast/overlays/spectrum_bitmap.py +181 -0
- c64cast/overlays/spectrum_petscii.py +124 -0
- c64cast/overlays/weather.py +176 -0
- c64cast/palette.py +1272 -0
- c64cast/paths.py +255 -0
- c64cast/perf_console.py +664 -0
- c64cast/performance.py +712 -0
- c64cast/petscii_styles.py +383 -0
- c64cast/playlist.py +1404 -0
- c64cast/preview.py +208 -0
- c64cast/profiler.py +219 -0
- c64cast/py.typed +0 -0
- c64cast/quickcast.py +423 -0
- c64cast/recording_metadata.py +284 -0
- c64cast/rolling_palette.py +146 -0
- c64cast/sampler.py +778 -0
- c64cast/scenes.py +2262 -0
- c64cast/schema.py +219 -0
- c64cast/sid_autoconfig.py +267 -0
- c64cast/sid_host_emu.py +694 -0
- c64cast/sid_hw_config.py +198 -0
- c64cast/sid_panning.py +347 -0
- c64cast/sid_volume.py +260 -0
- c64cast/sidemu.py +301 -0
- c64cast/socket_dma.py +329 -0
- c64cast/songlengths.py +115 -0
- c64cast/teensyrom_api.py +472 -0
- c64cast/teensyrom_dma.py +597 -0
- c64cast/tempo.py +370 -0
- c64cast/text_surface.py +254 -0
- c64cast/transport.py +623 -0
- c64cast/video.py +1017 -0
- c64cast/vision.py +552 -0
- c64cast/voice_scope.py +847 -0
- c64cast/waveform.py +1671 -0
- c64cast/wizard.py +850 -0
- c64cast/wled_device.py +1344 -0
- c64cast/wled_sink.py +341 -0
- c64cast/wled_sync.py +218 -0
- c64cast-0.1.0.dist-info/METADATA +477 -0
- c64cast-0.1.0.dist-info/RECORD +173 -0
- c64cast-0.1.0.dist-info/WHEEL +5 -0
- c64cast-0.1.0.dist-info/entry_points.txt +2 -0
- c64cast-0.1.0.dist-info/licenses/LICENSE +21 -0
- c64cast-0.1.0.dist-info/top_level.txt +1 -0
c64cast/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Video/audio streaming to Ultimate 64 hardware over the U64 REST API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import PackageNotFoundError
|
|
6
|
+
from importlib.metadata import version as _pkg_version
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
__version__ = _pkg_version("c64cast")
|
|
10
|
+
except PackageNotFoundError:
|
|
11
|
+
# Package not installed (running from a source checkout without
|
|
12
|
+
# `uv sync`). Fall back to a sentinel rather than crashing.
|
|
13
|
+
__version__ = "0+unknown"
|
|
14
|
+
|
|
15
|
+
__all__ = ["__version__"]
|
c64cast/__main__.py
ADDED
c64cast/_native_io.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Process-level stderr muting for native-library chatter.
|
|
2
|
+
|
|
3
|
+
Some of our native dependencies write diagnostics straight to file
|
|
4
|
+
descriptor 2, bypassing Python's `logging`, `sys.stderr`, and any
|
|
5
|
+
library-level verbosity flags:
|
|
6
|
+
|
|
7
|
+
* MediaPipe's C++/absl logging (GL/XNNPACK init, the benign "feedback
|
|
8
|
+
manager" + "landmark_projection NORM_RECT square ROI" warnings).
|
|
9
|
+
* OpenCV's AVFoundation/FFmpeg backend, when probing camera indices past
|
|
10
|
+
the highest valid one.
|
|
11
|
+
* The Obj-C runtime's "Class AVFFrameReceiver/AVFAudioReceiver is
|
|
12
|
+
implemented in both ..." warning, emitted once when PyAV's bundled
|
|
13
|
+
libavdevice loads on top of cv2's (different major versions, same
|
|
14
|
+
AVFoundation device classes) — harmless, neither file-decode path uses
|
|
15
|
+
the avfoundation input device.
|
|
16
|
+
|
|
17
|
+
An fd-level redirect is the only thing that catches these. Scope it as
|
|
18
|
+
tightly as possible (around the single import / construction / probe that
|
|
19
|
+
emits the noise, on the main thread before worker threads start) so it
|
|
20
|
+
never swallows real stderr from elsewhere.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import contextlib
|
|
26
|
+
import os
|
|
27
|
+
import sys
|
|
28
|
+
from collections.abc import Iterator
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@contextlib.contextmanager
|
|
32
|
+
def silence_native_stderr() -> Iterator[None]:
|
|
33
|
+
"""Temporarily redirect the process stderr fd (2) to /dev/null."""
|
|
34
|
+
sys.stderr.flush()
|
|
35
|
+
saved = os.dup(2)
|
|
36
|
+
devnull = os.open(os.devnull, os.O_WRONLY)
|
|
37
|
+
try:
|
|
38
|
+
os.dup2(devnull, 2)
|
|
39
|
+
os.close(devnull)
|
|
40
|
+
yield
|
|
41
|
+
finally:
|
|
42
|
+
os.dup2(saved, 2)
|
|
43
|
+
os.close(saved)
|
c64cast/_pollthread.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Background daemon thread with start/stop boilerplate.
|
|
2
|
+
|
|
3
|
+
Most sites use the simple form — a function called repeatedly on a fixed
|
|
4
|
+
cadence with a stop event handling shutdown:
|
|
5
|
+
|
|
6
|
+
self._poll = PollThread(self._fetch, period=10.0, name="rss-poll")
|
|
7
|
+
self._poll.start() # in setup()
|
|
8
|
+
self._poll.stop() # in teardown()
|
|
9
|
+
|
|
10
|
+
For variable-cadence loops (e.g. exponential backoff) pass `manual=True`
|
|
11
|
+
and the target owns its own loop and pacing:
|
|
12
|
+
|
|
13
|
+
self._poll = PollThread(self._worker, name="obs-status", manual=True)
|
|
14
|
+
# worker signature: def _worker(stop: threading.Event) -> None
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import threading
|
|
20
|
+
from collections.abc import Callable
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PollThread:
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
target: Callable,
|
|
27
|
+
*,
|
|
28
|
+
name: str,
|
|
29
|
+
period: float | None = None,
|
|
30
|
+
run_first: bool = True,
|
|
31
|
+
manual: bool = False,
|
|
32
|
+
join_timeout: float = 0.5,
|
|
33
|
+
):
|
|
34
|
+
if manual and period is not None:
|
|
35
|
+
raise ValueError("PollThread: period only meaningful when manual=False")
|
|
36
|
+
if not manual and period is None:
|
|
37
|
+
raise ValueError("PollThread: period required when manual=False")
|
|
38
|
+
self._target = target
|
|
39
|
+
self._name = name
|
|
40
|
+
self._period = period
|
|
41
|
+
self._run_first = run_first
|
|
42
|
+
self._manual = manual
|
|
43
|
+
self._join_timeout = join_timeout
|
|
44
|
+
self._stop = threading.Event()
|
|
45
|
+
self._thread: threading.Thread | None = None
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def stop_event(self) -> threading.Event:
|
|
49
|
+
return self._stop
|
|
50
|
+
|
|
51
|
+
def is_running(self) -> bool:
|
|
52
|
+
return self._thread is not None and self._thread.is_alive()
|
|
53
|
+
|
|
54
|
+
def start(self) -> None:
|
|
55
|
+
if self.is_running():
|
|
56
|
+
return
|
|
57
|
+
self._stop.clear()
|
|
58
|
+
self._thread = threading.Thread(target=self._run, daemon=True, name=self._name)
|
|
59
|
+
self._thread.start()
|
|
60
|
+
|
|
61
|
+
def stop(self) -> None:
|
|
62
|
+
self._stop.set()
|
|
63
|
+
if self._thread is not None:
|
|
64
|
+
self._thread.join(timeout=self._join_timeout)
|
|
65
|
+
self._thread = None
|
|
66
|
+
|
|
67
|
+
def _run(self) -> None:
|
|
68
|
+
if self._manual:
|
|
69
|
+
self._target(self._stop)
|
|
70
|
+
return
|
|
71
|
+
assert self._period is not None
|
|
72
|
+
if self._run_first:
|
|
73
|
+
while not self._stop.is_set():
|
|
74
|
+
self._target()
|
|
75
|
+
self._stop.wait(self._period)
|
|
76
|
+
else:
|
|
77
|
+
while not self._stop.wait(self._period):
|
|
78
|
+
self._target()
|