Add audio capture engine template system with multi-backend support
Introduces an engine+template abstraction for audio capture, mirroring the existing screen capture engine pattern. This enables multiple audio backends (WASAPI for Windows, sounddevice for cross-platform) with per-source engine configuration via reusable templates. Backend: - AudioCaptureEngine ABC with WasapiEngine and SounddeviceEngine implementations - AudioEngineRegistry for engine discovery and factory creation - AudioAnalyzer class decouples FFT/RMS/beat analysis from engine-specific capture - ManagedAudioStream wraps engine stream + analyzer in background thread - AudioCaptureTemplate model and AudioTemplateStore with JSON CRUD - AudioCaptureManager keyed by (engine_type, device_index, is_loopback) - Auto-migration: default template created on startup, assigned to existing sources - Full REST API: CRUD for audio templates + engine listing with availability flags - audio_template_id added to MultichannelAudioSource model and API schemas Frontend: - Audio template cards in Streams > Audio tab with engine badge and config details - Audio template editor modal with engine selector and dynamic config fields - Audio template dropdown in multichannel audio source editor - Template name crosslink badge on multichannel audio source cards - Confirm modal z-index fix (always stacks above editor modals) - i18n keys for EN and RU Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ class AudioSourceCreate(BaseModel):
|
||||
# multichannel fields
|
||||
device_index: Optional[int] = Field(None, description="Audio device index (-1 = default)")
|
||||
is_loopback: Optional[bool] = Field(None, description="True for system audio (WASAPI loopback)")
|
||||
audio_template_id: Optional[str] = Field(None, description="Audio capture template ID")
|
||||
# mono fields
|
||||
audio_source_id: Optional[str] = Field(None, description="Parent multichannel audio source ID")
|
||||
channel: Optional[str] = Field(None, description="Channel: mono|left|right")
|
||||
@@ -26,6 +27,7 @@ class AudioSourceUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, description="Source name", min_length=1, max_length=100)
|
||||
device_index: Optional[int] = Field(None, description="Audio device index (-1 = default)")
|
||||
is_loopback: Optional[bool] = Field(None, description="True for system audio (WASAPI loopback)")
|
||||
audio_template_id: Optional[str] = Field(None, description="Audio capture template ID")
|
||||
audio_source_id: Optional[str] = Field(None, description="Parent multichannel audio source ID")
|
||||
channel: Optional[str] = Field(None, description="Channel: mono|left|right")
|
||||
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
||||
@@ -39,6 +41,7 @@ class AudioSourceResponse(BaseModel):
|
||||
source_type: str = Field(description="Source type: multichannel or mono")
|
||||
device_index: Optional[int] = Field(None, description="Audio device index")
|
||||
is_loopback: Optional[bool] = Field(None, description="WASAPI loopback mode")
|
||||
audio_template_id: Optional[str] = Field(None, description="Audio capture template ID")
|
||||
audio_source_id: Optional[str] = Field(None, description="Parent multichannel source ID")
|
||||
channel: Optional[str] = Field(None, description="Channel: mono|left|right")
|
||||
description: Optional[str] = Field(None, description="Description")
|
||||
|
||||
59
server/src/wled_controller/api/schemas/audio_templates.py
Normal file
59
server/src/wled_controller/api/schemas/audio_templates.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""Audio capture template and engine schemas."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class AudioTemplateCreate(BaseModel):
|
||||
"""Request to create an audio capture template."""
|
||||
|
||||
name: str = Field(description="Template name", min_length=1, max_length=100)
|
||||
engine_type: str = Field(description="Audio engine type (e.g., 'wasapi', 'sounddevice')", 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 AudioTemplateUpdate(BaseModel):
|
||||
"""Request to update an audio template."""
|
||||
|
||||
name: Optional[str] = Field(None, description="Template name", min_length=1, max_length=100)
|
||||
engine_type: Optional[str] = Field(None, description="Audio engine type")
|
||||
engine_config: Optional[Dict] = Field(None, description="Engine-specific configuration")
|
||||
description: Optional[str] = Field(None, description="Template description", max_length=500)
|
||||
|
||||
|
||||
class AudioTemplateResponse(BaseModel):
|
||||
"""Audio 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")
|
||||
created_at: datetime = Field(description="Creation timestamp")
|
||||
updated_at: datetime = Field(description="Last update timestamp")
|
||||
description: Optional[str] = Field(None, description="Template description")
|
||||
|
||||
|
||||
class AudioTemplateListResponse(BaseModel):
|
||||
"""List of audio templates response."""
|
||||
|
||||
templates: List[AudioTemplateResponse] = Field(description="List of audio templates")
|
||||
count: int = Field(description="Number of templates")
|
||||
|
||||
|
||||
class AudioEngineInfo(BaseModel):
|
||||
"""Audio capture engine information."""
|
||||
|
||||
type: str = Field(description="Engine type identifier (e.g., 'wasapi', 'sounddevice')")
|
||||
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 AudioEngineListResponse(BaseModel):
|
||||
"""List of audio engines response."""
|
||||
|
||||
engines: List[AudioEngineInfo] = Field(description="Available audio engines")
|
||||
count: int = Field(description="Number of engines")
|
||||
Reference in New Issue
Block a user