Add adaptive brightness value source with time-of-day and scene modes

New "adaptive" value source type that automatically adjusts brightness
based on external conditions. Two sub-modes: time-of-day (schedule-based
interpolation with midnight wrap) and scene brightness (frame luminance
analysis via numpy BT.601 subsampling with EMA smoothing).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 15:14:30 +03:00
parent 48651f0a4e
commit d339dd3f90
11 changed files with 643 additions and 19 deletions

View File

@@ -43,6 +43,10 @@ def _to_response(source: ValueSource) -> ValueSourceResponse:
mode=d.get("mode"),
sensitivity=d.get("sensitivity"),
smoothing=d.get("smoothing"),
adaptive_mode=d.get("adaptive_mode"),
schedule=d.get("schedule"),
picture_source_id=d.get("picture_source_id"),
scene_behavior=d.get("scene_behavior"),
description=d.get("description"),
created_at=source.created_at,
updated_at=source.updated_at,
@@ -86,6 +90,10 @@ async def create_value_source(
sensitivity=data.sensitivity,
smoothing=data.smoothing,
description=data.description,
adaptive_mode=data.adaptive_mode,
schedule=data.schedule,
picture_source_id=data.picture_source_id,
scene_behavior=data.scene_behavior,
)
return _to_response(source)
except ValueError as e:
@@ -129,6 +137,10 @@ async def update_value_source(
sensitivity=data.sensitivity,
smoothing=data.smoothing,
description=data.description,
adaptive_mode=data.adaptive_mode,
schedule=data.schedule,
picture_source_id=data.picture_source_id,
scene_behavior=data.scene_behavior,
)
# Hot-reload running value streams
pm.update_value_source(source_id)

View File

@@ -10,7 +10,7 @@ class ValueSourceCreate(BaseModel):
"""Request to create a value source."""
name: str = Field(description="Source name", min_length=1, max_length=100)
source_type: Literal["static", "animated", "audio"] = Field(description="Source type")
source_type: Literal["static", "animated", "audio", "adaptive"] = Field(description="Source type")
# static fields
value: Optional[float] = Field(None, description="Constant value (0.0-1.0)", ge=0.0, le=1.0)
# animated fields
@@ -23,6 +23,11 @@ class ValueSourceCreate(BaseModel):
mode: Optional[str] = Field(None, description="Audio mode: rms|peak|beat")
sensitivity: Optional[float] = Field(None, description="Gain multiplier (0.1-5.0)", ge=0.1, le=5.0)
smoothing: Optional[float] = Field(None, description="Temporal smoothing (0.0-1.0)", ge=0.0, le=1.0)
# adaptive fields
adaptive_mode: Optional[str] = Field(None, description="Adaptive mode: time_of_day|scene")
schedule: Optional[list] = Field(None, description="Time-of-day schedule: [{time: 'HH:MM', value: 0.0-1.0}]")
picture_source_id: Optional[str] = Field(None, description="Picture source ID for scene mode")
scene_behavior: Optional[str] = Field(None, description="Scene behavior: complement|match")
description: Optional[str] = Field(None, description="Optional description", max_length=500)
@@ -42,6 +47,11 @@ class ValueSourceUpdate(BaseModel):
mode: Optional[str] = Field(None, description="Audio mode: rms|peak|beat")
sensitivity: Optional[float] = Field(None, description="Gain multiplier (0.1-5.0)", ge=0.1, le=5.0)
smoothing: Optional[float] = Field(None, description="Temporal smoothing (0.0-1.0)", ge=0.0, le=1.0)
# adaptive fields
adaptive_mode: Optional[str] = Field(None, description="Adaptive mode: time_of_day|scene")
schedule: Optional[list] = Field(None, description="Time-of-day schedule")
picture_source_id: Optional[str] = Field(None, description="Picture source ID for scene mode")
scene_behavior: Optional[str] = Field(None, description="Scene behavior: complement|match")
description: Optional[str] = Field(None, description="Optional description", max_length=500)
@@ -50,7 +60,7 @@ class ValueSourceResponse(BaseModel):
id: str = Field(description="Source ID")
name: str = Field(description="Source name")
source_type: str = Field(description="Source type: static, animated, or audio")
source_type: str = Field(description="Source type: static, animated, audio, or adaptive")
value: Optional[float] = Field(None, description="Static value")
waveform: Optional[str] = Field(None, description="Waveform type")
speed: Optional[float] = Field(None, description="Cycles per minute")
@@ -60,6 +70,10 @@ class ValueSourceResponse(BaseModel):
mode: Optional[str] = Field(None, description="Audio mode")
sensitivity: Optional[float] = Field(None, description="Gain multiplier")
smoothing: Optional[float] = Field(None, description="Temporal smoothing")
adaptive_mode: Optional[str] = Field(None, description="Adaptive mode")
schedule: Optional[list] = Field(None, description="Time-of-day schedule")
picture_source_id: Optional[str] = Field(None, description="Picture source ID")
scene_behavior: Optional[str] = Field(None, description="Scene behavior")
description: Optional[str] = Field(None, description="Description")
created_at: datetime = Field(description="Creation timestamp")
updated_at: datetime = Field(description="Last update timestamp")