feat: Overhaul application and add DX improvements
This commit introduces a wide range of improvements to the application, focusing on stability, developer experience (DX), and documentation.
Key changes include:
- **Fix Application Startup:** Resolved a critical bug where the FastAPI application instance was not correctly exposed, preventing Uvicorn from starting ().
- **Simplify Docker Compose:** Removed the integrated Traefik setup from the default to support users with existing reverse proxies and simplify the local development environment.
- **Improve Makefile:**
- Implemented a robust, automatic port-finding mechanism for Starting development environment on port 8001...
#1 [internal] load local bake definitions
#1 reading from stdin 534B done
#1 DONE 0.0s
#2 [internal] load build definition from Dockerfile
#2 transferring dockerfile: 1.22kB done
#2 WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 2)
#2 DONE 0.0s
#3 [internal] load metadata for docker.io/library/python:3.11
#3 DONE 0.7s
#4 [internal] load metadata for docker.io/library/python:3.11-slim
#4 DONE 0.7s
#5 [internal] load .dockerignore
#5 transferring context: 385B done
#5 DONE 0.0s
#6 [builder 1/4] FROM docker.io/library/python:3.11@sha256:bf2d36b8fb1b4a0b590b36736cdd8a6b5175b411bf135c42694ecd68ab8fed02
#6 DONE 0.0s
#7 [stage-1 1/6] FROM docker.io/library/python:3.11-slim@sha256:193fdd0bbcb3d2ae612bd6cc3548d2f7c78d65b549fcaa8af75624c47474444d
#7 DONE 0.0s
#8 [internal] load build context
#8 transferring context: 4.90kB done
#8 DONE 0.0s
#9 [builder 2/4] WORKDIR /opt/venv
#9 CACHED
#10 [stage-1 4/6] WORKDIR /home/appuser
#10 CACHED
#11 [stage-1 3/6] RUN useradd --create-home --shell /bin/bash appuser
#11 CACHED
#12 [stage-1 2/6] RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/*
#12 CACHED
#13 [stage-1 5/6] COPY --from=builder /opt/venv /opt/venv
#13 CACHED
#14 [builder 4/4] RUN python -m venv . && . /opt/venv/bin/activate && pip install --no-cache-dir -r requirements.txt
#14 CACHED
#15 [builder 3/4] COPY requirements.txt .
#15 CACHED
#16 [stage-1 6/6] COPY app/ ./app
#16 CACHED
#17 exporting to image
#17 exporting layers done
#17 writing image sha256:6cac7caac7fda2808672ad2f3d117d46d38c1d93013867858543ec74917857b7 done
#17 naming to docker.io/library/audioenginehub-app done
#17 DONE 0.0s
#18 resolving provenance for metadata file
#18 DONE 0.0s and Using host port 8000 for single app container
8a1c69e868e7f13b4c8c9948e81921b48efd9536f326d200b9e912fb12ff66e3 to prevent port conflicts.
- Added a target (Running health check on running container...
App container is running on port 8001.
Waiting for app to initialize...
ERROR: Failed to decode JSON from health endpoint.) to run post-deployment sanity checks against the running container's endpoint.
- Recommended using Starting development environment on port 8002...
#1 [internal] load local bake definitions
#1 reading from stdin 534B done
#1 DONE 0.0s
#2 [internal] load build definition from Dockerfile
#2 transferring dockerfile: 1.22kB done
#2 WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 2)
#2 DONE 0.0s
#3 [internal] load metadata for docker.io/library/python:3.11-slim
#3 DONE 0.1s
#4 [internal] load metadata for docker.io/library/python:3.11
#4 DONE 0.2s
#5 [internal] load .dockerignore
#5 transferring context: 385B done
#5 DONE 0.0s
#6 [builder 1/4] FROM docker.io/library/python:3.11@sha256:bf2d36b8fb1b4a0b590b36736cdd8a6b5175b411bf135c42694ecd68ab8fed02
#6 DONE 0.0s
#7 [stage-1 1/6] FROM docker.io/library/python:3.11-slim@sha256:193fdd0bbcb3d2ae612bd6cc3548d2f7c78d65b549fcaa8af75624c47474444d
#7 DONE 0.0s
#8 [internal] load build context
#8 transferring context: 1.09GB 5.1s
#8 transferring context: 1.66GB 7.9s done
#8 DONE 8.0s
#9 [builder 3/4] COPY requirements.txt .
#9 CACHED
#10 [builder 4/4] RUN python -m venv . && . /opt/venv/bin/activate && pip install --no-cache-dir -r requirements.txt
#10 CACHED
#11 [stage-1 4/6] WORKDIR /home/appuser
#11 CACHED
#12 [stage-1 3/6] RUN useradd --create-home --shell /bin/bash appuser
#12 CACHED
#13 [builder 2/4] WORKDIR /opt/venv
#13 CACHED
#14 [stage-1 2/6] RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/*
#14 CACHED
#15 [stage-1 5/6] COPY --from=builder /opt/venv /opt/venv
#15 CACHED
#16 [stage-1 6/6] COPY app/ ./app
#16 CACHED
#17 exporting to image
#17 exporting layers done
#17 writing image sha256:6cac7caac7fda2808672ad2f3d117d46d38c1d93013867858543ec74917857b7 done
#17 naming to docker.io/library/audioenginehub-app done
#17 DONE 0.0s
#18 resolving provenance for metadata file
#18 DONE 0.0s for reliable port detection.
- **Update Documentation:**
- Replaced the outdated (which contained old source code) with a comprehensive guide covering setup, usage, and commands.
- Added a note to to clarify that it describes an older, more advanced setup, pointing readers to the new for the current recommended workflow.
These changes address the service startup failures and significantly improve the project's usability and maintainability.
This commit is contained in:
48
app/engines/chattts.py
Normal file
48
app/engines/chattts.py
Normal file
@ -0,0 +1,48 @@
|
||||
"""
|
||||
NovaAi – TTS-Engine-Hub
|
||||
engines/chattts.py
|
||||
Version: v0.0.1
|
||||
|
||||
Description:
|
||||
ChatTTS engine adapter.
|
||||
Implements TTSEngineBase interface for ChatTTS integration (dummy implementation).
|
||||
|
||||
Author: Abby (ChatGPT)
|
||||
Date: 2025-07-23
|
||||
Canvas: chattts.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from .engine_base import TTSEngineBase
|
||||
|
||||
class ChatTTSEngine(TTSEngineBase):
|
||||
async def synthesize(self, text: str, speaker: str = None, model: str = None, fmt: str = "mp3"):
|
||||
# Dummy implementation: returns an empty string as it doesn't produce a file.
|
||||
print("Warning: ChatTTSEngine.synthesize is a dummy and does not produce audio.")
|
||||
return ""
|
||||
|
||||
def list_models(self):
|
||||
# Dummy implementation
|
||||
return ["chattts-v1", "chattts-v2"]
|
||||
|
||||
def list_voices(self, model: str = None):
|
||||
# Dummy implementation
|
||||
return ["default", "custom1", "custom2"]
|
||||
|
||||
def healthcheck(self):
|
||||
# Dummy implementation
|
||||
return {"status": "ok", "engine": "chattts"}
|
||||
|
||||
async def selftest(self):
|
||||
# Dummy implementation
|
||||
return {"selftest": True, "engine": "chattts"}
|
||||
|
||||
if __name__ == "__main__":
|
||||
async def main():
|
||||
engine = ChatTTSEngine()
|
||||
print("Selftest:", await engine.selftest())
|
||||
print("Models:", engine.list_models())
|
||||
print("Voices:", engine.list_voices())
|
||||
print("Healthcheck:", engine.healthcheck())
|
||||
|
||||
asyncio.run(main())
|
||||
45
app/engines/engine_base.py
Normal file
45
app/engines/engine_base.py
Normal file
@ -0,0 +1,45 @@
|
||||
"""
|
||||
NovaAi – TTS-Engine-Hub
|
||||
engine_base.py
|
||||
Version: v0.0.1
|
||||
|
||||
Description:
|
||||
Abstract base class for all TTS engine modules.
|
||||
Defines the required interface for engine adapters.
|
||||
|
||||
Author: Abby (ChatGPT)
|
||||
Date: 2025-07-23
|
||||
Canvas: engine_base.py
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
import asyncio
|
||||
|
||||
class TTSEngineBase(ABC):
|
||||
@abstractmethod
|
||||
async def synthesize(self, text: str, speaker: str = None, model: str = None, fmt: str = "ogg"):
|
||||
"""
|
||||
Asynchronously generate speech audio from text input.
|
||||
Returns path to audio file.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def list_models(self):
|
||||
"""Return a list of available models."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def list_voices(self, model: str = None):
|
||||
"""Return a list of available voices for a model."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def healthcheck(self):
|
||||
"""Return health/status info for this engine."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def selftest(self):
|
||||
"""Asynchronously run internal self-test (basic functionality check)."""
|
||||
raise NotImplementedError
|
||||
1
app/engines/f5-tts-voices/default.txt
Normal file
1
app/engines/f5-tts-voices/default.txt
Normal file
@ -0,0 +1 @@
|
||||
Some call me nature, others call me mother nature.
|
||||
143
app/engines/f5_tts.py
Normal file
143
app/engines/f5_tts.py
Normal file
@ -0,0 +1,143 @@
|
||||
"""
|
||||
NovaAi – TTS-Engine-Hub
|
||||
f5_tts.py
|
||||
Version: v0.0.2
|
||||
|
||||
Description:
|
||||
F5-TTS engine module.
|
||||
Implements the TTSEngineBase for F5-TTS text-to-speech synthesis.
|
||||
Now with robust speaker handling.
|
||||
|
||||
Author: Your Name (or leave as generated)
|
||||
Date: 2025-12-03
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import torch
|
||||
import torchaudio
|
||||
import numpy as np
|
||||
import soundfile as sf
|
||||
import asyncio
|
||||
from .engine_base import TTSEngineBase
|
||||
from importlib.resources import files
|
||||
|
||||
try:
|
||||
from f5_tts.api import F5TTS
|
||||
except ImportError:
|
||||
print("Warning: F5TTS could not be imported. F5-TTS engine will not be available.")
|
||||
F5TTS = None
|
||||
|
||||
class F5TTSEngine(TTSEngineBase):
|
||||
def __init__(self):
|
||||
# Initialize F5-TTS specific resources, models, etc.
|
||||
print("F5-TTS Engine Initializing...")
|
||||
self.speakers = {}
|
||||
self.model = None
|
||||
|
||||
if F5TTS:
|
||||
try:
|
||||
self.model = F5TTS(model="F5TTS_v1_Base")
|
||||
print("F5-TTS Engine Initialized.")
|
||||
self._load_speakers()
|
||||
except Exception as e:
|
||||
print(f"Error initializing F5-TTS Engine: {e}")
|
||||
self.model = None
|
||||
else:
|
||||
print("F5-TTS Engine not initialized because F5TTS is not available.")
|
||||
|
||||
def _load_speakers(self):
|
||||
# Add the default speaker
|
||||
default_wav = str(files("f5_tts").joinpath("infer/examples/basic/basic_ref_en.wav"))
|
||||
default_txt = "app/models/f5-tts-voices/default.txt"
|
||||
if os.path.exists(default_txt):
|
||||
self.speakers["default"] = {"wav": default_wav, "txt": default_txt}
|
||||
|
||||
# Scan for custom speakers
|
||||
voices_dir = "app/models/f5-tts-voices"
|
||||
if not os.path.isdir(voices_dir):
|
||||
return
|
||||
for file in os.listdir(voices_dir):
|
||||
if file.endswith(".wav"):
|
||||
speaker_name = file.rsplit('.', 1)[0]
|
||||
wav_path = os.path.join(voices_dir, file)
|
||||
txt_path = os.path.join(voices_dir, f"{speaker_name}.txt")
|
||||
if os.path.exists(txt_path):
|
||||
self.speakers[speaker_name] = {"wav": wav_path, "txt": txt_path}
|
||||
print(f"Found custom speaker: {speaker_name}")
|
||||
|
||||
def _blocking_synthesize(self, text: str, speaker: str, fmt: str):
|
||||
"""The actual blocking synthesis logic."""
|
||||
speaker_data = self.speakers[speaker]
|
||||
ref_file = speaker_data["wav"]
|
||||
|
||||
with open(speaker_data["txt"], 'r') as f:
|
||||
ref_text = f.read()
|
||||
|
||||
print(f"F5-TTS: Synthesizing '{text}' with reference voice from '{ref_file}'.")
|
||||
|
||||
wav, sr, spec = self.model.infer(
|
||||
ref_file=ref_file,
|
||||
ref_text=ref_text,
|
||||
gen_text=text,
|
||||
)
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=f".{fmt}") as temp_file:
|
||||
if fmt == "wav":
|
||||
torchaudio.save(temp_file.name, torch.from_numpy(wav).unsqueeze(0), sr, format="wav")
|
||||
else:
|
||||
# Convert to float32 for soundfile
|
||||
wav_float = wav.astype(np.float32) / np.iinfo(wav.dtype).max
|
||||
sf.write(temp_file.name, wav_float, sr)
|
||||
return temp_file.name
|
||||
|
||||
async def synthesize(self, text: str, speaker: str = None, model: str = None, fmt: str = "ogg"):
|
||||
"""
|
||||
Asynchronously generate speech audio from text input using F5-TTS.
|
||||
"""
|
||||
if not self.model:
|
||||
raise RuntimeError("F5-TTS Engine not initialized.")
|
||||
|
||||
speaker_to_use = speaker if speaker in self.speakers else "default"
|
||||
if speaker and speaker not in self.speakers:
|
||||
print(f"Warning: Speaker '{speaker}' not found. Falling back to default speaker.")
|
||||
|
||||
if speaker_to_use not in self.speakers:
|
||||
raise RuntimeError("No default speaker found for F5-TTS. Please add a 'default.wav' and 'default.txt' to the 'engines/f5-tts-voices' directory.")
|
||||
|
||||
try:
|
||||
# Run the blocking synthesis in a separate thread
|
||||
return await asyncio.to_thread(self._blocking_synthesize, text, speaker_to_use, fmt)
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"F5-TTS synthesis failed: {e}")
|
||||
|
||||
def list_models(self):
|
||||
"""Return a list of available F5-TTS models."""
|
||||
if not self.model:
|
||||
return []
|
||||
return ["F5TTS_v1_Base"]
|
||||
|
||||
def list_voices(self, model: str = None):
|
||||
"""Return a list of available F5-TTS voices for a model."""
|
||||
return list(self.speakers.keys())
|
||||
|
||||
def healthcheck(self):
|
||||
"""Return health/status info for F5-TTS engine."""
|
||||
if self.model:
|
||||
return {"status": "ok", "message": "F5-TTS engine is ready"}
|
||||
else:
|
||||
return {"status": "error", "message": "F5-TTS engine failed to initialize"}
|
||||
|
||||
async def selftest(self):
|
||||
"""Run internal self-test for F5-TTS."""
|
||||
if not self.model:
|
||||
return {"status": "failed", "message": "F5-TTS Engine not initialized."}
|
||||
try:
|
||||
# Await the async synthesize method
|
||||
audio_file = await self.synthesize("this is a test.")
|
||||
selftest_passed = os.path.exists(audio_file) and os.path.getsize(audio_file) > 0
|
||||
if selftest_passed:
|
||||
os.remove(audio_file)
|
||||
return {"status": "passed" if selftest_passed else "failed", "message": "F5-TTS self-test successful"}
|
||||
except Exception as e:
|
||||
return {"status": "failed", "message": f"F5-TTS self-test failed: {e}"}
|
||||
163
app/engines/piper.py
Normal file
163
app/engines/piper.py
Normal file
@ -0,0 +1,163 @@
|
||||
"""
|
||||
NovaAi – TTS-Engine-Hub
|
||||
engines/piper.py
|
||||
Version: v0.1.1
|
||||
|
||||
Description:
|
||||
Piper TTS engine adapter: real CLI invocation + output as WAV, OGG, or MP3.
|
||||
Synthesizes WAV via Piper, converts to OGG/MP3 via ffmpeg-python if needed.
|
||||
Uses dynamic model path: ./models/piper/[model]/model.onnx
|
||||
|
||||
Author: Abby (ChatGPT)
|
||||
Date: 2025-07-23
|
||||
Canvas: piper.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import subprocess
|
||||
import tempfile
|
||||
import os
|
||||
import shutil
|
||||
import json
|
||||
from .engine_base import TTSEngineBase
|
||||
import ffmpeg
|
||||
|
||||
class PiperEngine(TTSEngineBase):
|
||||
def __init__(self):
|
||||
self.piper_executable = shutil.which("piper")
|
||||
self.ffmpeg_executable = shutil.which("ffmpeg")
|
||||
|
||||
def _load_config(self, model: str):
|
||||
"""Load the model config JSON file to get speaker mappings."""
|
||||
model_dir = f"./app/models/piper/{model}"
|
||||
config_file = os.path.join(model_dir, f"{model}.onnx.json")
|
||||
if os.path.isfile(config_file):
|
||||
with open(config_file, 'r') as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
def _get_speaker_id(self, speaker: str, model: str):
|
||||
"""Convert speaker name to speaker ID using the model's config."""
|
||||
if not speaker or speaker == "default":
|
||||
return None
|
||||
if speaker.isdigit():
|
||||
return speaker
|
||||
config = self._load_config(model)
|
||||
speaker_id_map = config.get('speaker_id_map', {})
|
||||
return str(speaker_id_map.get(speaker))
|
||||
|
||||
def _run_ffmpeg_blocking(self, input_path, output_path):
|
||||
"""Wrapper for the blocking ffmpeg call."""
|
||||
(
|
||||
ffmpeg
|
||||
.input(input_path)
|
||||
.output(output_path)
|
||||
.run(overwrite_output=True, quiet=True)
|
||||
)
|
||||
|
||||
async def synthesize(self, text: str, speaker: str = None, model: str = None, fmt: str = "ogg"):
|
||||
if not self.piper_executable:
|
||||
raise RuntimeError("Piper executable not found. Please install it and ensure it's in your PATH.")
|
||||
if not model:
|
||||
raise ValueError("Model must be specified for Piper.")
|
||||
model_dir = f"./app/models/piper/{model}"
|
||||
model_file = os.path.join(model_dir, f"{model}.onnx")
|
||||
if not os.path.isfile(model_file):
|
||||
raise FileNotFoundError(f"Piper model not found: {model_file}")
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".wav", prefix="piper_", delete=False) as wav_file:
|
||||
output_wav_path = wav_file.name
|
||||
|
||||
cmd = [self.piper_executable, "--model", model_file, "--output_file", output_wav_path, "--stdin_text"]
|
||||
|
||||
if speaker:
|
||||
speaker_id = self._get_speaker_id(speaker, model)
|
||||
if speaker_id:
|
||||
cmd += ["--speaker", speaker_id]
|
||||
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
stdout, stderr = await process.communicate(input=text.encode('utf-8'))
|
||||
|
||||
if process.returncode != 0:
|
||||
os.remove(output_wav_path)
|
||||
raise RuntimeError(f"Piper synth failed: {stderr.decode()}")
|
||||
|
||||
fmt = (fmt or "ogg").lower()
|
||||
if fmt == "wav":
|
||||
return output_wav_path
|
||||
|
||||
if not self.ffmpeg_executable:
|
||||
os.remove(output_wav_path)
|
||||
raise RuntimeError("ffmpeg not found, cannot convert audio format.")
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=f'.{fmt}', prefix="piper_conv_", delete=False) as converted_file:
|
||||
output_other_path = converted_file.name
|
||||
|
||||
try:
|
||||
await asyncio.to_thread(self._run_ffmpeg_blocking, output_wav_path, output_other_path)
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"ffmpeg conversion failed: {e}")
|
||||
finally:
|
||||
os.remove(output_wav_path)
|
||||
|
||||
return output_other_path
|
||||
|
||||
def list_models(self):
|
||||
models_dir = "./app/models/piper/"
|
||||
if not os.path.isdir(models_dir):
|
||||
return []
|
||||
return [name for name in os.listdir(models_dir)
|
||||
if os.path.isdir(os.path.join(models_dir, name))]
|
||||
|
||||
def list_voices(self, model: str = None):
|
||||
if not model:
|
||||
return ["default"]
|
||||
config = self._load_config(model)
|
||||
speaker_id_map = config.get('speaker_id_map', {})
|
||||
if speaker_id_map:
|
||||
return ["default"] + sorted(speaker_id_map.keys())
|
||||
return ["default"]
|
||||
|
||||
def healthcheck(self):
|
||||
status = "ok"
|
||||
if not self.piper_executable:
|
||||
status = "missing_piper_executable"
|
||||
return {"status": status, "engine": "piper"}
|
||||
|
||||
async def selftest(self):
|
||||
if not self.piper_executable:
|
||||
return {"selftest": False, "error": "Piper executable not found.", "engine": "piper"}
|
||||
try:
|
||||
models = self.list_models()
|
||||
if not models:
|
||||
return {"selftest": False, "error": "No Piper models found.", "engine": "piper"}
|
||||
|
||||
test_text = "This is a selftest."
|
||||
first_model = models[0]
|
||||
voices = self.list_voices(first_model)
|
||||
test_voice = voices[0] if voices else None
|
||||
|
||||
audio_file = await self.synthesize(test_text, speaker=test_voice, model=first_model, fmt="wav")
|
||||
|
||||
selftest_passed = os.path.exists(audio_file) and os.path.getsize(audio_file) > 0
|
||||
if selftest_passed:
|
||||
os.remove(audio_file)
|
||||
|
||||
return {"selftest": selftest_passed, "models": models, "engine": "piper"}
|
||||
except Exception as e:
|
||||
return {"selftest": False, "error": str(e), "engine": "piper"}
|
||||
|
||||
if __name__ == "__main__":
|
||||
async def main():
|
||||
engine = PiperEngine()
|
||||
print("Selftest:", await engine.selftest())
|
||||
print("Models:", engine.list_models())
|
||||
print("Voices:", engine.list_voices(engine.list_models()[0]))
|
||||
print("Healthcheck:", engine.healthcheck())
|
||||
|
||||
asyncio.run(main())
|
||||
48
app/engines/styletts.py
Normal file
48
app/engines/styletts.py
Normal file
@ -0,0 +1,48 @@
|
||||
"""
|
||||
NovaAi – TTS-Engine-Hub
|
||||
engines/styletts.py
|
||||
Version: v0.0.1
|
||||
|
||||
Description:
|
||||
StyleTTS engine adapter.
|
||||
Implements TTSEngineBase interface for StyleTTS integration (dummy implementation).
|
||||
|
||||
Author: Abby (ChatGPT)
|
||||
Date: 2025-07-23
|
||||
Canvas: styletts.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from .engine_base import TTSEngineBase
|
||||
|
||||
class StyleTTSEngine(TTSEngineBase):
|
||||
async def synthesize(self, text: str, speaker: str = None, model: str = None, fmt: str = "mp3"):
|
||||
# Dummy implementation: returns an empty string as it doesn't produce a file.
|
||||
print("Warning: StyleTTSEngine.synthesize is a dummy and does not produce audio.")
|
||||
return ""
|
||||
|
||||
def list_models(self):
|
||||
# Dummy implementation
|
||||
return ["styletts_v2_de", "styletts_v2_en"]
|
||||
|
||||
def list_voices(self, model: str = None):
|
||||
# Dummy implementation
|
||||
return ["neutral", "emotional", "female"]
|
||||
|
||||
def healthcheck(self):
|
||||
# Dummy implementation
|
||||
return {"status": "ok", "engine": "styletts"}
|
||||
|
||||
async def selftest(self):
|
||||
# Dummy implementation
|
||||
return {"selftest": True, "engine": "styletts"}
|
||||
|
||||
if __name__ == "__main__":
|
||||
async def main():
|
||||
engine = StyleTTSEngine()
|
||||
print("Selftest:", await engine.selftest())
|
||||
print("Models:", engine.list_models())
|
||||
print("Voices:", engine.list_voices())
|
||||
print("Healthcheck:", engine.healthcheck())
|
||||
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user