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.
Files changed (173) hide show
  1. c64cast/__init__.py +15 -0
  2. c64cast/__main__.py +5 -0
  3. c64cast/_native_io.py +43 -0
  4. c64cast/_pollthread.py +78 -0
  5. c64cast/api.py +1423 -0
  6. c64cast/asid.py +231 -0
  7. c64cast/asid_player.py +789 -0
  8. c64cast/asid_scene.py +742 -0
  9. c64cast/asid_sidmap.py +432 -0
  10. c64cast/audio.py +3114 -0
  11. c64cast/audio_features.py +397 -0
  12. c64cast/audio_marker.py +119 -0
  13. c64cast/audio_source.py +759 -0
  14. c64cast/backend.py +732 -0
  15. c64cast/backgrounds.py +478 -0
  16. c64cast/bitmap_text.py +136 -0
  17. c64cast/c64.py +505 -0
  18. c64cast/camera.py +238 -0
  19. c64cast/cli.py +1917 -0
  20. c64cast/config.py +5613 -0
  21. c64cast/config_serialize.py +303 -0
  22. c64cast/connect.py +203 -0
  23. c64cast/control_plane.py +240 -0
  24. c64cast/dac_calibration.py +1485 -0
  25. c64cast/dac_curves.py +351 -0
  26. c64cast/data/c64cast.schema.json +2594 -0
  27. c64cast/dither.py +360 -0
  28. c64cast/doctor.py +1954 -0
  29. c64cast/dsp.py +497 -0
  30. c64cast/effects.py +428 -0
  31. c64cast/ensemble.py +175 -0
  32. c64cast/examples/audio-reactive-input.toml +54 -0
  33. c64cast/examples/audio-reactive-listen.toml +56 -0
  34. c64cast/examples/c64cast.example.toml +1467 -0
  35. c64cast/examples/color-dither.toml +41 -0
  36. c64cast/examples/color-force-palette.toml +44 -0
  37. c64cast/examples/effect-chain.toml +64 -0
  38. c64cast/examples/effect-reactive.toml +52 -0
  39. c64cast/examples/effect-trails.toml +29 -0
  40. c64cast/examples/ensemble/left.toml +55 -0
  41. c64cast/examples/ensemble/master.toml +33 -0
  42. c64cast/examples/ensemble/middle.toml +44 -0
  43. c64cast/examples/ensemble/right.toml +57 -0
  44. c64cast/examples/hello.toml +49 -0
  45. c64cast/examples/menu.toml +45 -0
  46. c64cast/examples/overlay-big_text.toml +43 -0
  47. c64cast/examples/overlay-callsign.toml +28 -0
  48. c64cast/examples/overlay-clock.toml +30 -0
  49. c64cast/examples/overlay-countdown.toml +31 -0
  50. c64cast/examples/overlay-logo.toml +32 -0
  51. c64cast/examples/overlay-marquee.toml +29 -0
  52. c64cast/examples/overlay-network.toml +30 -0
  53. c64cast/examples/overlay-obs_status.toml +39 -0
  54. c64cast/examples/overlay-rss.toml +30 -0
  55. c64cast/examples/overlay-scrolling_text.toml +35 -0
  56. c64cast/examples/overlay-spectrum_bitmap.toml +54 -0
  57. c64cast/examples/overlay-spectrum_petscii.toml +38 -0
  58. c64cast/examples/overlay-weather.toml +39 -0
  59. c64cast/examples/performance-clips.toml +88 -0
  60. c64cast/examples/scene-asid.toml +74 -0
  61. c64cast/examples/scene-blank.toml +32 -0
  62. c64cast/examples/scene-generative-colored-bursts.toml +26 -0
  63. c64cast/examples/scene-generative-dna.toml +30 -0
  64. c64cast/examples/scene-generative-dotswarm.toml +27 -0
  65. c64cast/examples/scene-generative-drift.toml +25 -0
  66. c64cast/examples/scene-generative-epicycle.toml +30 -0
  67. c64cast/examples/scene-generative-fire.toml +44 -0
  68. c64cast/examples/scene-generative-fireworks.toml +32 -0
  69. c64cast/examples/scene-generative-game-of-life.toml +25 -0
  70. c64cast/examples/scene-generative-halo.toml +28 -0
  71. c64cast/examples/scene-generative-hiphotic.toml +27 -0
  72. c64cast/examples/scene-generative-hopalong.toml +31 -0
  73. c64cast/examples/scene-generative-lissajous.toml +27 -0
  74. c64cast/examples/scene-generative-mandelbrot.toml +34 -0
  75. c64cast/examples/scene-generative-metaballs.toml +28 -0
  76. c64cast/examples/scene-generative-moire2.toml +29 -0
  77. c64cast/examples/scene-generative-petscii.toml +24 -0
  78. c64cast/examples/scene-generative-plasma.toml +30 -0
  79. c64cast/examples/scene-generative-rorschach.toml +30 -0
  80. c64cast/examples/scene-generative-rotozoomer.toml +33 -0
  81. c64cast/examples/scene-generative-sid.toml +45 -0
  82. c64cast/examples/scene-generative-soap.toml +32 -0
  83. c64cast/examples/scene-launcher.toml +53 -0
  84. c64cast/examples/scene-midi.toml +64 -0
  85. c64cast/examples/scene-slideshow.toml +54 -0
  86. c64cast/examples/scene-video-sampler.toml +48 -0
  87. c64cast/examples/scene-video.toml +48 -0
  88. c64cast/examples/scene-waveform.toml +49 -0
  89. c64cast/examples/scene-webcam-audio.toml +40 -0
  90. c64cast/examples/scene-webcam-hires.toml +23 -0
  91. c64cast/examples/scene-webcam-hires_edges.toml +23 -0
  92. c64cast/examples/scene-webcam-mcm.toml +26 -0
  93. c64cast/examples/scene-webcam-mhires.toml +30 -0
  94. c64cast/examples/scene-webcam-petscii.toml +27 -0
  95. c64cast/examples/scene-wled.toml +37 -0
  96. c64cast/examples/teensyrom-blank.toml +51 -0
  97. c64cast/examples/vision-gesture.toml +48 -0
  98. c64cast/examples/vision-modes.toml +77 -0
  99. c64cast/examples/wled-control.toml +198 -0
  100. c64cast/frame_source.py +63 -0
  101. c64cast/framebuffer.py +267 -0
  102. c64cast/generators.py +1756 -0
  103. c64cast/interstitial.py +190 -0
  104. c64cast/introspect.py +847 -0
  105. c64cast/keyboard.py +293 -0
  106. c64cast/midi_control.py +1149 -0
  107. c64cast/midi_scene.py +991 -0
  108. c64cast/midi_setup.py +471 -0
  109. c64cast/modes.py +3362 -0
  110. c64cast/modulation.py +164 -0
  111. c64cast/music_features.py +246 -0
  112. c64cast/orchestrator.py +213 -0
  113. c64cast/orchestrators/__init__.py +11 -0
  114. c64cast/orchestrators/big_text_span.py +156 -0
  115. c64cast/overlays/__init__.py +300 -0
  116. c64cast/overlays/_spectrum.py +215 -0
  117. c64cast/overlays/big_text.py +766 -0
  118. c64cast/overlays/callsign.py +31 -0
  119. c64cast/overlays/clock.py +40 -0
  120. c64cast/overlays/corner_text.py +122 -0
  121. c64cast/overlays/countdown.py +74 -0
  122. c64cast/overlays/logo.py +157 -0
  123. c64cast/overlays/marquee.py +112 -0
  124. c64cast/overlays/menu.py +366 -0
  125. c64cast/overlays/network.py +127 -0
  126. c64cast/overlays/obs_status.py +148 -0
  127. c64cast/overlays/rss.py +112 -0
  128. c64cast/overlays/scrolling_text.py +169 -0
  129. c64cast/overlays/spectrum_bitmap.py +181 -0
  130. c64cast/overlays/spectrum_petscii.py +124 -0
  131. c64cast/overlays/weather.py +176 -0
  132. c64cast/palette.py +1272 -0
  133. c64cast/paths.py +255 -0
  134. c64cast/perf_console.py +664 -0
  135. c64cast/performance.py +712 -0
  136. c64cast/petscii_styles.py +383 -0
  137. c64cast/playlist.py +1404 -0
  138. c64cast/preview.py +208 -0
  139. c64cast/profiler.py +219 -0
  140. c64cast/py.typed +0 -0
  141. c64cast/quickcast.py +423 -0
  142. c64cast/recording_metadata.py +284 -0
  143. c64cast/rolling_palette.py +146 -0
  144. c64cast/sampler.py +778 -0
  145. c64cast/scenes.py +2262 -0
  146. c64cast/schema.py +219 -0
  147. c64cast/sid_autoconfig.py +267 -0
  148. c64cast/sid_host_emu.py +694 -0
  149. c64cast/sid_hw_config.py +198 -0
  150. c64cast/sid_panning.py +347 -0
  151. c64cast/sid_volume.py +260 -0
  152. c64cast/sidemu.py +301 -0
  153. c64cast/socket_dma.py +329 -0
  154. c64cast/songlengths.py +115 -0
  155. c64cast/teensyrom_api.py +472 -0
  156. c64cast/teensyrom_dma.py +597 -0
  157. c64cast/tempo.py +370 -0
  158. c64cast/text_surface.py +254 -0
  159. c64cast/transport.py +623 -0
  160. c64cast/video.py +1017 -0
  161. c64cast/vision.py +552 -0
  162. c64cast/voice_scope.py +847 -0
  163. c64cast/waveform.py +1671 -0
  164. c64cast/wizard.py +850 -0
  165. c64cast/wled_device.py +1344 -0
  166. c64cast/wled_sink.py +341 -0
  167. c64cast/wled_sync.py +218 -0
  168. c64cast-0.1.0.dist-info/METADATA +477 -0
  169. c64cast-0.1.0.dist-info/RECORD +173 -0
  170. c64cast-0.1.0.dist-info/WHEEL +5 -0
  171. c64cast-0.1.0.dist-info/entry_points.txt +2 -0
  172. c64cast-0.1.0.dist-info/licenses/LICENSE +21 -0
  173. 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
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())
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()