Add Picture Streams architecture with postprocessing templates and stream test UI
Introduce Picture Stream abstraction that separates the capture pipeline into composable layers: raw streams (display + capture engine + FPS) and processed streams (source stream + postprocessing template). Devices reference a picture stream instead of managing individual capture settings. - Add PictureStream and PostprocessingTemplate data models and stores - Add CRUD API endpoints for picture streams and postprocessing templates - Add stream chain resolution in ProcessorManager for start_processing - Add picture stream test endpoint with postprocessing preview support - Add Stream Settings modal with border_width and interpolation_mode controls - Add stream test modal with capture preview and performance metrics - Add full frontend: Picture Streams tab, Processing Templates tab, stream selector on device cards, test buttons on stream cards - Add localization keys for all new features (en, ru) - Migrate existing devices to picture streams on startup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,8 @@ from wled_controller.config import get_config
|
||||
from wled_controller.core.processor_manager import ProcessorManager
|
||||
from wled_controller.storage import DeviceStore
|
||||
from wled_controller.storage.template_store import TemplateStore
|
||||
from wled_controller.storage.postprocessing_template_store import PostprocessingTemplateStore
|
||||
from wled_controller.storage.picture_stream_store import PictureStreamStore
|
||||
from wled_controller.utils import setup_logging, get_logger
|
||||
|
||||
# Initialize logging
|
||||
@@ -28,7 +30,65 @@ config = get_config()
|
||||
# Initialize storage and processing
|
||||
device_store = DeviceStore(config.storage.devices_file)
|
||||
template_store = TemplateStore(config.storage.templates_file)
|
||||
processor_manager = ProcessorManager()
|
||||
pp_template_store = PostprocessingTemplateStore(config.storage.postprocessing_templates_file)
|
||||
picture_stream_store = PictureStreamStore(config.storage.picture_streams_file)
|
||||
|
||||
# Assign first available template to devices with missing/invalid template
|
||||
all_templates = template_store.get_all_templates()
|
||||
if all_templates:
|
||||
valid_ids = {t.id for t in all_templates}
|
||||
for device in device_store.get_all_devices():
|
||||
if not device.capture_template_id or device.capture_template_id not in valid_ids:
|
||||
old_id = device.capture_template_id
|
||||
device_store.update_device(device.id, capture_template_id=all_templates[0].id)
|
||||
logger.info(
|
||||
f"Assigned template '{all_templates[0].name}' to device '{device.name}' "
|
||||
f"(was '{old_id}')"
|
||||
)
|
||||
|
||||
# Migrate devices without picture_stream_id: create streams from legacy settings
|
||||
for device in device_store.get_all_devices():
|
||||
if not device.picture_stream_id:
|
||||
try:
|
||||
# Create a raw stream from the device's current capture settings
|
||||
raw_stream = picture_stream_store.create_stream(
|
||||
name=f"{device.name} - Raw",
|
||||
stream_type="raw",
|
||||
display_index=device.settings.display_index,
|
||||
capture_template_id=device.capture_template_id,
|
||||
target_fps=device.settings.fps,
|
||||
description=f"Auto-migrated from device '{device.name}'",
|
||||
)
|
||||
|
||||
# Create a processed stream with the first PP template
|
||||
pp_templates = pp_template_store.get_all_templates()
|
||||
if pp_templates:
|
||||
processed_stream = picture_stream_store.create_stream(
|
||||
name=f"{device.name} - Processed",
|
||||
stream_type="processed",
|
||||
source_stream_id=raw_stream.id,
|
||||
postprocessing_template_id=pp_templates[0].id,
|
||||
description=f"Auto-migrated from device '{device.name}'",
|
||||
)
|
||||
device_store.update_device(device.id, picture_stream_id=processed_stream.id)
|
||||
logger.info(
|
||||
f"Migrated device '{device.name}': created raw stream '{raw_stream.id}' "
|
||||
f"+ processed stream '{processed_stream.id}'"
|
||||
)
|
||||
else:
|
||||
# No PP templates, assign raw stream directly
|
||||
device_store.update_device(device.id, picture_stream_id=raw_stream.id)
|
||||
logger.info(
|
||||
f"Migrated device '{device.name}': created raw stream '{raw_stream.id}'"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to migrate device '{device.name}': {e}")
|
||||
|
||||
processor_manager = ProcessorManager(
|
||||
picture_stream_store=picture_stream_store,
|
||||
capture_template_store=template_store,
|
||||
pp_template_store=pp_template_store,
|
||||
)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -61,7 +121,11 @@ async def lifespan(app: FastAPI):
|
||||
logger.info("All API requests require valid Bearer token authentication")
|
||||
|
||||
# Initialize API dependencies
|
||||
init_dependencies(device_store, template_store, processor_manager)
|
||||
init_dependencies(
|
||||
device_store, template_store, processor_manager,
|
||||
pp_template_store=pp_template_store,
|
||||
picture_stream_store=picture_stream_store,
|
||||
)
|
||||
|
||||
# Load existing devices into processor manager
|
||||
devices = device_store.get_all_devices()
|
||||
@@ -74,6 +138,7 @@ async def lifespan(app: FastAPI):
|
||||
settings=device.settings,
|
||||
calibration=device.calibration,
|
||||
capture_template_id=device.capture_template_id,
|
||||
picture_stream_id=device.picture_stream_id,
|
||||
)
|
||||
logger.info(f"Loaded device: {device.name} ({device.id})")
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user