par-cli-tts 0.2.2__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: par-cli-tts
3
- Version: 0.2.2
3
+ Version: 0.4.0
4
4
  Summary: PAR CLI TTS - Command line text-to-speech tool using ElevenLabs with voice caching and name resolution
5
5
  Project-URL: Homepage, https://github.com/paulrobello/par-cli-tts
6
6
  Project-URL: Documentation, https://github.com/paulrobello/par-cli-tts/blob/main/README.md
@@ -51,7 +51,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
51
51
  Classifier: Typing :: Typed
52
52
  Requires-Python: >=3.11
53
53
  Requires-Dist: elevenlabs>=1.0.0
54
- Requires-Dist: kokoro-onnx>=0.4.9
54
+ Requires-Dist: kokoro-onnx>=0.5.0
55
55
  Requires-Dist: openai>=1.100.1
56
56
  Requires-Dist: platformdirs>=4.3.0
57
57
  Requires-Dist: python-dotenv>=1.0.0
@@ -68,7 +68,7 @@ Description-Content-Type: text/markdown
68
68
  ![Arch x86-63 | ARM | AppleSilicon](https://img.shields.io/badge/arch-x86--64%20%7C%20ARM%20%7C%20AppleSilicon-blue)
69
69
 
70
70
  ![MIT License](https://img.shields.io/badge/license-MIT-green.svg)
71
- ![Version](https://img.shields.io/badge/version-0.2.2-green.svg)
71
+ ![Version](https://img.shields.io/badge/version-0.4.0-green.svg)
72
72
  ![Development Status](https://img.shields.io/badge/status-stable-green.svg)
73
73
 
74
74
  A powerful command-line text-to-speech tool supporting multiple TTS providers (ElevenLabs, OpenAI, and Kokoro ONNX) with intelligent voice caching, name resolution, and flexible output options.
@@ -77,6 +77,18 @@ A powerful command-line text-to-speech tool supporting multiple TTS providers (E
77
77
 
78
78
  ## What's New
79
79
 
80
+ ### v0.4.0
81
+ - **OpenAI gpt-4o-mini-tts** - New steerable TTS model with `--instructions` option
82
+ - **7 new OpenAI voices** - ash, ballad, coral, sage, verse, marin, cedar (13 total)
83
+ - **ElevenLabs model updated** - Changed from deprecated `eleven_monolingual_v1` to `eleven_multilingual_v2`
84
+ - **Kokoro ONNX 0.5.0** - Updated to latest version
85
+
86
+ ### v0.3.0
87
+ - Major code refactoring with better modularity and code organization
88
+ - New utility modules for shared console, defaults, and HTTP client
89
+ - Test suite with 46 tests for better reliability
90
+ - Documentation synced to match current implementation
91
+
80
92
  ### v0.2.2
81
93
  - Updated all HTTP requests and downloaders to ignore SSL certificate errors
82
94
  - Improves compatibility with corporate proxies and development environments
@@ -404,6 +416,10 @@ par-tts "Stable voice" --stability 0.8 --similarity 0.7
404
416
  # Adjust OpenAI speech speed
405
417
  par-tts "Fast speech" --provider openai --speed 1.5
406
418
 
419
+ # Use OpenAI with voice instructions (gpt-4o-mini-tts only)
420
+ par-tts "Hello there!" --provider openai --instructions "Speak in a cheerful and positive tone"
421
+ par-tts "Good morning" -P openai -i "Speak like a pirate"
422
+
407
423
  # Keep temp files after playback
408
424
  par-tts "Keep this" --keep-temp
409
425
 
@@ -510,6 +526,7 @@ make clear-cache # Clear voice cache including samples
510
526
  |--------|-------|-------------|---------|
511
527
  | `--speed` | `-r` | Speech speed (0.25 to 4.0) | 1.0 |
512
528
  | `--format` | `-f` | Audio format (mp3, opus, aac, flac, wav) | mp3 |
529
+ | `--instructions` | `-i` | Voice instructions for gpt-4o-mini-tts (e.g., "Speak cheerfully") | None |
513
530
 
514
531
  ### Kokoro ONNX Options
515
532
 
@@ -543,7 +560,12 @@ make clear-cache # Clear voice cache including samples
543
560
 
544
561
  ### ElevenLabs
545
562
 
546
- - **Models**: eleven_monolingual_v1, eleven_multilingual_v1, eleven_multilingual_v2
563
+ - **Models**:
564
+ - `eleven_multilingual_v2` (default) - Most lifelike, 29 languages
565
+ - `eleven_v3` - Most expressive, 70+ languages
566
+ - `eleven_flash_v2.5` - Ultra-low latency (~75ms), 32 languages
567
+ - `eleven_turbo_v2.5` - Balanced quality/speed, 32 languages
568
+ - ~~`eleven_monolingual_v1`~~ - Deprecated, will be removed
547
569
  - **Voices**: 25+ voices with different accents and styles
548
570
  - **Features**: Voice cloning, stability control, similarity boost
549
571
  - **Smart Caching**:
@@ -555,15 +577,28 @@ make clear-cache # Clear voice cache including samples
555
577
 
556
578
  ### OpenAI
557
579
 
558
- - **Models**: tts-1 (optimized for speed), tts-1-hd (optimized for quality)
559
- - **Voices**:
580
+ - **Models**:
581
+ - `gpt-4o-mini-tts` (default) - Steerable TTS with instructions
582
+ - `tts-1` - Optimized for speed
583
+ - `tts-1-hd` - Optimized for quality
584
+ - **Voices** (13 total):
560
585
  - alloy - Neutral and balanced
586
+ - ash - Enthusiastic and energetic
587
+ - ballad - Warm and soulful
588
+ - coral - Friendly and approachable
561
589
  - echo - Smooth and articulate
562
590
  - fable - Expressive and animated
563
- - onyx - Deep and authoritative
564
591
  - nova - Warm and friendly (default)
592
+ - onyx - Deep and authoritative
593
+ - sage - Calm and wise
565
594
  - shimmer - Soft and gentle
566
- - **Features**: Speed control (0.25x to 4x), multiple output formats
595
+ - verse - Clear and melodic
596
+ - marin - Gentle and soothing
597
+ - cedar - Rich and resonant
598
+ - **Features**:
599
+ - Speed control (0.25x to 4x)
600
+ - Multiple output formats
601
+ - Voice instructions for gpt-4o-mini-tts (steer emotion, accent, tone)
567
602
  - **Output Formats**: mp3, opus, aac, flac, wav, pcm
568
603
  - **API Key**: Set `OPENAI_API_KEY` in your .env file
569
604
 
@@ -5,7 +5,7 @@
5
5
  ![Arch x86-63 | ARM | AppleSilicon](https://img.shields.io/badge/arch-x86--64%20%7C%20ARM%20%7C%20AppleSilicon-blue)
6
6
 
7
7
  ![MIT License](https://img.shields.io/badge/license-MIT-green.svg)
8
- ![Version](https://img.shields.io/badge/version-0.2.2-green.svg)
8
+ ![Version](https://img.shields.io/badge/version-0.4.0-green.svg)
9
9
  ![Development Status](https://img.shields.io/badge/status-stable-green.svg)
10
10
 
11
11
  A powerful command-line text-to-speech tool supporting multiple TTS providers (ElevenLabs, OpenAI, and Kokoro ONNX) with intelligent voice caching, name resolution, and flexible output options.
@@ -14,6 +14,18 @@ A powerful command-line text-to-speech tool supporting multiple TTS providers (E
14
14
 
15
15
  ## What's New
16
16
 
17
+ ### v0.4.0
18
+ - **OpenAI gpt-4o-mini-tts** - New steerable TTS model with `--instructions` option
19
+ - **7 new OpenAI voices** - ash, ballad, coral, sage, verse, marin, cedar (13 total)
20
+ - **ElevenLabs model updated** - Changed from deprecated `eleven_monolingual_v1` to `eleven_multilingual_v2`
21
+ - **Kokoro ONNX 0.5.0** - Updated to latest version
22
+
23
+ ### v0.3.0
24
+ - Major code refactoring with better modularity and code organization
25
+ - New utility modules for shared console, defaults, and HTTP client
26
+ - Test suite with 46 tests for better reliability
27
+ - Documentation synced to match current implementation
28
+
17
29
  ### v0.2.2
18
30
  - Updated all HTTP requests and downloaders to ignore SSL certificate errors
19
31
  - Improves compatibility with corporate proxies and development environments
@@ -341,6 +353,10 @@ par-tts "Stable voice" --stability 0.8 --similarity 0.7
341
353
  # Adjust OpenAI speech speed
342
354
  par-tts "Fast speech" --provider openai --speed 1.5
343
355
 
356
+ # Use OpenAI with voice instructions (gpt-4o-mini-tts only)
357
+ par-tts "Hello there!" --provider openai --instructions "Speak in a cheerful and positive tone"
358
+ par-tts "Good morning" -P openai -i "Speak like a pirate"
359
+
344
360
  # Keep temp files after playback
345
361
  par-tts "Keep this" --keep-temp
346
362
 
@@ -447,6 +463,7 @@ make clear-cache # Clear voice cache including samples
447
463
  |--------|-------|-------------|---------|
448
464
  | `--speed` | `-r` | Speech speed (0.25 to 4.0) | 1.0 |
449
465
  | `--format` | `-f` | Audio format (mp3, opus, aac, flac, wav) | mp3 |
466
+ | `--instructions` | `-i` | Voice instructions for gpt-4o-mini-tts (e.g., "Speak cheerfully") | None |
450
467
 
451
468
  ### Kokoro ONNX Options
452
469
 
@@ -480,7 +497,12 @@ make clear-cache # Clear voice cache including samples
480
497
 
481
498
  ### ElevenLabs
482
499
 
483
- - **Models**: eleven_monolingual_v1, eleven_multilingual_v1, eleven_multilingual_v2
500
+ - **Models**:
501
+ - `eleven_multilingual_v2` (default) - Most lifelike, 29 languages
502
+ - `eleven_v3` - Most expressive, 70+ languages
503
+ - `eleven_flash_v2.5` - Ultra-low latency (~75ms), 32 languages
504
+ - `eleven_turbo_v2.5` - Balanced quality/speed, 32 languages
505
+ - ~~`eleven_monolingual_v1`~~ - Deprecated, will be removed
484
506
  - **Voices**: 25+ voices with different accents and styles
485
507
  - **Features**: Voice cloning, stability control, similarity boost
486
508
  - **Smart Caching**:
@@ -492,15 +514,28 @@ make clear-cache # Clear voice cache including samples
492
514
 
493
515
  ### OpenAI
494
516
 
495
- - **Models**: tts-1 (optimized for speed), tts-1-hd (optimized for quality)
496
- - **Voices**:
517
+ - **Models**:
518
+ - `gpt-4o-mini-tts` (default) - Steerable TTS with instructions
519
+ - `tts-1` - Optimized for speed
520
+ - `tts-1-hd` - Optimized for quality
521
+ - **Voices** (13 total):
497
522
  - alloy - Neutral and balanced
523
+ - ash - Enthusiastic and energetic
524
+ - ballad - Warm and soulful
525
+ - coral - Friendly and approachable
498
526
  - echo - Smooth and articulate
499
527
  - fable - Expressive and animated
500
- - onyx - Deep and authoritative
501
528
  - nova - Warm and friendly (default)
529
+ - onyx - Deep and authoritative
530
+ - sage - Calm and wise
502
531
  - shimmer - Soft and gentle
503
- - **Features**: Speed control (0.25x to 4x), multiple output formats
532
+ - verse - Clear and melodic
533
+ - marin - Gentle and soothing
534
+ - cedar - Rich and resonant
535
+ - **Features**:
536
+ - Speed control (0.25x to 4x)
537
+ - Multiple output formats
538
+ - Voice instructions for gpt-4o-mini-tts (steer emotion, accent, tone)
504
539
  - **Output Formats**: mp3, opus, aac, flac, wav, pcm
505
540
  - **API Key**: Set `OPENAI_API_KEY` in your .env file
506
541
 
@@ -45,7 +45,7 @@ dependencies = [
45
45
  "platformdirs>=4.3.0",
46
46
  "pyyaml>=6.0.0",
47
47
  "openai>=1.100.1",
48
- "kokoro-onnx>=0.4.9",
48
+ "kokoro-onnx>=0.5.0",
49
49
  "soundfile>=0.13.1",
50
50
  ]
51
51
 
@@ -78,8 +78,14 @@ dev = [
78
78
  "pre-commit>=4.2.0",
79
79
  "ruff>=0.12.7",
80
80
  "hatchling>=1.27.0",
81
+ "pytest>=8.0.0",
82
+ "pytest-cov>=5.0.0",
81
83
  ]
82
84
 
85
+ [tool.pytest.ini_options]
86
+ testpaths = ["tests"]
87
+ addopts = "-v --cov=src --cov-report=term-missing"
88
+
83
89
  [tool.hatch.version]
84
90
  path = "src/__init__.py"
85
91
 
@@ -1,3 +1,3 @@
1
1
  """PAR CLI TTS - Command line text-to-speech tool."""
2
2
 
3
- __version__ = "0.2.2"
3
+ __version__ = "0.4.0"
@@ -6,9 +6,8 @@ from typing import Any
6
6
  import platformdirs
7
7
  import yaml
8
8
  from pydantic import BaseModel, Field, ValidationError
9
- from rich.console import Console
10
9
 
11
- console = Console()
10
+ from src.console import console
12
11
 
13
12
 
14
13
  class ConfigFile(BaseModel):
@@ -0,0 +1,13 @@
1
+ """Shared console instance for consistent output handling.
2
+
3
+ This module provides a single Console instance that can be imported
4
+ by all modules for consistent output formatting.
5
+ """
6
+
7
+ from rich.console import Console
8
+
9
+ # Shared console instance for standard output
10
+ console = Console()
11
+
12
+ # Console instance for error output (writes to stderr)
13
+ error_console = Console(stderr=True)
@@ -0,0 +1,33 @@
1
+ """Central default values for PAR CLI TTS.
2
+
3
+ This module provides default configuration values for all providers
4
+ to avoid duplication and ensure consistency.
5
+ """
6
+
7
+ import os
8
+
9
+ # Default provider
10
+ DEFAULT_PROVIDER = "kokoro-onnx"
11
+
12
+ # Default voices by provider
13
+ DEFAULT_ELEVENLABS_VOICE = "Juniper"
14
+ DEFAULT_OPENAI_VOICE = "nova"
15
+ DEFAULT_KOKORO_VOICE = "af_sarah"
16
+
17
+
18
+ def get_default_voice(provider: str) -> str:
19
+ """Get default voice for the specified provider.
20
+
21
+ Args:
22
+ provider: Provider name (elevenlabs, openai, kokoro-onnx).
23
+
24
+ Returns:
25
+ Default voice ID for the provider, checking environment
26
+ variables first, then falling back to built-in defaults.
27
+ """
28
+ defaults = {
29
+ "elevenlabs": os.getenv("ELEVENLABS_VOICE_ID", DEFAULT_ELEVENLABS_VOICE),
30
+ "openai": os.getenv("OPENAI_VOICE_ID", DEFAULT_OPENAI_VOICE),
31
+ "kokoro-onnx": os.getenv("KOKORO_VOICE_ID", DEFAULT_KOKORO_VOICE),
32
+ }
33
+ return defaults.get(provider, "")
@@ -4,9 +4,7 @@ import sys
4
4
  from enum import Enum
5
5
  from typing import NoReturn
6
6
 
7
- from rich.console import Console
8
-
9
- console = Console(stderr=True)
7
+ from src.console import error_console as console
10
8
 
11
9
 
12
10
  class ErrorType(Enum):
@@ -0,0 +1,21 @@
1
+ """HTTP client factory for PAR CLI TTS.
2
+
3
+ This module provides a factory function for creating HTTP clients
4
+ with consistent configuration across all providers.
5
+ """
6
+
7
+ import httpx
8
+
9
+
10
+ def create_http_client(timeout: float = 10.0, verify: bool = False) -> httpx.Client:
11
+ """Create an HTTP client with standard configuration.
12
+
13
+ Args:
14
+ timeout: Request timeout in seconds.
15
+ verify: Whether to verify SSL certificates. Defaults to False
16
+ to handle environments with SSL certificate issues.
17
+
18
+ Returns:
19
+ Configured httpx.Client instance.
20
+ """
21
+ return httpx.Client(verify=verify, timeout=timeout)
@@ -5,12 +5,11 @@ import sys
5
5
  from typing import Annotated
6
6
 
7
7
  import typer
8
- from rich.console import Console
9
8
  from rich.table import Table
10
9
 
10
+ from src.console import console
11
11
  from src.model_downloader import ModelDownloader
12
12
 
13
- console: Console = Console()
14
13
  app = typer.Typer(help="Manage Kokoro ONNX TTS models")
15
14
 
16
15
 
@@ -6,10 +6,9 @@ import urllib.request
6
6
  from pathlib import Path
7
7
 
8
8
  from platformdirs import user_data_dir
9
- from rich.console import Console
10
9
  from rich.progress import BarColumn, DownloadColumn, Progress, SpinnerColumn, TextColumn, TransferSpeedColumn
11
10
 
12
- console = Console()
11
+ from src.console import console
13
12
 
14
13
 
15
14
  class ModelDownloader:
@@ -1,19 +1,20 @@
1
1
  """ElevenLabs TTS provider implementation."""
2
2
 
3
+ import warnings
3
4
  from collections.abc import Iterator
4
5
  from pathlib import Path
5
6
  from typing import Any
6
7
 
7
- import httpx
8
- from elevenlabs import VoiceSettings, play, save
9
- from elevenlabs.client import ElevenLabs
10
- from rich.console import Console
8
+ with warnings.catch_warnings():
9
+ warnings.filterwarnings("ignore", message="Core Pydantic V1 functionality", category=UserWarning)
10
+ from elevenlabs import VoiceSettings, play, save
11
+ from elevenlabs.client import ElevenLabs
11
12
 
13
+ from src.defaults import DEFAULT_ELEVENLABS_VOICE
14
+ from src.http_client import create_http_client
12
15
  from src.providers.base import TTSProvider, Voice
13
16
  from src.voice_cache import VoiceCache, resolve_voice_identifier
14
17
 
15
- console = Console()
16
-
17
18
 
18
19
  class ElevenLabsProvider(TTSProvider):
19
20
  """ElevenLabs TTS provider."""
@@ -27,8 +28,8 @@ class ElevenLabsProvider(TTSProvider):
27
28
  **kwargs: Additional configuration.
28
29
  """
29
30
  super().__init__(api_key, **kwargs)
30
- # Create httpx client with SSL verification disabled
31
- http_client = httpx.Client(verify=False, timeout=kwargs.get("timeout", 10.0))
31
+ # Create httpx client with standard configuration
32
+ http_client = create_http_client(timeout=kwargs.get("timeout", 10.0))
32
33
  self.client = ElevenLabs(api_key=api_key, httpx_client=http_client)
33
34
  self.cache = VoiceCache(app_name="par-tts-elevenlabs")
34
35
 
@@ -45,12 +46,12 @@ class ElevenLabsProvider(TTSProvider):
45
46
  @property
46
47
  def default_model(self) -> str:
47
48
  """Default model for this provider."""
48
- return "eleven_monolingual_v1"
49
+ return "eleven_multilingual_v2"
49
50
 
50
51
  @property
51
52
  def default_voice(self) -> str:
52
53
  """Default voice for this provider."""
53
- return "aMSt68OGf4xUZAnLpTU8"
54
+ return DEFAULT_ELEVENLABS_VOICE
54
55
 
55
56
  def generate_speech(
56
57
  self,
@@ -8,7 +8,9 @@ from pathlib import Path
8
8
  import soundfile as sf
9
9
  from kokoro_onnx import Kokoro
10
10
 
11
+ from src.defaults import DEFAULT_KOKORO_VOICE
11
12
  from src.model_downloader import ModelDownloader
13
+ from src.utils import play_audio_bytes
12
14
 
13
15
  from .base import TTSProvider, Voice
14
16
 
@@ -76,7 +78,7 @@ class KokoroONNXProvider(TTSProvider):
76
78
  @property
77
79
  def default_voice(self) -> str:
78
80
  """Default voice ID."""
79
- return "af_sarah"
81
+ return DEFAULT_KOKORO_VOICE
80
82
 
81
83
  def list_voices(self) -> list[Voice]:
82
84
  """List available voices.
@@ -187,42 +189,4 @@ class KokoroONNXProvider(TTSProvider):
187
189
  if not isinstance(audio_data, bytes):
188
190
  audio_data = b"".join(audio_data)
189
191
 
190
- # Save to temp file and play
191
- with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file:
192
- temp_file.write(audio_data)
193
- temp_path = temp_file.name
194
-
195
- try:
196
- # Use system command to play audio
197
- import platform
198
- import subprocess
199
-
200
- system = platform.system()
201
- if system == "Darwin": # macOS
202
- # afplay supports volume flag (-v)
203
- subprocess.run(["afplay", "-v", str(volume), temp_path], check=True)
204
- elif system == "Windows":
205
- # Windows doesn't have native volume control for start command
206
- subprocess.run(["start", "", temp_path], shell=True, check=True)
207
- else: # Linux and others
208
- # Try different players with volume support
209
- players_with_volume = [
210
- ("paplay", ["--volume", str(int(volume * 65536))]), # paplay uses 0-65536
211
- ("ffplay", ["-volume", str(int(volume * 100)), "-nodisp", "-autoexit"]), # ffplay uses 0-100
212
- ("mpg123", ["-f", str(int(volume * 32768))]), # mpg123 uses scale factor
213
- ("aplay", []), # aplay doesn't support volume directly
214
- ]
215
-
216
- for player, volume_args in players_with_volume:
217
- try:
218
- cmd = [player] + volume_args + [temp_path]
219
- subprocess.run(cmd, check=True)
220
- break
221
- except (subprocess.CalledProcessError, FileNotFoundError):
222
- continue
223
- else:
224
- raise RuntimeError("No audio player found. Install aplay, paplay, ffplay, or mpg123.")
225
- finally:
226
- # Clean up temp file
227
- if Path(temp_path).exists():
228
- Path(temp_path).unlink()
192
+ play_audio_bytes(audio_data, volume=volume, suffix=".wav")
@@ -1,30 +1,38 @@
1
1
  """OpenAI TTS provider implementation."""
2
2
 
3
- import tempfile
4
3
  from collections.abc import Iterator
5
4
  from pathlib import Path
6
5
  from typing import Any, Literal
7
6
 
8
- import httpx
9
7
  from openai import OpenAI
10
- from rich.console import Console
11
8
 
9
+ from src.console import console
10
+ from src.defaults import DEFAULT_OPENAI_VOICE
11
+ from src.http_client import create_http_client
12
12
  from src.providers.base import TTSProvider, Voice
13
-
14
- console = Console()
13
+ from src.utils import play_audio_bytes
15
14
 
16
15
 
17
16
  class OpenAIProvider(TTSProvider):
18
17
  """OpenAI TTS provider."""
19
18
 
20
19
  # Available voices for OpenAI TTS
20
+ # tts-1/tts-1-hd support: alloy, ash, coral, echo, fable, onyx, nova, sage, shimmer
21
+ # gpt-4o-mini-tts adds: ballad, verse, marin, cedar
21
22
  VOICES = {
22
23
  "alloy": "Alloy - Neutral and balanced",
24
+ "ash": "Ash - Enthusiastic and energetic",
25
+ "ballad": "Ballad - Warm and soulful",
26
+ "coral": "Coral - Friendly and approachable",
23
27
  "echo": "Echo - Smooth and articulate",
24
28
  "fable": "Fable - Expressive and animated",
25
- "onyx": "Onyx - Deep and authoritative",
26
29
  "nova": "Nova - Warm and friendly",
30
+ "onyx": "Onyx - Deep and authoritative",
31
+ "sage": "Sage - Calm and wise",
27
32
  "shimmer": "Shimmer - Soft and gentle",
33
+ "verse": "Verse - Clear and melodic",
34
+ "marin": "Marin - Gentle and soothing",
35
+ "cedar": "Cedar - Rich and resonant",
28
36
  }
29
37
 
30
38
  def __init__(self, api_key: str, **kwargs: Any):
@@ -36,8 +44,8 @@ class OpenAIProvider(TTSProvider):
36
44
  **kwargs: Additional configuration.
37
45
  """
38
46
  super().__init__(api_key, **kwargs)
39
- # Create httpx client with SSL verification disabled
40
- http_client = httpx.Client(verify=False, timeout=kwargs.get("timeout", 10.0))
47
+ # Create httpx client with standard configuration
48
+ http_client = create_http_client(timeout=kwargs.get("timeout", 10.0))
41
49
  self.client = OpenAI(api_key=api_key, http_client=http_client)
42
50
 
43
51
  @property
@@ -53,12 +61,12 @@ class OpenAIProvider(TTSProvider):
53
61
  @property
54
62
  def default_model(self) -> str:
55
63
  """Default model for this provider."""
56
- return "tts-1"
64
+ return "gpt-4o-mini-tts"
57
65
 
58
66
  @property
59
67
  def default_voice(self) -> str:
60
68
  """Default voice for this provider."""
61
- return "nova"
69
+ return DEFAULT_OPENAI_VOICE
62
70
 
63
71
  def generate_speech(
64
72
  self,
@@ -67,6 +75,7 @@ class OpenAIProvider(TTSProvider):
67
75
  model: str | None = None,
68
76
  response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] = "mp3",
69
77
  speed: float = 1.0,
78
+ instructions: str | None = None,
70
79
  **kwargs: Any,
71
80
  ) -> bytes:
72
81
  """
@@ -74,10 +83,11 @@ class OpenAIProvider(TTSProvider):
74
83
 
75
84
  Args:
76
85
  text: Text to convert to speech.
77
- voice: Voice to use (alloy, echo, fable, onyx, nova, shimmer).
78
- model: Model to use (tts-1 or tts-1-hd).
86
+ voice: Voice to use (alloy, ash, ballad, coral, echo, fable, nova, onyx, sage, shimmer, verse, marin, cedar).
87
+ model: Model to use (gpt-4o-mini-tts, tts-1, or tts-1-hd).
79
88
  response_format: Audio format (mp3, opus, aac, flac, wav).
80
89
  speed: Speed of speech (0.25 to 4.0).
90
+ instructions: Instructions for voice style (gpt-4o-mini-tts only).
81
91
  **kwargs: Additional parameters.
82
92
 
83
93
  Returns:
@@ -89,13 +99,20 @@ class OpenAIProvider(TTSProvider):
89
99
  # Ensure speed is within valid range
90
100
  speed = max(0.25, min(4.0, speed))
91
101
 
92
- response = self.client.audio.speech.create(
93
- model=model,
94
- voice=voice, # type: ignore
95
- input=text,
96
- response_format=response_format, # type: ignore
97
- speed=speed,
98
- )
102
+ # Build request parameters
103
+ request_params: dict[str, Any] = {
104
+ "model": model,
105
+ "voice": voice,
106
+ "input": text,
107
+ "response_format": response_format,
108
+ "speed": speed,
109
+ }
110
+
111
+ # Add instructions parameter for gpt-4o-mini-tts model
112
+ if instructions and model == "gpt-4o-mini-tts":
113
+ request_params["instructions"] = instructions
114
+
115
+ response = self.client.audio.speech.create(**request_params)
99
116
 
100
117
  # Get audio data as bytes
101
118
  audio_bytes = response.content
@@ -177,45 +194,8 @@ class OpenAIProvider(TTSProvider):
177
194
  audio_data: Audio data to play (bytes or iterator).
178
195
  volume: Volume level (0.0 = silent, 1.0 = normal, 2.0 = double volume).
179
196
  """
180
- # OpenAI doesn't provide a play function, so we'll use a temporary file
181
- # and system audio player
182
- import subprocess
183
- import sys
184
-
185
197
  # Convert iterator to bytes if needed
186
198
  if not isinstance(audio_data, bytes):
187
199
  audio_data = b"".join(audio_data)
188
200
 
189
- with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as tmp:
190
- tmp.write(audio_data)
191
- tmp_path = tmp.name
192
-
193
- try:
194
- # Determine the appropriate command based on the platform
195
- if sys.platform == "darwin": # macOS
196
- # afplay supports volume flag (-v)
197
- subprocess.run(["afplay", "-v", str(volume), tmp_path], check=True)
198
- elif sys.platform == "win32": # Windows
199
- # Windows doesn't have native volume control for start command
200
- subprocess.run(["start", "", tmp_path], shell=True, check=True)
201
- else: # Linux and others
202
- # Try common audio players with volume support
203
- players_with_volume = [
204
- ("paplay", ["--volume", str(int(volume * 65536))]), # paplay uses 0-65536
205
- ("ffplay", ["-volume", str(int(volume * 100)), "-nodisp", "-autoexit"]), # ffplay uses 0-100
206
- ("mpg123", ["-f", str(int(volume * 32768))]), # mpg123 uses scale factor
207
- ("aplay", []), # aplay doesn't support volume directly
208
- ]
209
-
210
- for player, volume_args in players_with_volume:
211
- try:
212
- cmd = [player] + volume_args + [tmp_path]
213
- subprocess.run(cmd, check=True)
214
- break
215
- except (subprocess.CalledProcessError, FileNotFoundError):
216
- continue
217
- else:
218
- console.print("[yellow]Warning: Could not find audio player. Audio saved but not played.[/yellow]")
219
- finally:
220
- # Clean up temp file
221
- Path(tmp_path).unlink(missing_ok=True)
201
+ play_audio_bytes(audio_data, volume=volume, suffix=".mp3")