refactor: key colors targets → CSS source type, HA target improvements
Lint & Test / test (push) Successful in 1m26s
Lint & Test / test (push) Successful in 1m26s
Key Colors refactor: - New `key_colors` CSS source type with inline rectangles - KeyColorsColorStripStream: extracts N colors from screen regions - CSS editor: EntitySelect for picture source, IconSelect for color mode - Configure Regions button on card opens pattern canvas editor - Live WS preview at 5 FPS with rectangle overlay + color swatches - Removed KC target type, pattern template entity, and related API routes - Removed KC/pattern template sections from Targets tab HA light target improvements: - Update rate, transition, mappings, brightness VS now editable via PUT - Card crosslinks for HA source, CSS source, brightness VS - HA connection status icon, text metrics (Hz, uptime) - Brightness value source selector in editor
This commit is contained in:
@@ -19,15 +19,74 @@ logger = get_logger(__name__)
|
||||
# Lock + handle for cancelling previous sound
|
||||
_play_lock = threading.Lock()
|
||||
_current_process: subprocess.Popen | None = None
|
||||
# Hold reference to SND_MEMORY buffer to prevent GC during async playback
|
||||
_win_sound_buf: bytes | None = None
|
||||
|
||||
|
||||
def _scale_wav_volume(file_path: Path, volume: float) -> bytes | None:
|
||||
"""Read a WAV file and return a volume-scaled WAV as bytes.
|
||||
|
||||
Uses stdlib wave + struct to scale PCM samples in memory.
|
||||
Returns None on error or if the format is unsupported.
|
||||
"""
|
||||
import io
|
||||
import struct
|
||||
import wave
|
||||
|
||||
try:
|
||||
with wave.open(str(file_path), "rb") as wf:
|
||||
n_channels = wf.getnchannels()
|
||||
sample_width = wf.getsampwidth()
|
||||
framerate = wf.getframerate()
|
||||
n_frames = wf.getnframes()
|
||||
raw = wf.readframes(n_frames)
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to read WAV for volume scaling: {e}")
|
||||
return None
|
||||
|
||||
if sample_width not in (1, 2):
|
||||
return None # Only 8-bit and 16-bit PCM supported
|
||||
|
||||
# Scale samples
|
||||
if sample_width == 2:
|
||||
fmt = f"<{len(raw) // 2}h"
|
||||
samples = struct.unpack(fmt, raw)
|
||||
scaled = struct.pack(fmt, *(max(-32768, min(32767, int(s * volume))) for s in samples))
|
||||
else:
|
||||
# 8-bit WAV is unsigned, center at 128
|
||||
samples = struct.unpack(f"{len(raw)}B", raw)
|
||||
scaled = struct.pack(
|
||||
f"{len(raw)}B",
|
||||
*(max(0, min(255, int((s - 128) * volume + 128))) for s in samples),
|
||||
)
|
||||
|
||||
# Write scaled WAV to memory buffer
|
||||
buf = io.BytesIO()
|
||||
with wave.open(buf, "wb") as out:
|
||||
out.setnchannels(n_channels)
|
||||
out.setsampwidth(sample_width)
|
||||
out.setframerate(framerate)
|
||||
out.writeframes(scaled)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def _play_windows(file_path: Path, volume: float) -> None:
|
||||
"""Play a WAV file on Windows using winsound."""
|
||||
"""Play a WAV file on Windows using winsound with volume scaling."""
|
||||
import winsound
|
||||
|
||||
# winsound doesn't support volume control natively,
|
||||
# but SND_ASYNC plays non-blocking within this thread
|
||||
global _win_sound_buf
|
||||
|
||||
try:
|
||||
if volume < 1.0:
|
||||
wav_data = _scale_wav_volume(file_path, volume)
|
||||
if wav_data:
|
||||
# Keep a global reference so GC doesn't free the buffer
|
||||
# while async playback is still using it
|
||||
_win_sound_buf = wav_data
|
||||
winsound.PlaySound(wav_data, winsound.SND_MEMORY | winsound.SND_ASYNC)
|
||||
return
|
||||
# Full volume or fallback: play file directly
|
||||
_win_sound_buf = None
|
||||
winsound.PlaySound(str(file_path), winsound.SND_FILENAME | winsound.SND_ASYNC)
|
||||
except Exception as e:
|
||||
logger.error(f"winsound playback failed: {e}")
|
||||
@@ -110,6 +169,7 @@ def stop_current_sound() -> None:
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
import winsound
|
||||
|
||||
winsound.PlaySound(None, winsound.SND_PURGE)
|
||||
except Exception as e:
|
||||
logger.debug("Failed to stop winsound playback: %s", e)
|
||||
|
||||
Reference in New Issue
Block a user