Skill: merend/openai-tts-python (version 1.0.1)

--- name: openai-tts description: | Text-to-speech conversion using OpenAI's TTS API for generating high-quality, natural-sounding audio. Supports 6 voices (alloy, echo, fable, onyx, nova, shimmer), speed control (0.25x-4.0x), HD quality model, multiple output formats (mp3, opus, aac, flac), and automatic text chunking for long content (4096 char limit per request). Use when: (1) User requests audio/voice output with triggers like "read this to me", "convert to audio", "generate speech", "text to speech", "tts", "narrate", "speak", or when keywords "openai tts", "voice", "podcast" appear. (2) Content needs to be spoken rather than read (multitasking, accessibility). (3) User wants specific voice preferences like "alloy", "echo", "fable", "onyx", "nova", "shimmer" or speed adjustments. --- # OpenAI TTS Text-to-speech conversion using OpenAI's TTS API for generating high-quality, natural-sounding audio from text. ## Features - 6 different voice options (male/female) - Standard and HD quality models - Automatic text chunking for long content (4096 char limit) - Multiple output formats (mp3, opus, aac, flac) ## Activation This skill activates when the user: - Requests audio/voice output: "read this to me", "convert to audio", "generate speech", "make this an audio file" - Uses keywords: "tts", "openai tts", "text to speech", "voice", "audio", "podcast" - Needs content spoken for accessibility, multitasking, or podcast creation - Specifies voice preferences: "alloy", "echo", "fable", "onyx", "nova", "shimmer" - Asks to "narrate", "speak", or "vocalize" text ## Requirements - `OPENAI_API_KEY` environment variable must be set - Python 3.8+ - Dependencies: `openai`, `pydub` (optional, for long text) ## Voices | Voice | Type | Description | |-------|------|-------------| | alloy | Neutral | Balanced, versatile | | echo | Male | Warm, conversational | | fable | Neutral | Expressive, storytelling | | onyx | Male | Deep, authoritative | | nova | Female | Friendly, upbeat | | shimmer | Female | Clear, professional | ## Usage ### Basic Usage ```python from openai import OpenAI import os client = OpenAI([REDACTED_SECRET]'OPENAI_API_KEY')) response = client.audio.speech.create( model="tts-1", # or "tts-1-hd" for higher quality voice="onyx", # choose from: alloy, echo, fable, onyx, nova, shimmer input="Your text here", speed=1.0 # 0.25 to 4.0 (optional) ) with open("output.mp3", "wb") as f: for chunk in response.iter_bytes(): f.write(chunk) ``` ### Command Line ```bash # Basic python -c " from openai import OpenAI client = OpenAI() response = client.audio.speech.create(model='tts-1', voice='onyx', input='Hello world') open('output.mp3', 'wb').write(response.content) " ``` ### Long Text (Auto-chunking) ```python from openai import OpenAI from pydub import AudioSegment import tempfile import os import re client = OpenAI() MAX_CHARS = 4096 def split_text(text): if len(text) <= MAX_CHARS: return [text] chunks = [] sentences = re.split(r'(?<=[.!?])\s+', text) current = '' for sentence in sentences: if len(current) + len(sentence) + 1 <= MAX_CHARS: current += (' ' if current else '') + sentence else: if current: chunks.append(current) current = sentence if current: chunks.append(current) return chunks def generate_tts(text, output_path, voice='onyx', model='tts-1'): chunks = split_text(text) if len(chunks) == 1: response = client.audio.speech.create(model=model, voice=voice, input=text) with open(output_path, 'wb') as f: f.write(response.content) else: segments = [] for chunk in chunks: response = client.audio.speech.create(model=model, voice=voice, input=chunk) with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as tmp: tmp.write(response.content) segments.append(AudioSegment.from_mp3(tmp.name)) os.unlink(tmp.name) combined = segments[0] for seg in segments[1:]: combined += seg combined.export(output_path, format='mp3') return output_path # Usage generate_tts("Your long text here...", "output.mp3", voice="nova") ``` ## Models | Model | Quality | Speed | Cost | |-------|---------|-------|------| | tts-1 | Standard | Fast | $0.015/1K chars | | tts-1-hd | High Definition | Slower | $0.030/1K chars | ## Output Formats Supported formats: `mp3` (default), `opus`, `aac`, `flac` ```python response = client.audio.speech.create( model="tts-1", voice="onyx", input="Hello", response_format="opus" # or mp3, aac, flac ) ``` ## Error Handling ```python from openai import OpenAI, APIError, RateLimitError import time client = OpenAI() def generate_with_retry(text, voice='onyx', max_retries=3): for attempt in range(max_retries): try: response = client.audio.speech.create( model="tts-1", voice=voice, input=text ) return response.content except RateLimitError: if attempt < max_retries - 1: time.sleep(2 ** attempt) # Exponential backoff continue raise except APIError as e: print(f"API Error: {e}") raise return None ``` ## Examples ### Convert Article to Podcast ```python def article_to_podcast(article_text, output_file): intro = "Welcome to today's article reading." outro = "Thank you for listening." full_text = f"{intro}\n\n{article_text}\n\n{outro}" generate_tts(full_text, output_file, voice='nova', model='tts-1-hd') print(f"Podcast saved to {output_file}") ``` ### Batch Processing ```python def batch_tts(texts, output_dir, voice='onyx'): import os os.makedirs(output_dir, exist_ok=True) for i, text in enumerate(texts): output_path = os.path.join(output_dir, f"audio_{i+1}.mp3") generate_tts(text, output_path, voice=voice) print(f"Generated: {output_path}") ``` ## Links - [OpenAI TTS Documentation](https://platform.openai.com/docs/guides/text-to-speech) - [OpenAI API Reference](https://platform.openai.com/docs/api-reference/audio/createSpeech) - [Pricing](https://openai.com/pricing)

path: README.md
content: # OpenAI TTS Skill

Text-to-speech conversion using OpenAI's TTS API for generating high-quality, natural-sounding audio from text.

## Installation

```bash
pip install openai pydub

# For audio processing (pydub dependency)
# macOS
brew install ffmpeg

# Ubuntu/Debian
sudo apt install ffmpeg
```

## Setup

Set your OpenAI API key:

```bash
export OPENAI_API_KEY="sk-..."
```

## Usage

### Command Line

```bash
# Basic usage
python openai_tts.py "Hello world" -o output.mp3

# From file
python openai_tts.py -f article.txt -o article.mp3

# With voice selection
python openai_tts.py "Your text" -o output.mp3 --voice nova

# High quality
python openai_tts.py "Your text" -o output.mp3 --model tts-1-hd

# Adjust speed (0.25 to 4.0)
python openai_tts.py "Your text" -o output.mp3 --speed 1.5

# Pipe input
echo "Hello world" | python openai_tts.py -o output.mp3

# Verbose mode
python openai_tts.py "Test" -o test.mp3 -v

# List available voices
python openai_tts.py --list-voices
```

### As Module

```python
from openai_tts import generate_tts

# Basic
generate_tts("Hello world", "output.mp3")

# With options
generate_tts(
    text="Your text here",
    output_path="output.mp3",
    voice="nova",
    model="tts-1-hd",
    response_format="mp3",
    speed=1.25,  # 0.25 to 4.0
    verbose=True
)
```

## Voices

| Voice | Type | Description |
|-------|------|-------------|
| alloy | Neutral | Balanced, versatile |
| echo | Male | Warm, conversational |
| fable | Neutral | Expressive, storytelling |
| **onyx** | Male | Deep, authoritative (default) |
| nova | Female | Friendly, upbeat |
| shimmer | Female | Clear, professional |

## Models

| Model | Quality | Speed | Cost |
|-------|---------|-------|------|
| **tts-1** | Standard | Fast | $0.015/1K chars |
| tts-1-hd | High Definition | Slower | $0.030/1K chars |

## Features

- **Auto-chunking**: Automatically splits text longer than 4096 characters
- **Multiple formats**: mp3, opus, aac, flac
- **6 voices**: Male and female options
- **Pipe support**: Read from stdin

## Output Formats

| Format | Description |
|--------|-------------|
| **mp3** | Default, widely compatible |
| opus | Smaller file size, good quality |
| aac | Apple/iOS compatible |
| flac | Lossless, larger files |

## License

MIT


path: openai_tts.py
content: #!/usr/bin/env python3
"""
OpenAI TTS - Text-to-speech using OpenAI's TTS API

Usage:
    python openai_tts.py "Your text here" -o output.mp3
    python openai_tts.py -f input.txt -o output.mp3 --voice nova
    echo "Hello" | python openai_tts.py -o output.mp3

Voices: alloy, echo, fable, onyx (default), nova, shimmer
Models: tts-1 (default), tts-1-hd
"""

import argparse
import os
import sys
import re
import tempfile
from typing import List

# Constants
MAX_CHARS = 4096
VOICES = ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer']
MODELS = ['tts-1', 'tts-1-hd']
FORMATS = ['mp3', 'opus', 'aac', 'flac']
SPEED_MIN = 0.25
SPEED_MAX = 4.0
SPEED_DEFAULT = 1.0

VOICE_INFO = {
    'alloy': 'Neutral, balanced',
    'echo': 'Male, warm, conversational',
    'fable': 'Neutral, expressive, storytelling',
    'onyx': 'Male, deep, authoritative',
    'nova': 'Female, friendly, upbeat',
    'shimmer': 'Female, clear, professional'
}


def split_text(text: str, max_chars: int = MAX_CHARS) -> List[str]:
    """Split text into chunks respecting sentence boundaries."""
    if len(text) <= max_chars:
        return [text]

    chunks = []
    sentences = re.split(r'(?<=[.!?])\s+', text)
    current = ''

    for sentence in sentences:
        if len(current) + len(sentence) + 1 <= max_chars:
            current += (' ' if current else '') + sentence
        else:
            if current:
                chunks.append(current)
            if len(sentence) > max_chars:
                words = sentence.split()
                current = ''
                for word in words:
                    if len(current) + len(word) + 1 <= max_chars:
                        current += (' ' if current else '') + word
                    else:
                        if current:
                            chunks.append(current)
                        current = word
            else:
                current = sentence

    if current:
        chunks.append(current)

    return chunks


def generate_tts(
    text: str,
    output_path: str,
    voice: str = 'onyx',
    model: str = 'tts-1',
    response_format: str = 'mp3',
    speed: float = SPEED_DEFAULT,
    verbose: bool = False
) -> bool:
    """Generate TTS audio from text.

    Args:
        text: Text to convert to speech
        output_path: Path to save the audio file
        voice: Voice to use (alloy, echo, fable, onyx, nova, shimmer)
        model: Model to use (tts-1, tts-1-hd)
        response_format: Output format (mp3, opus, aac, flac)
        speed: Playback speed (0.25 to 4.0, default 1.0)
        verbose: Print progress information

    Returns:
        True if successful, False otherwise
    """
    try:
        from openai import OpenAI
    except ImportError:
        print("Error: openai package not installed. Run: pip install openai")
        return False

    [REDACTED_SECRET]'OPENAI_API_KEY')
    if not [REDACTED_SECRET]"Error: OPENAI_API_KEY environment variable not set")
        return False

    client = OpenAI([REDACTED_SECRET])
    chunks = split_text(text)

    if verbose:
        print(f"Text length: {len(text)} chars")
        print(f"Chunks: {len(chunks)}")
        print(f"Voice: {voice}, Model: {model}, Format: {response_format}, Speed: {speed}x")

    try:
        if len(chunks) == 1:
            if verbose:
                print("Generating audio...")
            response = client.audio.speech.create(
                model=model,
                voice=voice,
                input=chunks[0],
                response_format=response_format,
                speed=speed
            )
            with open(output_path, 'wb') as f:
                for chunk in response.iter_bytes():
                    f.write(chunk)
        else:
            try:
                from pydub import AudioSegment
            except ImportError:
                print("Error: pydub required for long text. Run: pip install pydub")
                print("Also ensure ffmpeg is installed.")
                return False

            segments = []
            for i, chunk in enumerate(chunks):
                if verbose:
                    print(f"Generating chunk {i+1}/{len(chunks)}...")
                response = client.audio.speech.create(
                    model=model,
                    voice=voice,
                    input=chunk,
                    response_format='mp3',  # Always use mp3 for combining
                    speed=speed
                )
                with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as tmp:
                    for data in response.iter_bytes():
                        tmp.write(data)
                    segments.append(AudioSegment.from_mp3(tmp.name))
                    os.unlink(tmp.name)

            if verbose:
                print("Combining chunks...")
            combined = segments[0]
            for seg in segments[1:]:
                combined += seg
            combined.export(output_path, format=response_format)

        if verbose:
            size = os.path.getsize(output_path)
            print(f"Saved: {output_path} ({size:,} bytes)")

        return True

    except Exception as e:
        print(f"Error: {e}")
        return False


def main():
    parser = argparse.ArgumentParser(
        description='Generate speech from text using OpenAI TTS API',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog='''
Examples:
  %(prog)s "Hello world" -o hello.mp3
  %(prog)s -f article.txt -o article.mp3 --voice nova
  %(prog)s "Long text..." -o output.mp3 --model tts-1-hd
  echo "Test" | %(prog)s -o test.mp3

Voices:
  alloy   - Neutral, balanced
  echo    - Male, warm, conversational
  fable   - Neutral, expressive, storytelling
  onyx    - Male, deep, authoritative (default)
  nova    - Female, friendly, upbeat
  shimmer - Female, clear, professional
        '''
    )

    parser.add_argument('text', nargs='?', help='Text to convert')
    parser.add_argument('-f', '--file', help='Read text from file')
    parser.add_argument('-o', '--output', default='output.mp3', help='Output file')
    parser.add_argument('--voice', default='onyx', choices=VOICES, help='Voice')
    parser.add_argument('--model', default='tts-1', choices=MODELS, help='Model')
    parser.add_argument('--format', default='mp3', choices=FORMATS, help='Output format')
    parser.add_argument('--speed', type=float, default=SPEED_DEFAULT,
                        help=f'Playback speed ({SPEED_MIN}-{SPEED_MAX}, default: {SPEED_DEFAULT})')
    parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
    parser.add_argument('--list-voices', action='store_true', help='List voices')

    args = parser.parse_args()

    if args.list_voices:
        print("Available voices:")
        for voice, desc in VOICE_INFO.items():
            print(f"  {voice:8} - {desc}")
        return 0

    # Validate speed
    if not (SPEED_MIN <= args.speed <= SPEED_MAX):
        print(f"Error: Speed must be between {SPEED_MIN} and {SPEED_MAX}")
        return 1

    # Get text from argument, file, or stdin
    text = None
    if args.file:
        try:
            with open(args.file, 'r', encoding='utf-8') as f:
                text = f.read()
        except Exception as e:
            print(f"Error reading file: {e}")
            return 1
    elif args.text:
        text = args.text
    elif not sys.stdin.isatty():
        text = sys.stdin.read()
    else:
        print("Error: No text provided")
        parser.print_help()
        return 1

    if not text or not text.strip():
        print("Error: Empty text")
        return 1

    success = generate_tts(
        text=text.strip(),
        output_path=args.output,
        voice=args.voice,
        model=args.model,
        response_format=args.format,
        speed=args.speed,
        verbose=args.verbose
    )

    return 0 if success else 1


if __name__ == '__main__':
    sys.exit(main())


path: manifest.json
content: {
  "name": "openai-tts",
  "display_name": "OpenAI TTS",
  "version": "1.0.0",
  "description": "Text-to-speech conversion using OpenAI's TTS API for generating high-quality, natural-sounding audio from text.",
  "summary": "Convert text to natural-sounding speech using OpenAI's TTS API. Features 6 voice options (alloy, echo, fable, onyx, nova, shimmer), adjustable speed (0.25x-4.0x), HD quality model, and multiple output formats (mp3, opus, aac, flac). Activates on phrases like 'read this to me', 'text to speech', 'tts', or voice preferences.",
  "author": "clawdbot",
  "license": "MIT",
  "keywords": [
    "tts",
    "text-to-speech",
    "openai",
    "audio",
    "voice",
    "speech",
    "podcast",
    "narration"
  ],
  "category": "audio",
  "requirements": {
    "env_vars": ["OPENAI_API_KEY"],
    "python_packages": ["openai", "pydub"],
    "system_packages": ["ffmpeg"]
  },
  "activation": {
    "triggers": [
      "read this to me",
      "convert to audio",
      "generate speech",
      "make this an audio file",
      "text to speech",
      "tts",
      "openai tts",
      "voice",
      "podcast",
      "narrate",
      "speak",
      "vocalize"
    ],
    "keywords": ["tts", "audio", "voice", "speech", "podcast", "mp3", "speed"],
    "file_extensions": [".mp3", ".opus", ".aac", ".flac"]
  },
  "parameters": {
    "voice": {
      "type": "string",
      "default": "onyx",
      "options": ["alloy", "echo", "fable", "onyx", "nova", "shimmer"],
      "description": "Voice to use for speech generation"
    },
    "model": {
      "type": "string",
      "default": "tts-1",
      "options": ["tts-1", "tts-1-hd"],
      "description": "Model quality (tts-1 = standard, tts-1-hd = high quality)"
    },
    "speed": {
      "type": "number",
      "default": 1.0,
      "min": 0.25,
      "max": 4.0,
      "description": "Playback speed (0.25 = slowest, 4.0 = fastest)"
    },
    "format": {
      "type": "string",
      "default": "mp3",
      "options": ["mp3", "opus", "aac", "flac"],
      "description": "Output audio format"
    }
  },
  "voices": {
    "alloy": {"type": "neutral", "description": "Balanced, versatile"},
    "echo": {"type": "male", "description": "Warm, conversational"},
    "fable": {"type": "neutral", "description": "Expressive, storytelling"},
    "onyx": {"type": "male", "description": "Deep, authoritative"},
    "nova": {"type": "female", "description": "Friendly, upbeat"},
    "shimmer": {"type": "female", "description": "Clear, professional"}
  },
  "limits": {
    "max_chars_per_request": 4096,
    "supported_languages": "All languages supported by OpenAI TTS"
  },
  "pricing": {
    "tts-1": "$0.015 per 1,000 characters",
    "tts-1-hd": "$0.030 per 1,000 characters"
  },
  "links": {
    "documentation": "https://platform.openai.com/docs/guides/text-to-speech",
    "api_reference": "https://platform.openai.com/docs/api-reference/audio/createSpeech",
    "pricing": "https://openai.com/pricing"
  }
}


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)