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:
2026-02-26 13:55:46 +03:00
parent cbbaa852ed
commit bae2166bc2
35 changed files with 2163 additions and 402 deletions

View File

@@ -33,6 +33,7 @@ def _to_response(source: AudioSource) -> AudioSourceResponse:
source_type=source.source_type,
device_index=getattr(source, "device_index", None),
is_loopback=getattr(source, "is_loopback", None),
audio_template_id=getattr(source, "audio_template_id", None),
audio_source_id=getattr(source, "audio_source_id", None),
channel=getattr(source, "channel", None),
description=source.description,
@@ -73,6 +74,7 @@ async def create_audio_source(
audio_source_id=data.audio_source_id,
channel=data.channel,
description=data.description,
audio_template_id=data.audio_template_id,
)
return _to_response(source)
except ValueError as e:
@@ -110,6 +112,7 @@ async def update_audio_source(
audio_source_id=data.audio_source_id,
channel=data.channel,
description=data.description,
audio_template_id=data.audio_template_id,
)
return _to_response(source)
except ValueError as e: