Skip to content

Commit 7ce4021

Browse files
committed
feat: maryTTS api compat
1 parent 11087ce commit 7ce4021

File tree

2 files changed

+75
-4
lines changed

2 files changed

+75
-4
lines changed

ovos_tts_server/__init__.py

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
1-
from typing import Optional, Tuple
2-
from fastapi import FastAPI, Request, Depends
1+
from typing import Optional, Tuple, Literal
2+
from fastapi import FastAPI, Request, Depends, Response
33
from fastapi.responses import FileResponse
4+
from pydantic import BaseModel, Field
45
from ovos_plugin_manager.tts import load_tts_plugin
56
from ovos_config import Configuration
67

8+
class MaryTTSInput(BaseModel):
9+
"""
10+
Pydantic model for validating MaryTTS /process API requests.
11+
Supports both standard MaryTTS params and basic defaults.
12+
"""
13+
INPUT_TEXT: str = Field(..., description="The text to synthesize")
14+
INPUT_TYPE: Literal["TEXT", "SSML"] = "TEXT"
15+
LOCALE: Optional[str] = Field(None, description="Target Locale (e.g. en_US)")
16+
VOICE: Optional[str] = Field(None, description="Target Voice name")
17+
OUTPUT_TYPE: str = "AUDIO"
18+
AUDIO: str = "WAVE_FILE"
19+
720

821
class TTSEngineWrapper:
922
"""Wrapper around an OVOS TTS engine for dependency injection."""
@@ -34,6 +47,16 @@ def langs(self):
3447
"""
3548
return self.engine.available_languages or [self.lang]
3649

50+
@property
51+
def voices(self):
52+
"""
53+
Attempt to retrieve available voices from the plugin.
54+
Returns a list of dictionaries or strings depending on the plugin.
55+
"""
56+
if hasattr(self.engine, "available_voices"):
57+
return self.engine.available_voices
58+
return []
59+
3760
def synthesize(self, utterance: str, **kwargs) -> Tuple[str, Optional[str]]:
3861
"""
3962
Synthesize spoken audio from the given text or SSML.
@@ -86,7 +109,54 @@ def status() -> dict:
86109
"default_voice": config.get("voice")
87110
}
88111

89-
# legacy OVOS endpoints
112+
# --- MaryTTS Compatibility Endpoints ---
113+
114+
@app.get("/locales")
115+
def mary_locales():
116+
"""
117+
MaryTTS Compatibility: Returns a newline-separated list of supported locales.
118+
Format: [locale]\n...
119+
"""
120+
langs = tts_engine.langs
121+
# Ensure we return plain text, not JSON
122+
return Response(content="\n".join(langs), media_type="text/plain")
123+
124+
@app.get("/voices")
125+
def mary_voices():
126+
"""
127+
MaryTTS Compatibility: Returns a list of supported voices.
128+
Format: [name] [locale] [gender]\n...
129+
Note: Name must be space-free.
130+
"""
131+
lines = []
132+
133+
# plugins don't report specific voices - TODO - add available_voices/models property to TTS plugins
134+
lines.append(f"default {tts_engine.lang} m {tts_engine.plugin_name}")
135+
136+
return Response(content="\n".join(lines), media_type="text/plain")
137+
138+
@app.api_route("/process", methods=["GET", "POST"])
139+
def mary_process(params: MaryTTSInput = Depends()):
140+
"""
141+
MaryTTS Compatibility: Processes input text and returns a wav file.
142+
Accepts both GET and POST parameters validated by Pydantic.
143+
"""
144+
# Map MaryTTS specific params to OVOS synthesize params
145+
synth_kwargs = {}
146+
147+
if params.LOCALE:
148+
synth_kwargs["lang"] = params.LOCALE
149+
150+
if params.VOICE:
151+
# Revert the space sanitization if the plugin needs real spaces
152+
# (Though most OVOS plugins map by ID, strict names might differ)
153+
synth_kwargs["voice"] = params.VOICE.replace("_", " ")
154+
155+
audio_path, _ = tts_engine.synthesize(params.INPUT_TEXT, **synth_kwargs)
156+
return FileResponse(audio_path, media_type="audio/wav")
157+
158+
# --- Legacy OVOS Endpoints ---
159+
90160
@app.get("/synthesize/{utterance}")
91161
async def synth_legacy(utterance: str, request: Request) -> FileResponse:
92162
"""
@@ -114,7 +184,7 @@ async def synth_v2(request: Request) -> FileResponse:
114184
"""
115185
utterance = request.query_params.get("utterance")
116186
if not utterance:
117-
return {"error": "Missing 'utterance' query parameter"}
187+
return Response(content='{"error": "Missing utterance"}', status_code=400, media_type="application/json")
118188

119189
# Pass all plugin-specific options
120190
plugin_params = dict(request.query_params)

requirements/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ ovos-plugin-manager>=2.1.0,<3.0.0
22
fastapi~=0.115
33
uvicorn~=0.34
44
ovos-utils>=0.0.38
5+
pydantic

0 commit comments

Comments
 (0)