Rename picture-targets to output-targets across entire codebase

Rename all Python modules, classes, API endpoints, config keys, frontend
fetch URLs, and Home Assistant integration URLs from picture-targets to
output-targets. Store loads both new and legacy JSON keys for backward
compatibility with existing data files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 10:55:36 +03:00
parent 5b4813368b
commit 353a1c2d85
37 changed files with 243 additions and 244 deletions

View File

@@ -9,7 +9,7 @@ from wled_controller.api.auth import AuthRequired
from wled_controller.api.dependencies import (
get_color_strip_store,
get_picture_source_store,
get_picture_target_store,
get_output_target_store,
get_processor_manager,
)
from wled_controller.api.schemas.color_strip_sources import (
@@ -35,7 +35,7 @@ from wled_controller.storage.color_strip_source import ApiInputColorStripSource,
from wled_controller.storage.color_strip_store import ColorStripStore
from wled_controller.storage.picture_source import ProcessedPictureSource, ScreenCapturePictureSource
from wled_controller.storage.picture_source_store import PictureSourceStore
from wled_controller.storage.picture_target_store import PictureTargetStore
from wled_controller.storage.output_target_store import OutputTargetStore
from wled_controller.utils import get_logger
from wled_controller.config import get_config
@@ -295,7 +295,7 @@ async def delete_color_strip_source(
source_id: str,
_auth: AuthRequired,
store: ColorStripStore = Depends(get_color_strip_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
):
"""Delete a color strip source. Returns 409 if referenced by any LED target."""
try:

View File

@@ -13,7 +13,7 @@ from wled_controller.core.devices.led_client import (
)
from wled_controller.api.dependencies import (
get_device_store,
get_picture_target_store,
get_output_target_store,
get_processor_manager,
)
from wled_controller.api.schemas.devices import (
@@ -29,7 +29,7 @@ from wled_controller.api.schemas.devices import (
)
from wled_controller.core.processing.processor_manager import ProcessorManager
from wled_controller.storage import DeviceStore
from wled_controller.storage.picture_target_store import PictureTargetStore
from wled_controller.storage.output_target_store import OutputTargetStore
from wled_controller.utils import get_logger
logger = get_logger(__name__)
@@ -342,7 +342,7 @@ async def delete_device(
device_id: str,
_auth: AuthRequired,
store: DeviceStore = Depends(get_device_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Delete/detach a device. Returns 409 if referenced by a target."""

View File

@@ -1,4 +1,4 @@
"""Picture target routes: CRUD, processing control, settings, state, metrics."""
"""Output target routes: CRUD, processing control, settings, state, metrics."""
import asyncio
import base64
@@ -16,21 +16,21 @@ from wled_controller.api.dependencies import (
get_device_store,
get_pattern_template_store,
get_picture_source_store,
get_picture_target_store,
get_output_target_store,
get_pp_template_store,
get_processor_manager,
get_template_store,
)
from wled_controller.api.schemas.picture_targets import (
from wled_controller.api.schemas.output_targets import (
ExtractedColorResponse,
KCTestRectangleResponse,
KCTestResponse,
KeyColorsResponse,
KeyColorsSettingsSchema,
PictureTargetCreate,
PictureTargetListResponse,
PictureTargetResponse,
PictureTargetUpdate,
OutputTargetCreate,
OutputTargetListResponse,
OutputTargetResponse,
OutputTargetUpdate,
TargetMetricsResponse,
TargetProcessingState,
)
@@ -51,12 +51,12 @@ from wled_controller.storage.pattern_template_store import PatternTemplateStore
from wled_controller.storage.picture_source import ScreenCapturePictureSource, StaticImagePictureSource
from wled_controller.storage.picture_source_store import PictureSourceStore
from wled_controller.storage.template_store import TemplateStore
from wled_controller.storage.wled_picture_target import WledPictureTarget
from wled_controller.storage.key_colors_picture_target import (
from wled_controller.storage.wled_output_target import WledOutputTarget
from wled_controller.storage.key_colors_output_target import (
KeyColorsSettings,
KeyColorsPictureTarget,
KeyColorsOutputTarget,
)
from wled_controller.storage.picture_target_store import PictureTargetStore
from wled_controller.storage.output_target_store import OutputTargetStore
from wled_controller.utils import get_logger
logger = get_logger(__name__)
@@ -88,10 +88,10 @@ def _kc_schema_to_settings(schema: KeyColorsSettingsSchema) -> KeyColorsSettings
)
def _target_to_response(target) -> PictureTargetResponse:
"""Convert a PictureTarget to PictureTargetResponse."""
if isinstance(target, WledPictureTarget):
return PictureTargetResponse(
def _target_to_response(target) -> OutputTargetResponse:
"""Convert an OutputTarget to OutputTargetResponse."""
if isinstance(target, WledOutputTarget):
return OutputTargetResponse(
id=target.id,
name=target.name,
target_type=target.target_type,
@@ -109,8 +109,8 @@ def _target_to_response(target) -> PictureTargetResponse:
created_at=target.created_at,
updated_at=target.updated_at,
)
elif isinstance(target, KeyColorsPictureTarget):
return PictureTargetResponse(
elif isinstance(target, KeyColorsOutputTarget):
return OutputTargetResponse(
id=target.id,
name=target.name,
target_type=target.target_type,
@@ -122,7 +122,7 @@ def _target_to_response(target) -> PictureTargetResponse:
updated_at=target.updated_at,
)
else:
return PictureTargetResponse(
return OutputTargetResponse(
id=target.id,
name=target.name,
target_type=target.target_type,
@@ -135,15 +135,15 @@ def _target_to_response(target) -> PictureTargetResponse:
# ===== CRUD ENDPOINTS =====
@router.post("/api/v1/picture-targets", response_model=PictureTargetResponse, tags=["Targets"], status_code=201)
@router.post("/api/v1/output-targets", response_model=OutputTargetResponse, tags=["Targets"], status_code=201)
async def create_target(
data: PictureTargetCreate,
data: OutputTargetCreate,
_auth: AuthRequired,
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
device_store: DeviceStore = Depends(get_device_store),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Create a new picture target."""
"""Create a new output target."""
try:
# Validate device exists if provided
if data.device_id:
@@ -188,18 +188,18 @@ async def create_target(
raise HTTPException(status_code=500, detail=str(e))
@router.get("/api/v1/picture-targets", response_model=PictureTargetListResponse, tags=["Targets"])
@router.get("/api/v1/output-targets", response_model=OutputTargetListResponse, tags=["Targets"])
async def list_targets(
_auth: AuthRequired,
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
):
"""List all picture targets."""
"""List all output targets."""
targets = target_store.get_all_targets()
responses = [_target_to_response(t) for t in targets]
return PictureTargetListResponse(targets=responses, count=len(responses))
return OutputTargetListResponse(targets=responses, count=len(responses))
@router.get("/api/v1/picture-targets/batch/states", tags=["Processing"])
@router.get("/api/v1/output-targets/batch/states", tags=["Processing"])
async def batch_target_states(
_auth: AuthRequired,
manager: ProcessorManager = Depends(get_processor_manager),
@@ -208,7 +208,7 @@ async def batch_target_states(
return {"states": manager.get_all_target_states()}
@router.get("/api/v1/picture-targets/batch/metrics", tags=["Metrics"])
@router.get("/api/v1/output-targets/batch/metrics", tags=["Metrics"])
async def batch_target_metrics(
_auth: AuthRequired,
manager: ProcessorManager = Depends(get_processor_manager),
@@ -217,13 +217,13 @@ async def batch_target_metrics(
return {"metrics": manager.get_all_target_metrics()}
@router.get("/api/v1/picture-targets/{target_id}", response_model=PictureTargetResponse, tags=["Targets"])
@router.get("/api/v1/output-targets/{target_id}", response_model=OutputTargetResponse, tags=["Targets"])
async def get_target(
target_id: str,
_auth: AuthRequired,
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
):
"""Get a picture target by ID."""
"""Get a output target by ID."""
try:
target = target_store.get_target(target_id)
return _target_to_response(target)
@@ -231,16 +231,16 @@ async def get_target(
raise HTTPException(status_code=404, detail=str(e))
@router.put("/api/v1/picture-targets/{target_id}", response_model=PictureTargetResponse, tags=["Targets"])
@router.put("/api/v1/output-targets/{target_id}", response_model=OutputTargetResponse, tags=["Targets"])
async def update_target(
target_id: str,
data: PictureTargetUpdate,
data: OutputTargetUpdate,
_auth: AuthRequired,
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
device_store: DeviceStore = Depends(get_device_store),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Update a picture target."""
"""Update a output target."""
try:
# Validate device exists if changing
if data.device_id is not None and data.device_id:
@@ -258,7 +258,7 @@ async def update_target(
except ValueError:
existing_target = None
if isinstance(existing_target, KeyColorsPictureTarget):
if isinstance(existing_target, KeyColorsOutputTarget):
ex = existing_target.settings
merged = KeyColorsSettingsSchema(
fps=incoming.get("fps", ex.fps),
@@ -325,14 +325,14 @@ async def update_target(
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/api/v1/picture-targets/{target_id}", status_code=204, tags=["Targets"])
@router.delete("/api/v1/output-targets/{target_id}", status_code=204, tags=["Targets"])
async def delete_target(
target_id: str,
_auth: AuthRequired,
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Delete a picture target. Stops processing first if active."""
"""Delete a output target. Stops processing first if active."""
try:
# Stop processing if running
try:
@@ -360,14 +360,14 @@ async def delete_target(
# ===== PROCESSING CONTROL ENDPOINTS =====
@router.post("/api/v1/picture-targets/{target_id}/start", tags=["Processing"])
@router.post("/api/v1/output-targets/{target_id}/start", tags=["Processing"])
async def start_processing(
target_id: str,
_auth: AuthRequired,
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Start processing for a picture target."""
"""Start processing for a output target."""
try:
# Verify target exists in store
target_store.get_target(target_id)
@@ -386,13 +386,13 @@ async def start_processing(
raise HTTPException(status_code=500, detail=str(e))
@router.post("/api/v1/picture-targets/{target_id}/stop", tags=["Processing"])
@router.post("/api/v1/output-targets/{target_id}/stop", tags=["Processing"])
async def stop_processing(
target_id: str,
_auth: AuthRequired,
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Stop processing for a picture target."""
"""Stop processing for a output target."""
try:
await manager.stop_processing(target_id)
@@ -408,7 +408,7 @@ async def stop_processing(
# ===== STATE & METRICS ENDPOINTS =====
@router.get("/api/v1/picture-targets/{target_id}/state", response_model=TargetProcessingState, tags=["Processing"])
@router.get("/api/v1/output-targets/{target_id}/state", response_model=TargetProcessingState, tags=["Processing"])
async def get_target_state(
target_id: str,
_auth: AuthRequired,
@@ -426,7 +426,7 @@ async def get_target_state(
raise HTTPException(status_code=500, detail=str(e))
@router.get("/api/v1/picture-targets/{target_id}/metrics", response_model=TargetMetricsResponse, tags=["Metrics"])
@router.get("/api/v1/output-targets/{target_id}/metrics", response_model=TargetMetricsResponse, tags=["Metrics"])
async def get_target_metrics(
target_id: str,
_auth: AuthRequired,
@@ -446,7 +446,7 @@ async def get_target_metrics(
# ===== KEY COLORS ENDPOINTS =====
@router.get("/api/v1/picture-targets/{target_id}/colors", response_model=KeyColorsResponse, tags=["Key Colors"])
@router.get("/api/v1/output-targets/{target_id}/colors", response_model=KeyColorsResponse, tags=["Key Colors"])
async def get_target_colors(
target_id: str,
_auth: AuthRequired,
@@ -471,11 +471,11 @@ async def get_target_colors(
raise HTTPException(status_code=404, detail=str(e))
@router.post("/api/v1/picture-targets/{target_id}/test", response_model=KCTestResponse, tags=["Key Colors"])
@router.post("/api/v1/output-targets/{target_id}/test", response_model=KCTestResponse, tags=["Key Colors"])
async def test_kc_target(
target_id: str,
_auth: AuthRequired,
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
source_store: PictureSourceStore = Depends(get_picture_source_store),
template_store: TemplateStore = Depends(get_template_store),
pattern_store: PatternTemplateStore = Depends(get_pattern_template_store),
@@ -494,7 +494,7 @@ async def test_kc_target(
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
if not isinstance(target, KeyColorsPictureTarget):
if not isinstance(target, KeyColorsOutputTarget):
raise HTTPException(status_code=400, detail="Target is not a key_colors target")
settings = target.settings
@@ -670,7 +670,7 @@ async def test_kc_target(
logger.error(f"Error cleaning up test stream: {e}")
@router.websocket("/api/v1/picture-targets/{target_id}/ws")
@router.websocket("/api/v1/output-targets/{target_id}/ws")
async def target_colors_ws(
websocket: WebSocket,
target_id: str,
@@ -710,7 +710,7 @@ async def target_colors_ws(
manager.remove_kc_ws_client(target_id, websocket)
@router.websocket("/api/v1/picture-targets/{target_id}/led-preview/ws")
@router.websocket("/api/v1/output-targets/{target_id}/led-preview/ws")
async def led_preview_ws(
websocket: WebSocket,
target_id: str,
@@ -788,12 +788,12 @@ async def events_ws(
# ===== OVERLAY VISUALIZATION =====
@router.post("/api/v1/picture-targets/{target_id}/overlay/start", tags=["Visualization"])
@router.post("/api/v1/output-targets/{target_id}/overlay/start", tags=["Visualization"])
async def start_target_overlay(
target_id: str,
_auth: AuthRequired,
manager: ProcessorManager = Depends(get_processor_manager),
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
color_strip_store: ColorStripStore = Depends(get_color_strip_store),
picture_source_store: PictureSourceStore = Depends(get_picture_source_store),
):
@@ -815,7 +815,7 @@ async def start_target_overlay(
# can start even when processing is not currently running.
calibration = None
display_info = None
if isinstance(target, WledPictureTarget) and target.color_strip_source_id:
if isinstance(target, WledOutputTarget) and target.color_strip_source_id:
first_css_id = target.color_strip_source_id
if first_css_id:
try:
@@ -844,7 +844,7 @@ async def start_target_overlay(
raise HTTPException(status_code=500, detail=str(e))
@router.post("/api/v1/picture-targets/{target_id}/overlay/stop", tags=["Visualization"])
@router.post("/api/v1/output-targets/{target_id}/overlay/stop", tags=["Visualization"])
async def stop_target_overlay(
target_id: str,
_auth: AuthRequired,
@@ -862,7 +862,7 @@ async def stop_target_overlay(
raise HTTPException(status_code=500, detail=str(e))
@router.get("/api/v1/picture-targets/{target_id}/overlay/status", tags=["Visualization"])
@router.get("/api/v1/output-targets/{target_id}/overlay/status", tags=["Visualization"])
async def get_overlay_status(
target_id: str,
_auth: AuthRequired,

View File

@@ -5,7 +5,7 @@ from fastapi import APIRouter, HTTPException, Depends
from wled_controller.api.auth import AuthRequired
from wled_controller.api.dependencies import (
get_pattern_template_store,
get_picture_target_store,
get_output_target_store,
)
from wled_controller.api.schemas.pattern_templates import (
PatternTemplateCreate,
@@ -13,10 +13,10 @@ from wled_controller.api.schemas.pattern_templates import (
PatternTemplateResponse,
PatternTemplateUpdate,
)
from wled_controller.api.schemas.picture_targets import KeyColorRectangleSchema
from wled_controller.storage.key_colors_picture_target import KeyColorRectangle
from wled_controller.api.schemas.output_targets import KeyColorRectangleSchema
from wled_controller.storage.key_colors_output_target import KeyColorRectangle
from wled_controller.storage.pattern_template_store import PatternTemplateStore
from wled_controller.storage.picture_target_store import PictureTargetStore
from wled_controller.storage.output_target_store import OutputTargetStore
from wled_controller.utils import get_logger
logger = get_logger(__name__)
@@ -127,7 +127,7 @@ async def delete_pattern_template(
template_id: str,
_auth: AuthRequired,
store: PatternTemplateStore = Depends(get_pattern_template_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
):
"""Delete a pattern template."""
try:

View File

@@ -13,7 +13,7 @@ from fastapi.responses import Response
from wled_controller.api.auth import AuthRequired
from wled_controller.api.dependencies import (
get_picture_source_store,
get_picture_target_store,
get_output_target_store,
get_pp_template_store,
get_template_store,
)
@@ -33,7 +33,7 @@ from wled_controller.api.schemas.picture_sources import (
)
from wled_controller.core.capture_engines import EngineRegistry
from wled_controller.core.filters import FilterRegistry, ImagePool
from wled_controller.storage.picture_target_store import PictureTargetStore
from wled_controller.storage.output_target_store import OutputTargetStore
from wled_controller.storage.template_store import TemplateStore
from wled_controller.storage.postprocessing_template_store import PostprocessingTemplateStore
from wled_controller.storage.picture_source_store import PictureSourceStore
@@ -254,7 +254,7 @@ async def delete_picture_source(
stream_id: str,
_auth: AuthRequired,
store: PictureSourceStore = Depends(get_picture_source_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
):
"""Delete a picture source."""
try:

View File

@@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException
from wled_controller.api.auth import AuthRequired
from wled_controller.api.dependencies import (
get_picture_target_store,
get_output_target_store,
get_processor_manager,
get_scene_preset_store,
)
@@ -23,7 +23,7 @@ from wled_controller.core.scenes.scene_activator import (
apply_scene_state,
capture_current_snapshot,
)
from wled_controller.storage.picture_target_store import PictureTargetStore
from wled_controller.storage.output_target_store import OutputTargetStore
from wled_controller.storage.scene_preset import ScenePreset
from wled_controller.storage.scene_preset_store import ScenePresetStore
from wled_controller.utils import get_logger
@@ -62,7 +62,7 @@ async def create_scene_preset(
data: ScenePresetCreate,
_auth: AuthRequired,
store: ScenePresetStore = Depends(get_scene_preset_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Capture current state as a new scene preset."""
@@ -176,7 +176,7 @@ async def recapture_scene_preset(
preset_id: str,
_auth: AuthRequired,
store: ScenePresetStore = Depends(get_scene_preset_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Re-capture current state into an existing preset (updates snapshot)."""
@@ -214,7 +214,7 @@ async def activate_scene_preset(
preset_id: str,
_auth: AuthRequired,
store: ScenePresetStore = Depends(get_scene_preset_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Activate a scene preset — restore the captured state."""

View File

@@ -265,7 +265,7 @@ STORE_MAP = {
"capture_templates": "templates_file",
"postprocessing_templates": "postprocessing_templates_file",
"picture_sources": "picture_sources_file",
"picture_targets": "picture_targets_file",
"output_targets": "output_targets_file",
"pattern_templates": "pattern_templates_file",
"color_strip_sources": "color_strip_sources_file",
"audio_sources": "audio_sources_file",

View File

@@ -8,7 +8,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, WebSock
from wled_controller.api.auth import AuthRequired
from wled_controller.api.dependencies import (
get_picture_target_store,
get_output_target_store,
get_processor_manager,
get_value_source_store,
)
@@ -21,7 +21,7 @@ from wled_controller.api.schemas.value_sources import (
)
from wled_controller.storage.value_source import ValueSource
from wled_controller.storage.value_source_store import ValueSourceStore
from wled_controller.storage.picture_target_store import PictureTargetStore
from wled_controller.storage.output_target_store import OutputTargetStore
from wled_controller.core.processing.processor_manager import ProcessorManager
from wled_controller.utils import get_logger
@@ -157,14 +157,14 @@ async def delete_value_source(
source_id: str,
_auth: AuthRequired,
store: ValueSourceStore = Depends(get_value_source_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
target_store: OutputTargetStore = Depends(get_output_target_store),
):
"""Delete a value source."""
try:
# Check if any targets reference this value source
from wled_controller.storage.wled_picture_target import WledPictureTarget
from wled_controller.storage.wled_output_target import WledOutputTarget
for target in target_store.get_all_targets():
if isinstance(target, WledPictureTarget):
if isinstance(target, WledOutputTarget):
if getattr(target, "brightness_value_source_id", "") == source_id:
raise ValueError(
f"Cannot delete: referenced by target '{target.name}'"