Skip to content
Back to skills

140 Claude 684eb6d1

ASecurity

Text-to-speech (TTS) provider implementations for generating audio from text.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
businesspythongogcpazureapidocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 140-claude_684eb6d1 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 140 Claude 684eb6d1?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 140 Claude 684eb6d1
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-140-claude-684eb6d1/badge)](https://www.skillsdirectory.com/skills/tools-only-140-claude-684eb6d1)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Text-to-Speech Providers

Text-to-speech (TTS) provider implementations for generating audio from text.

## Files

- **`base.py`**: Abstract base class `TextToSpeechModel` defining the interface
- **`openai.py`**: OpenAI TTS API (voices: alloy, echo, fable, nova, onyx, shimmer)
- **`elevenlabs.py`**: ElevenLabs TTS with multilingual support
- **`google.py`**: Google Cloud Text-to-Speech (massive voice catalog)
- **`vertex.py`**: Google Vertex AI TTS
- **`azure.py`**: Azure Speech Service TTS
- **`openai_compatible.py`**: Generic OpenAI-compatible TTS API

## Patterns

### Base Class Contract

All providers inherit from `TextToSpeechModel` (base.py:16) and must:

1. **Implement abstract methods**:
   - `generate_speech()`: Synchronous speech generation
   - `agenerate_speech()`: Async speech generation
   - `_get_models()`: Return list of available models
   - `available_voices` property: Return dict of available voices
   - `provider` property: Return provider name string

2. **Override `__post_init__()`**:
   - Call `super().__post_init__()` first
   - Set `api_key` from parameter or environment variable
   - Set `base_url` (if applicable)
   - Call `self._create_http_clients()` last

3. **Return standardized response**:
   - Use `AudioResponse` from `esperanto.common_types.tts`
   - Contains `audio_data` (bytes), `format`, `duration`, `voice_used`

### Voice Management

Each provider must implement `available_voices` property (base.py:107):

- Returns `Dict[str, Voice]` mapping voice ID → Voice object
- `Voice` contains: `id`, `name`, `language`, `gender`, optional `description`
- Used for voice discovery and validation

Example:

```python
@property
def available_voices(self) -> Dict[str, Voice]:
    return {
        "alloy": Voice(id="alloy", name="Alloy", language="en", gender="neutral"),
        "echo": Voice(id="echo", name="Echo", language="en", gender="male"),
        # ...
    }
```

### SSML Support

Base class defines `COMMON_SSML_TAGS` (base.py:35):

- Standard SSML tags: speak, break, emphasis, prosody, say-as, voice, audio, p, s, phoneme, sub
- Providers can override `get_supported_tags()` to return their specific tags
- Not all providers support SSML - check docs

Some providers support SSML natively, others need custom handling:

- **Google**: Full SSML support
- **ElevenLabs**: Limited support (some tags)
- **OpenAI**: No SSML support (plain text only)

### Parameter Validation

Base class provides `validate_parameters()` (base.py:176):

- Checks text is non-empty string
- Checks voice is non-empty string
- Checks model is string (if provided)
- Call before making API request

```python
def generate_speech(self, text: str, voice: str, output_file: Optional[Union[str, Path]] = None, **kwargs):
    self.validate_parameters(text, voice)
    # Proceed with generation...
```

### File Saving

Base class provides `save_audio()` helper (base.py:198):

- Saves audio bytes to file
- Creates parent directories if needed
- Returns absolute path to saved file
- Handles IOError gracefully

Use in implementations:

```python
audio_data = api_response.content

if output_file:
    file_path = self.save_audio(audio_data, output_file)
else:
    file_path = None

return AudioResponse(audio_data=audio_data, format="mp3", output_file=file_path, ...)
```

### HTTP Client Pattern

Same as other providers:

```python
def __post_init__(self):
    super().__post_init__()
    self.api_key = self.api_key or os.getenv("PROVIDER_API_KEY")
    self.base_url = self.base_url or "https://api.provider.com/v1"
    self._create_http_clients()
```

### Response Construction

Build `AudioResponse` from API results:

```python
from esperanto.common_types.tts import AudioResponse

return AudioResponse(
    audio_data=audio_bytes,
    format="mp3",  # or "wav", "ogg", etc.
    voice_used=voice,
    model_used=self.get_model_name(),
    output_file=file_path if output_file else None,
    duration=duration,  # if available
)
```

## Integration

- Imported by `factory.py` via `AIFactory._provider_modules["text_to_speech"]`
- Uses `AudioResponse`, `Voice` from `esperanto.common_types.tts`
- Inherits mixins from `esperanto.utils.timeout` and `esperanto.utils.ssl`

## Gotchas

- **Voice validation**: Check voice exists in `available_voices` before using
- **Audio format**: Different providers support different formats (MP3, WAV, OGG, etc.)
- **File extension**: Ensure output_file has correct extension for audio format
- **Text length limits**: APIs have character limits (OpenAI: 4096 chars, ElevenLabs: varies by plan)
- **Streaming support**: Some providers support streaming audio (OpenAI, ElevenLabs) - handle separately
- **Voice IDs vs names**: Some providers use IDs (google voice IDs), others use names
- **Language codes**: Different providers use different formats (Google uses BCP-47)
- **Speed/pitch/volume**: Providers expose these differently - map to common parameters or kwargs
- **SSML validation**: If provider doesn't support SSML, strip tags or raise error
- **Audio quality**: Providers offer different quality settings (bitrate, sample rate)
- **Billing**: TTS is billed per character - be mindful of costs
- **Deprecation warnings**: Base class doesn't have model deprecation (no `.models` on TTS yet)

## When Adding a New Provider

1. Create new file `provider_name.py`
2. Import `TextToSpeechModel` from `esperanto.providers.tts.base`
3. Import `AudioResponse`, `Voice` from `esperanto.common_types.tts`
4. Define class inheriting from `TextToSpeechModel`
5. Implement all abstract methods and properties
6. Define `available_voices` property with provider's voice catalog
7. Add `__post_init__()` following the pattern
8. Use `validate_parameters()` for input validation
9. Use `save_audio()` for file saving
10. Add provider to `factory.py` in `_provider_modules["text_to_speech"]` dict
11. Write tests in `tests/providers/tts/test_provider_name.py`
12. Add documentation in `docs/providers/provider_name.md`

## Special Cases

### Google TTS

- Massive voice catalog (hundreds of voices across languages)
- Full SSML support with custom Google tags
- Supports audio effects (e.g., telephony, headphones)
- Uses `google-cloud-texttospeech` library (not just HTTP)
- Requires GCP credentials (JSON key or ADC)
- Different language code format (BCP-47: "en-US" not "en")

### ElevenLabs

- High-quality voices with emotional range
- Supports voice cloning (custom voices)
- SSML support for some tags
- Different pricing tiers with different features
- Streaming support for long-form content
- Multilingual voices (single voice, multiple languages)

### OpenAI

- Six preset voices: alloy, echo, fable, nova, onyx, shimmer
- Two models: tts-1 (fast), tts-1-hd (high quality)
- No SSML support (plain text only)
- Supports speed adjustment (0.25-4.0x)
- Returns MP3 or other formats based on request

### Azure

- Uses Speech SDK or REST API
- Different authentication (subscription key + region)
- Supports neural voices and custom voices
- Full SSML support with Azure-specific tags
- Real-time streaming support

### Vertex AI

- Google's Vertex AI TTS (different from Cloud TTS)
- Similar to Google TTS but integrated with Vertex platform
- Requires Vertex AI setup and permissions

## Common Implementation Patterns

### Generating Speech

```python
def generate_speech(
    self,
    text: str,
    voice: str,
    output_file: Optional[Union[str, Path]] = None,
    **kwargs
) -> AudioResponse:
    self.validate_parameters(text, voice)

    # Build request payload
    payload = {
        "model": self.get_model_name(),
        "voice": voice,
        "input": text,
    }
    payload.update(kwargs)  # Add provider-specific params

    # Make API request
    response = self.client.post(
        f"{self.base_url}/audio/speech",
        json=payload,
        headers=self._get_headers()
    )
    response.raise_for_status()

    audio_data = response.content

    # Save to file if requested
    file_path = None
    if output_file:
        file_path = self.save_audio(audio_data, output_file)

    return AudioResponse(
        audio_data=audio_data,
        format="mp3",
        voice_used=voice,
        model_used=self.get_model_name(),
        output_file=file_path
    )
```

### Handling SSML

```python
def generate_speech(self, text: str, voice: str, output_file: Optional[Union[str, Path]] = None, **kwargs):
    # Check if text contains SSML
    is_ssml = text.strip().startswith("<speak>")

    if is_ssml:
        if not self._supports_ssml():
            # Option 1: Strip SSML tags
            import re
            text = re.sub(r'<[^>]+>', '', text)
            # Option 2: Raise error
            # raise ValueError("This provider does not support SSML")

    # Proceed with generation...
```

### Voice Discovery

```python
@property
def available_voices(self) -> Dict[str, Voice]:
    # For API providers with dynamic voice lists, cache this
    if not hasattr(self, "_voices_cache"):
        response = self.client.get(f"{self.base_url}/voices")
        response.raise_for_status()
        voices_data = response.json()

        self._voices_cache = {
            v["voice_id"]: Voice(
                id=v["voice_id"],
                name=v["name"],
                language=v.get("language", "en"),
                gender=v.get("gender", "neutral")
            )
            for v in voices_data
        }

    return self._voices_cache
```

### Async Implementation

```python
async def agenerate_speech(
    self,
    text: str,
    voice: str,
    output_file: Optional[Union[str, Path]] = None,
    **kwargs
) -> AudioResponse:
    self.validate_parameters(text, voice)

    payload = {
        "model": self.get_model_name(),
        "voice": voice,
        "input": text,
    }
    payload.update(kwargs)

    response = await self.async_client.post(
        f"{self.base_url}/audio/speech",
        json=payload,
        headers=self._get_headers()
    )
    response.raise_for_status()

    audio_data = response.content

    file_path = None
    if output_file:
        file_path = self.save_audio(audio_data, output_file)

    return AudioResponse(
        audio_data=audio_data,
        format="mp3",
        voice_used=voice,
        model_used=self.get_model_name(),
        output_file=file_path
    )
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.