mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
docs: archive historical plans, remove duplicates
- Move migration-roadmap.md, epub3_upgrade_plan.md, entities_step_overhaul_plan.md to docs/archive/ - Remove duplicate tts-plugin-architecture.md
This commit is contained in:
@@ -0,0 +1,689 @@
|
|||||||
|
# TTS Plugin Architecture — Final Specification
|
||||||
|
|
||||||
|
## 1. Core Domain
|
||||||
|
|
||||||
|
Zero dependencies. Pure business logic.
|
||||||
|
|
||||||
|
### 1.1 Engine
|
||||||
|
|
||||||
|
Factory for sessions. Stateless. Thread-safe for createSession().
|
||||||
|
|
||||||
|
```
|
||||||
|
interface Engine:
|
||||||
|
createSession() -> EngineSession
|
||||||
|
dispose() -> void
|
||||||
|
```
|
||||||
|
|
||||||
|
**createSession() contract**:
|
||||||
|
- Returns: EngineSession
|
||||||
|
- Raises: EngineError on failure
|
||||||
|
- Ownership: Transfers to caller
|
||||||
|
- Thread-safe: Yes
|
||||||
|
|
||||||
|
**dispose() contract**:
|
||||||
|
- Releases engine resources
|
||||||
|
- Caller must ensure all sessions created by this engine are disposed before calling dispose()
|
||||||
|
- Disposing an engine while any session is still alive violates the API contract; behavior is undefined
|
||||||
|
- Idempotent: Safe to call multiple times
|
||||||
|
- Never raises: Catches and logs internally
|
||||||
|
- After dispose(): All methods except dispose() raise EngineError
|
||||||
|
|
||||||
|
### 1.2 EngineSession
|
||||||
|
|
||||||
|
Owns mutable execution state isolated from other concurrent work. NOT thread-safe.
|
||||||
|
|
||||||
|
```
|
||||||
|
interface EngineSession:
|
||||||
|
synthesize(request: SynthesisRequest) -> SynthesizedAudio
|
||||||
|
dispose() -> void
|
||||||
|
```
|
||||||
|
|
||||||
|
**synthesize() contract**:
|
||||||
|
- Returns: SynthesizedAudio
|
||||||
|
- Raises: EngineError on failure (session remains usable)
|
||||||
|
- Thread-safe: No
|
||||||
|
|
||||||
|
**dispose() contract**:
|
||||||
|
- Releases session resources
|
||||||
|
- Idempotent: Safe to call multiple times
|
||||||
|
- Never raises: Catches and logs internally
|
||||||
|
- After dispose(): All methods except dispose() raise EngineError
|
||||||
|
|
||||||
|
### 1.3 SynthesisRequest
|
||||||
|
|
||||||
|
Immutable value object.
|
||||||
|
|
||||||
|
```
|
||||||
|
SynthesisRequest:
|
||||||
|
text: string
|
||||||
|
voice: VoiceSelection
|
||||||
|
parameters: ParameterValues
|
||||||
|
format: AudioFormat
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 SynthesizedAudio
|
||||||
|
|
||||||
|
Immutable value object.
|
||||||
|
|
||||||
|
```
|
||||||
|
SynthesizedAudio:
|
||||||
|
data: bytes
|
||||||
|
format: AudioFormat
|
||||||
|
duration: Duration
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.5 VoiceSelection
|
||||||
|
|
||||||
|
Immutable value object. Opaque to engine.
|
||||||
|
|
||||||
|
```
|
||||||
|
VoiceSelection:
|
||||||
|
source: string
|
||||||
|
key: string
|
||||||
|
payload: any = None # Optional; required for clone/blend sources
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.6 ParameterValues
|
||||||
|
|
||||||
|
Immutable value object. Behaves like Mapping[str, Any].
|
||||||
|
|
||||||
|
```
|
||||||
|
ParameterValues:
|
||||||
|
values: Mapping[str, Any]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.7 AudioFormat
|
||||||
|
|
||||||
|
Immutable value object.
|
||||||
|
|
||||||
|
```
|
||||||
|
AudioFormat:
|
||||||
|
mime: string
|
||||||
|
extension: string
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.8 Duration
|
||||||
|
|
||||||
|
Immutable value object.
|
||||||
|
|
||||||
|
```
|
||||||
|
Duration:
|
||||||
|
seconds: number
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.9 EngineConfig
|
||||||
|
|
||||||
|
Engine initialization settings only. No resource references.
|
||||||
|
|
||||||
|
```
|
||||||
|
EngineConfig:
|
||||||
|
device: string # "cpu", "cuda:0", etc.
|
||||||
|
# Engine-specific settings (if any)
|
||||||
|
# Unknown keys are ignored (no error)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Error Hierarchy
|
||||||
|
|
||||||
|
Typed exceptions. Engines raise EngineError or subtypes. Never raw exceptions.
|
||||||
|
|
||||||
|
```
|
||||||
|
EngineError (base)
|
||||||
|
├── ModelNotFoundError
|
||||||
|
├── ModelLoadError
|
||||||
|
├── NetworkError
|
||||||
|
├── InvalidInputError
|
||||||
|
├── ConfigurationError
|
||||||
|
├── CancelledError
|
||||||
|
└── InternalError
|
||||||
|
```
|
||||||
|
|
||||||
|
**Contract**:
|
||||||
|
- synthesize() raises EngineError on failure, session remains usable
|
||||||
|
- dispose() never raises (catches and logs internally)
|
||||||
|
- create_engine() raises EngineError on failure, cleans up partially created resources
|
||||||
|
- createSession() raises EngineError on failure, no partially initialized session returned
|
||||||
|
- cancel() causes synthesize() to raise CancelledError
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Capability Interfaces (Optional)
|
||||||
|
|
||||||
|
Engines implement only what they support. Capabilities are additive.
|
||||||
|
|
||||||
|
### 3.1 VoiceLister
|
||||||
|
|
||||||
|
```
|
||||||
|
interface VoiceLister:
|
||||||
|
listVoices(sourceId: string) -> list[VoiceManifest]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 PreviewGenerator
|
||||||
|
|
||||||
|
```
|
||||||
|
interface PreviewGenerator:
|
||||||
|
generatePreview(voice: VoiceSelection, text: string) -> SynthesizedAudio
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 ModelRequirements
|
||||||
|
|
||||||
|
Static at plugin level, not engine level. Host reads before creating engine.
|
||||||
|
|
||||||
|
```
|
||||||
|
MODEL_REQUIREMENTS = list[ModelManifest]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 StreamingSynthesizer
|
||||||
|
|
||||||
|
Optional capability of EngineSession, not Engine.
|
||||||
|
|
||||||
|
```
|
||||||
|
interface StreamingSynthesizer:
|
||||||
|
synthesizeStream(request: SynthesisRequest) -> Iterator[bytes]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Iterator contract**:
|
||||||
|
- Yields audio chunks as they become available
|
||||||
|
- Raises CancelledError if cancel() is called during iteration
|
||||||
|
- Raises EngineError on synthesis failure
|
||||||
|
- Iterator exhaustion = synthesis complete
|
||||||
|
- Session remains usable after iterator completes
|
||||||
|
|
||||||
|
### 3.5 CancelableSession
|
||||||
|
|
||||||
|
Optional capability for engines that support cancellation.
|
||||||
|
|
||||||
|
```
|
||||||
|
interface CancelableSession:
|
||||||
|
cancel() -> void
|
||||||
|
```
|
||||||
|
|
||||||
|
**cancel() contract**:
|
||||||
|
- Cancels in-progress synthesize()
|
||||||
|
- synthesize() raises CancelledError (subtype of EngineError)
|
||||||
|
- EngineSession remains usable after cancellation (unless implementation documents otherwise)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Plugin Manifest
|
||||||
|
|
||||||
|
Static metadata. Immutable. No dependencies.
|
||||||
|
|
||||||
|
### 4.1 PluginManifest
|
||||||
|
|
||||||
|
```
|
||||||
|
PluginManifest:
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
api_version: string # semver format: MAJOR.MINOR
|
||||||
|
description: string
|
||||||
|
author: string
|
||||||
|
capabilities: list[string]
|
||||||
|
requires: RequirementManifest
|
||||||
|
engine: EngineManifest
|
||||||
|
```
|
||||||
|
|
||||||
|
**api_version contract**:
|
||||||
|
- Format: semver (MAJOR.MINOR)
|
||||||
|
- Compatibility: Host rejects plugin if major version differs
|
||||||
|
- Minor version: backward compatible, Host accepts higher minor
|
||||||
|
|
||||||
|
### 4.2 EngineManifest
|
||||||
|
|
||||||
|
```
|
||||||
|
EngineManifest:
|
||||||
|
voiceSources: list[VoiceSourceManifest]
|
||||||
|
parameters: list[ParameterManifest]
|
||||||
|
audioFormats: list[AudioFormatManifest]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 VoiceSourceManifest
|
||||||
|
|
||||||
|
```
|
||||||
|
VoiceSourceManifest:
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
type: string # "list", "speaker_id", "clone", "blend", "generate", "none"
|
||||||
|
config: any
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 VoiceManifest
|
||||||
|
|
||||||
|
```
|
||||||
|
VoiceManifest:
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
tags: list[string]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.5 ParameterManifest
|
||||||
|
|
||||||
|
```
|
||||||
|
ParameterManifest:
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
type: string # "float", "int", "string", "boolean", "enum"
|
||||||
|
default: any
|
||||||
|
min: number (optional)
|
||||||
|
max: number (optional)
|
||||||
|
step: number (optional)
|
||||||
|
options: list[EnumOption] (optional)
|
||||||
|
unit: string (optional)
|
||||||
|
group: string (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.6 AudioFormatManifest
|
||||||
|
|
||||||
|
```
|
||||||
|
AudioFormatManifest:
|
||||||
|
mime: string
|
||||||
|
extension: string
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.7 EnumOption
|
||||||
|
|
||||||
|
```
|
||||||
|
EnumOption:
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.8 RequirementManifest
|
||||||
|
|
||||||
|
```
|
||||||
|
RequirementManifest:
|
||||||
|
gpu: GpuRequirement (optional)
|
||||||
|
memory: number (optional)
|
||||||
|
internet: boolean (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.9 GpuRequirement
|
||||||
|
|
||||||
|
```
|
||||||
|
GpuRequirement:
|
||||||
|
required: boolean
|
||||||
|
type: string (optional)
|
||||||
|
memory: number (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.10 ModelManifest
|
||||||
|
|
||||||
|
```
|
||||||
|
ModelManifest:
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
size: string
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Host Services
|
||||||
|
|
||||||
|
### 5.1 HostContext
|
||||||
|
|
||||||
|
Minimal. 3 fields maximum. No business logic.
|
||||||
|
|
||||||
|
```
|
||||||
|
HostContext:
|
||||||
|
config_dir: Path # For API keys, preferences
|
||||||
|
logger: Logger # For logging
|
||||||
|
http_client: HttpClient # For network requests
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Plugin Contract
|
||||||
|
|
||||||
|
### 6.1 Plugin Exports
|
||||||
|
|
||||||
|
```python
|
||||||
|
# plugins/kokoro/__init__.py
|
||||||
|
|
||||||
|
PLUGIN_MANIFEST = PluginManifest(...)
|
||||||
|
MODEL_REQUIREMENTS = [...] # Static at plugin level
|
||||||
|
|
||||||
|
def create_engine(
|
||||||
|
context: HostContext,
|
||||||
|
model_path: Path | None,
|
||||||
|
config: EngineConfig
|
||||||
|
) -> Engine:
|
||||||
|
"""Create engine. Atomic: succeeds fully or raises and cleans up."""
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 create_engine() Contract
|
||||||
|
|
||||||
|
- Parameters:
|
||||||
|
- context: HostContext (host services)
|
||||||
|
- model_path: Path | None (resolved model path, or None for cloud/no-model engines)
|
||||||
|
- config: EngineConfig (engine initialization settings)
|
||||||
|
- Returns: Engine
|
||||||
|
- Raises: EngineError on failure
|
||||||
|
- Atomic: Succeeds fully or cleans up and raises
|
||||||
|
- Thread-safe: Can be called from any thread
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Object Lifecycle
|
||||||
|
|
||||||
|
### 7.1 Engine Lifecycle
|
||||||
|
|
||||||
|
```
|
||||||
|
1. DISCOVERY
|
||||||
|
Host scans plugin directories
|
||||||
|
Loads PLUGIN_MANIFEST and MODEL_REQUIREMENTS
|
||||||
|
|
||||||
|
2. MODEL DOWNLOAD (if MODEL_REQUIREMENTS non-empty)
|
||||||
|
Host reads MODEL_REQUIREMENTS
|
||||||
|
Downloads/caches required models
|
||||||
|
Resolves model_path for create_engine()
|
||||||
|
|
||||||
|
3. ACTIVATION
|
||||||
|
Host calls create_engine(context, model_path, config)
|
||||||
|
Engine created, ready to use
|
||||||
|
Raises EngineError on failure
|
||||||
|
|
||||||
|
4. SESSION CREATION
|
||||||
|
Client calls engine.createSession()
|
||||||
|
Returns EngineSession
|
||||||
|
Ownership transfers to caller
|
||||||
|
Raises EngineError on failure
|
||||||
|
No partially initialized session returned
|
||||||
|
|
||||||
|
5. SYNTHESIS
|
||||||
|
Client calls session.synthesize(request)
|
||||||
|
Returns SynthesizedAudio
|
||||||
|
Raises EngineError on failure (session remains usable)
|
||||||
|
|
||||||
|
6. SESSION DISPOSAL
|
||||||
|
Client calls session.dispose()
|
||||||
|
Releases session resources
|
||||||
|
|
||||||
|
7. DEACTIVATION
|
||||||
|
Client calls engine.dispose()
|
||||||
|
Caller must ensure all sessions are disposed first
|
||||||
|
Disposing engine while sessions are alive is undefined behavior
|
||||||
|
Releases engine resources
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 EngineSession Lifecycle
|
||||||
|
|
||||||
|
```
|
||||||
|
1. CREATION
|
||||||
|
Created by Engine.createSession()
|
||||||
|
Ownership transfers to caller
|
||||||
|
Raises EngineError on failure
|
||||||
|
|
||||||
|
2. USAGE
|
||||||
|
Client calls synthesize() one or more times
|
||||||
|
Each call returns SynthesizedAudio or raises EngineError
|
||||||
|
Session remains usable after synthesize() failure
|
||||||
|
If CancelableSession: cancel() causes synthesize() to raise CancelledError
|
||||||
|
If StreamingSynthesizer: iterator raises CancelledError on cancel(), EngineError on failure
|
||||||
|
|
||||||
|
3. DISPOSAL
|
||||||
|
Client calls dispose()
|
||||||
|
Releases session resources
|
||||||
|
After dispose(), all methods except dispose() raise EngineError
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Ownership Rules
|
||||||
|
|
||||||
|
- Engine.createSession() transfers ownership of the returned session to the caller
|
||||||
|
- Caller is responsible for disposing all sessions before disposing the engine
|
||||||
|
- Engine does not track sessions; it has no lifecycle registry
|
||||||
|
- Disposing an engine while any session is still alive violates the API contract; behavior is undefined
|
||||||
|
- This design avoids coupling, synchronization overhead, and lifecycle registry complexity
|
||||||
|
|
||||||
|
### 7.4 Concurrent Operations
|
||||||
|
|
||||||
|
**Engine.dispose() concurrent with Engine.createSession()**:
|
||||||
|
- createSession() must either succeed with fully initialized EngineSession or raise EngineError
|
||||||
|
- Partially initialized EngineSession must never be returned
|
||||||
|
- After dispose() completes, subsequent createSession() calls must raise EngineError
|
||||||
|
|
||||||
|
**EngineSession.dispose() concurrent with EngineSession.synthesize()**:
|
||||||
|
- Not thread-safe. Caller must ensure synthesize() completes before dispose().
|
||||||
|
|
||||||
|
**EngineSession.dispose() concurrent with StreamingSynthesizer.synthesizeStream()**:
|
||||||
|
- Not thread-safe. Caller must ensure stream iteration completes before dispose().
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Thread Safety Contract
|
||||||
|
|
||||||
|
| Component | Thread-safe | Notes |
|
||||||
|
|-----------|-------------|-------|
|
||||||
|
| Engine | Yes | createSession() can be called from any thread |
|
||||||
|
| EngineSession | No | synthesize() must be called from one thread at a time |
|
||||||
|
| HostContext | Yes | Provides shared services |
|
||||||
|
| VoiceSelection | Yes | Immutable value object |
|
||||||
|
| ParameterValues | Yes | Immutable value object |
|
||||||
|
| AudioFormat | Yes | Immutable value object |
|
||||||
|
| EngineConfig | Yes | Immutable value object |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. dispose() Contract
|
||||||
|
|
||||||
|
**General rules**:
|
||||||
|
- Calling dispose() multiple times is safe (no-op on second call)
|
||||||
|
- dispose() never raises exceptions (catches and logs internally)
|
||||||
|
- After dispose(), all methods except dispose() raise EngineError
|
||||||
|
|
||||||
|
**Engine.dispose()**:
|
||||||
|
- Caller must ensure all sessions are disposed first
|
||||||
|
- Disposing engine while sessions are alive violates API contract; behavior is undefined
|
||||||
|
- Releases engine resources
|
||||||
|
|
||||||
|
**EngineSession.dispose()**:
|
||||||
|
- Releases session resources
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Dependency Rules
|
||||||
|
|
||||||
|
```
|
||||||
|
Core Domain (Engine, EngineSession, Value Objects)
|
||||||
|
-> No dependencies
|
||||||
|
|
||||||
|
Plugin Manifest (PluginManifest, ModelManifest, etc.)
|
||||||
|
-> No dependencies
|
||||||
|
|
||||||
|
Host Context (HostContext)
|
||||||
|
-> Depends on: Core Domain (for types)
|
||||||
|
|
||||||
|
Plugin Implementation
|
||||||
|
-> Depends on: Core Domain, Host Context
|
||||||
|
|
||||||
|
Host
|
||||||
|
-> Depends on: Core Domain, Plugin Manifest
|
||||||
|
```
|
||||||
|
|
||||||
|
**Forbidden**:
|
||||||
|
- Core Domain -> anything else
|
||||||
|
- Plugin Manifest -> anything else
|
||||||
|
- Plugin Implementation -> Host (only receives HostContext)
|
||||||
|
- Host -> Plugin Implementation (only via create_engine function)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Architectural Invariants
|
||||||
|
|
||||||
|
1. Core Domain has zero dependencies
|
||||||
|
2. Plugins receive HostContext at creation, not via global state
|
||||||
|
3. Model requirements are static (plugin level), not dynamic (engine level)
|
||||||
|
4. Host validates capability implementation at load time: each capability declared in PluginManifest.capabilities must be implemented by the exported object via the corresponding interface
|
||||||
|
5. synthesize() raises typed exceptions, not returns Result
|
||||||
|
6. dispose() is idempotent and never raises
|
||||||
|
7. No global state, no service locator
|
||||||
|
8. VoiceSelection and ParameterValues are opaque to engine
|
||||||
|
9. Display information comes from VoiceManifest
|
||||||
|
10. HostContext is minimal (3 fields max)
|
||||||
|
11. EngineConfig contains only engine settings, not resource references
|
||||||
|
12. EngineSession owns mutable execution state isolated from other concurrent work
|
||||||
|
13. Engine.createSession() transfers ownership to caller
|
||||||
|
14. Caller must dispose all sessions before disposing engine
|
||||||
|
15. After dispose(), all methods except dispose() raise EngineError
|
||||||
|
16. create_engine() is atomic (all-or-nothing)
|
||||||
|
17. Garbage collection without dispose() may leak (documented)
|
||||||
|
18. Capabilities are additive (new capabilities don't break old plugins)
|
||||||
|
19. api_version enables compatibility checking
|
||||||
|
20. createSession() returns fully initialized session or raises, never partial
|
||||||
|
21. cancel() causes synthesize() to raise CancelledError
|
||||||
|
22. EngineSession remains usable after cancellation
|
||||||
|
23. Engine does not track sessions; no lifecycle registry
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Validation Examples
|
||||||
|
|
||||||
|
### 12.1 Kokoro
|
||||||
|
|
||||||
|
```python
|
||||||
|
PLUGIN_MANIFEST = PluginManifest(
|
||||||
|
id="kokoro",
|
||||||
|
api_version="1.0",
|
||||||
|
capabilities=["voice_list", "preview", "voice_blend"],
|
||||||
|
engine=EngineManifest(
|
||||||
|
voiceSources=[
|
||||||
|
VoiceSourceManifest(id="builtin", type="list", config={"voices": [...]}),
|
||||||
|
VoiceSourceManifest(id="formula", type="blend", config={"syntax": "{a}*0.5+{b}*0.5"}),
|
||||||
|
],
|
||||||
|
parameters=[ParameterManifest(id="speed", type="float", default=1.0, min=0.5, max=2.0)],
|
||||||
|
audioFormats=[AudioFormatManifest(mime="audio/wav", extension="wav")],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
MODEL_REQUIREMENTS = []
|
||||||
|
|
||||||
|
def create_engine(context: HostContext, model_path: Path | None, config: EngineConfig) -> Engine:
|
||||||
|
model = load_kokoro(model_path)
|
||||||
|
return KokoroEngine(model, config.device)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 12.2 SuperTonic
|
||||||
|
|
||||||
|
```python
|
||||||
|
PLUGIN_MANIFEST = PluginManifest(
|
||||||
|
id="supertonic",
|
||||||
|
api_version="1.0",
|
||||||
|
capabilities=["voice_list", "preview"],
|
||||||
|
engine=EngineManifest(
|
||||||
|
voiceSources=[VoiceSourceManifest(id="builtin", type="list", config={"voices": [...]})],
|
||||||
|
parameters=[
|
||||||
|
ParameterManifest(id="speed", type="float", default=1.0, min=0.5, max=2.0),
|
||||||
|
ParameterManifest(id="steps", type="int", default=20, min=5, max=50),
|
||||||
|
],
|
||||||
|
audioFormats=[AudioFormatManifest(mime="audio/wav", extension="wav")],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
MODEL_REQUIREMENTS = []
|
||||||
|
|
||||||
|
def create_engine(context: HostContext, model_path: Path | None, config: EngineConfig) -> Engine:
|
||||||
|
model = load_supertonic(model_path)
|
||||||
|
return SuperTonicEngine(model, config.device)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 12.3 ElevenLabs
|
||||||
|
|
||||||
|
```python
|
||||||
|
PLUGIN_MANIFEST = PluginManifest(
|
||||||
|
id="elevenlabs",
|
||||||
|
api_version="1.0",
|
||||||
|
capabilities=["voice_list"],
|
||||||
|
requires=RequirementManifest(internet=True),
|
||||||
|
engine=EngineManifest(
|
||||||
|
voiceSources=[VoiceSourceManifest(id="cloud", type="list", config={"speakers": [...]})],
|
||||||
|
parameters=[ParameterManifest(id="stability", type="float", default=0.5, min=0.0, max=1.0)],
|
||||||
|
audioFormats=[AudioFormatManifest(mime="audio/mpeg", extension="mp3")],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
MODEL_REQUIREMENTS = []
|
||||||
|
|
||||||
|
def create_engine(context: HostContext, model_path: Path | None, config: EngineConfig) -> Engine:
|
||||||
|
api_key = (context.config_dir / "elevenlabs_key").read_text()
|
||||||
|
return ElevenLabsEngine(api_key)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 12.4 Piper
|
||||||
|
|
||||||
|
```python
|
||||||
|
PLUGIN_MANIFEST = PluginManifest(
|
||||||
|
id="piper",
|
||||||
|
api_version="1.0",
|
||||||
|
capabilities=[],
|
||||||
|
engine=EngineManifest(
|
||||||
|
voiceSources=[VoiceSourceManifest(id="downloadable", type="list", config={"models": [...]})],
|
||||||
|
parameters=[ParameterManifest(id="speed", type="float", default=1.0, min=0.5, max=2.0)],
|
||||||
|
audioFormats=[AudioFormatManifest(mime="audio/wav", extension="wav")],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
MODEL_REQUIREMENTS = [
|
||||||
|
ModelManifest(id="en_US-lessac-medium", name="English Lessac Medium", size="100MB"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def create_engine(context: HostContext, model_path: Path | None, config: EngineConfig) -> Engine:
|
||||||
|
return PiperEngine(model_path, config.device)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 12.5 XTTS (with streaming and cancellation)
|
||||||
|
|
||||||
|
```python
|
||||||
|
PLUGIN_MANIFEST = PluginManifest(
|
||||||
|
id="xtts",
|
||||||
|
api_version="1.0",
|
||||||
|
capabilities=["voice_list", "preview", "voice_clone", "streaming", "cancel"],
|
||||||
|
requires=RequirementManifest(gpu=GpuRequirement(required=True, type="cuda")),
|
||||||
|
engine=EngineManifest(
|
||||||
|
voiceSources=[
|
||||||
|
VoiceSourceManifest(id="speakers", type="speaker_id", config={"speakers": [...]}),
|
||||||
|
VoiceSourceManifest(id="clone", type="clone", config={"requiresAudio": True, "maxDuration": 30}),
|
||||||
|
],
|
||||||
|
parameters=[ParameterManifest(id="temperature", type="float", default=0.7, min=0.1, max=1.0)],
|
||||||
|
audioFormats=[AudioFormatManifest(mime="audio/wav", extension="wav")],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
MODEL_REQUIREMENTS = [
|
||||||
|
ModelManifest(id="xtts_v2", name="XTTS v2", size="2GB"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def create_engine(context: HostContext, model_path: Path | None, config: EngineConfig) -> Engine:
|
||||||
|
return XTTSEngine(model_path, config.device)
|
||||||
|
```
|
||||||
|
|
||||||
|
XTTS session implements: EngineSession, StreamingSynthesizer, CancelableSession.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Summary of All Decisions
|
||||||
|
|
||||||
|
| Aspect | Decision |
|
||||||
|
|--------|----------|
|
||||||
|
| Engine | Factory, stateless, thread-safe for createSession() |
|
||||||
|
| EngineSession | Owns mutable execution state, not thread-safe |
|
||||||
|
| EngineSession ownership | Caller owns (transferred from createSession) |
|
||||||
|
| Engine session tracking | None; engine does not track sessions |
|
||||||
|
| StreamingSynthesizer | Optional capability of EngineSession |
|
||||||
|
| CancelableSession | Optional capability, cancel() raises CancelledError |
|
||||||
|
| dispose() | Idempotent, never raises |
|
||||||
|
| Engine.dispose() | Caller must dispose sessions first; undefined if violated |
|
||||||
|
| createSession() | Raises EngineError on failure, no partial sessions |
|
||||||
|
| create_engine() | Atomic, takes context, model_path, config |
|
||||||
|
| EngineConfig | Engine settings only, no resource references |
|
||||||
|
| model_path | Separate argument, not in EngineConfig |
|
||||||
|
| MODEL_REQUIREMENTS | Static at plugin level |
|
||||||
|
| HostContext | Minimal (3 fields) |
|
||||||
|
| Error handling | Typed exceptions (EngineError hierarchy) |
|
||||||
|
| Thread safety | Documented per component |
|
||||||
|
| Capabilities | Additive, optional interfaces |
|
||||||
|
| API versioning | api_version in manifest |
|
||||||
|
| Concurrent dispose/createSession | Fully initialized session or EngineError |
|
||||||
|
| Concurrent dispose/synthesizeStream | Not thread-safe; caller must complete iteration first |
|
||||||
Reference in New Issue
Block a user