Add capture template system with in-memory defaults and split device settings UI
Some checks failed
Validate / validate (push) Failing after 8s
Some checks failed
Validate / validate (push) Failing after 8s
- Generate default templates (MSS, DXcam, WGC) in memory from EngineRegistry at startup - Only persist user-created templates to JSON, skip defaults on load/save - Add capture_template_id to Device model and DeviceCreate schema - Remember last used template in localStorage, use it for new devices with fallback - Split Device Settings dialog into General Settings and Capture Settings - Add capture settings button (🎬) to device card - Separate default and custom templates with visual separator in Templates tab - Add capture engine integration to ProcessorManager - Add CLAUDE.md with git commit/push policy and server restart instructions - Add en/ru localization for all new UI elements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -176,6 +176,7 @@ async def create_device(
|
||||
_auth: AuthRequired,
|
||||
store: DeviceStore = Depends(get_device_store),
|
||||
manager: ProcessorManager = Depends(get_processor_manager),
|
||||
template_store: TemplateStore = Depends(get_template_store),
|
||||
):
|
||||
"""Create and attach a new WLED device."""
|
||||
try:
|
||||
@@ -214,11 +215,29 @@ async def create_device(
|
||||
detail=f"Failed to connect to WLED device at {device_url}: {e}"
|
||||
)
|
||||
|
||||
# Resolve capture template: use requested ID if valid, else first available
|
||||
capture_template_id = None
|
||||
if device_data.capture_template_id:
|
||||
try:
|
||||
template_store.get_template(device_data.capture_template_id)
|
||||
capture_template_id = device_data.capture_template_id
|
||||
except ValueError:
|
||||
logger.warning(
|
||||
f"Requested template '{device_data.capture_template_id}' not found, using first available"
|
||||
)
|
||||
if not capture_template_id:
|
||||
all_templates = template_store.get_all_templates()
|
||||
if all_templates:
|
||||
capture_template_id = all_templates[0].id
|
||||
else:
|
||||
capture_template_id = "tpl_mss_default"
|
||||
|
||||
# Create device in storage (LED count auto-detected from WLED)
|
||||
device = store.create_device(
|
||||
name=device_data.name,
|
||||
url=device_data.url,
|
||||
led_count=wled_led_count,
|
||||
capture_template_id=capture_template_id,
|
||||
)
|
||||
|
||||
# Add to processor manager
|
||||
|
||||
@@ -55,6 +55,7 @@ class DeviceCreate(BaseModel):
|
||||
|
||||
name: str = Field(description="Device name", min_length=1, max_length=100)
|
||||
url: str = Field(description="WLED device URL (e.g., http://192.168.1.100)")
|
||||
capture_template_id: Optional[str] = Field(None, description="Capture template ID (uses first available if not set or invalid)")
|
||||
|
||||
|
||||
class DeviceUpdate(BaseModel):
|
||||
@@ -63,6 +64,7 @@ class DeviceUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, description="Device name", min_length=1, max_length=100)
|
||||
url: Optional[str] = Field(None, description="WLED device URL")
|
||||
enabled: Optional[bool] = Field(None, description="Whether device is enabled")
|
||||
capture_template_id: Optional[str] = Field(None, description="Capture template ID")
|
||||
|
||||
|
||||
class ColorCorrection(BaseModel):
|
||||
@@ -153,6 +155,7 @@ class DeviceResponse(BaseModel):
|
||||
)
|
||||
settings: ProcessingSettings = Field(description="Processing settings")
|
||||
calibration: Optional[Calibration] = Field(None, description="Calibration configuration")
|
||||
capture_template_id: str = Field(description="ID of assigned capture template")
|
||||
created_at: datetime = Field(description="Creation timestamp")
|
||||
updated_at: datetime = Field(description="Last update timestamp")
|
||||
|
||||
@@ -210,3 +213,110 @@ class ErrorResponse(BaseModel):
|
||||
message: str = Field(description="Error message")
|
||||
detail: Optional[Dict] = Field(None, description="Additional error details")
|
||||
timestamp: datetime = Field(default_factory=datetime.utcnow, description="Error timestamp")
|
||||
|
||||
|
||||
# Capture Template Schemas
|
||||
|
||||
class TemplateCreate(BaseModel):
|
||||
"""Request to create a capture template."""
|
||||
|
||||
name: str = Field(description="Template name", min_length=1, max_length=100)
|
||||
engine_type: str = Field(description="Engine type (e.g., 'mss', 'dxcam', 'wgc')", min_length=1)
|
||||
engine_config: Dict = Field(default_factory=dict, description="Engine-specific configuration")
|
||||
description: Optional[str] = Field(None, description="Template description", max_length=500)
|
||||
|
||||
|
||||
class TemplateUpdate(BaseModel):
|
||||
"""Request to update a template."""
|
||||
|
||||
name: Optional[str] = Field(None, description="Template name", min_length=1, max_length=100)
|
||||
engine_config: Optional[Dict] = Field(None, description="Engine-specific configuration")
|
||||
description: Optional[str] = Field(None, description="Template description", max_length=500)
|
||||
|
||||
|
||||
class TemplateResponse(BaseModel):
|
||||
"""Template information response."""
|
||||
|
||||
id: str = Field(description="Template ID")
|
||||
name: str = Field(description="Template name")
|
||||
engine_type: str = Field(description="Engine type identifier")
|
||||
engine_config: Dict = Field(description="Engine-specific configuration")
|
||||
is_default: bool = Field(description="Whether this is a system default template")
|
||||
created_at: datetime = Field(description="Creation timestamp")
|
||||
updated_at: datetime = Field(description="Last update timestamp")
|
||||
description: Optional[str] = Field(None, description="Template description")
|
||||
|
||||
|
||||
class TemplateListResponse(BaseModel):
|
||||
"""List of templates response."""
|
||||
|
||||
templates: List[TemplateResponse] = Field(description="List of templates")
|
||||
count: int = Field(description="Number of templates")
|
||||
|
||||
|
||||
class EngineInfo(BaseModel):
|
||||
"""Capture engine information."""
|
||||
|
||||
type: str = Field(description="Engine type identifier (e.g., 'mss', 'dxcam')")
|
||||
name: str = Field(description="Human-readable engine name")
|
||||
default_config: Dict = Field(description="Default configuration for this engine")
|
||||
available: bool = Field(description="Whether engine is available on this system")
|
||||
|
||||
|
||||
class EngineListResponse(BaseModel):
|
||||
"""List of available engines response."""
|
||||
|
||||
engines: List[EngineInfo] = Field(description="Available capture engines")
|
||||
count: int = Field(description="Number of engines")
|
||||
|
||||
|
||||
class TemplateAssignment(BaseModel):
|
||||
"""Request to assign template to device."""
|
||||
|
||||
template_id: str = Field(description="Template ID to assign")
|
||||
|
||||
|
||||
class TemplateTestRequest(BaseModel):
|
||||
"""Request to test a capture template."""
|
||||
|
||||
engine_type: str = Field(description="Capture engine type to test")
|
||||
engine_config: Dict = Field(default={}, description="Engine configuration")
|
||||
display_index: int = Field(description="Display index to capture")
|
||||
border_width: int = Field(default=10, ge=1, le=100, description="Border width in pixels")
|
||||
capture_duration: float = Field(default=5.0, ge=1.0, le=30.0, description="Duration to capture in seconds")
|
||||
|
||||
|
||||
class CaptureImage(BaseModel):
|
||||
"""Captured image with metadata."""
|
||||
|
||||
image: str = Field(description="Base64-encoded image data")
|
||||
width: int = Field(description="Image width in pixels")
|
||||
height: int = Field(description="Image height in pixels")
|
||||
thumbnail_width: Optional[int] = Field(None, description="Thumbnail width (if resized)")
|
||||
thumbnail_height: Optional[int] = Field(None, description="Thumbnail height (if resized)")
|
||||
|
||||
|
||||
class BorderExtraction(BaseModel):
|
||||
"""Extracted border images."""
|
||||
|
||||
top: str = Field(description="Base64-encoded top border image")
|
||||
right: str = Field(description="Base64-encoded right border image")
|
||||
bottom: str = Field(description="Base64-encoded bottom border image")
|
||||
left: str = Field(description="Base64-encoded left border image")
|
||||
|
||||
|
||||
class PerformanceMetrics(BaseModel):
|
||||
"""Performance metrics for template test."""
|
||||
|
||||
capture_duration_s: float = Field(description="Total capture duration in seconds")
|
||||
frame_count: int = Field(description="Number of frames captured")
|
||||
actual_fps: float = Field(description="Actual FPS (frame_count / duration)")
|
||||
avg_capture_time_ms: float = Field(description="Average time per frame capture in milliseconds")
|
||||
|
||||
|
||||
class TemplateTestResponse(BaseModel):
|
||||
"""Response from template test."""
|
||||
|
||||
full_capture: CaptureImage = Field(description="Full screen capture with thumbnail")
|
||||
border_extraction: Optional[BorderExtraction] = Field(None, description="Extracted border images (deprecated)")
|
||||
performance: PerformanceMetrics = Field(description="Performance metrics")
|
||||
|
||||
Reference in New Issue
Block a user