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.
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/PKG-INFO +43 -8
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/README.md +41 -6
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/pyproject.toml +7 -1
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/__init__.py +1 -1
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/config_file.py +1 -2
- par_cli_tts-0.4.0/src/console.py +13 -0
- par_cli_tts-0.4.0/src/defaults.py +33 -0
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/errors.py +1 -3
- par_cli_tts-0.4.0/src/http_client.py +21 -0
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/kokoro_cli.py +1 -2
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/model_downloader.py +1 -2
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/providers/elevenlabs.py +11 -10
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/providers/kokoro_onnx.py +4 -40
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/providers/openai.py +37 -57
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/tts_cli.py +529 -304
- par_cli_tts-0.4.0/src/utils.py +183 -0
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/voice_cache.py +3 -3
- par_cli_tts-0.2.2/src/utils.py +0 -101
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/.gitignore +0 -0
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/LICENSE +0 -0
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/config.py +0 -0
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/providers/__init__.py +0 -0
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/providers/base.py +0 -0
- {par_cli_tts-0.2.2 → par_cli_tts-0.4.0}/src/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: par-cli-tts
|
|
3
|
-
Version: 0.
|
|
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.
|
|
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
|

|
|
69
69
|
|
|
70
70
|

|
|
71
|
-

|
|
72
72
|

|
|
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**:
|
|
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**:
|
|
559
|
-
-
|
|
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
|
-
-
|
|
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
|

|
|
6
6
|
|
|
7
7
|

|
|
8
|
-

|
|
9
9
|

|
|
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**:
|
|
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**:
|
|
496
|
-
-
|
|
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
|
-
-
|
|
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.
|
|
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
|
|
|
@@ -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, "")
|
|
@@ -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
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
from elevenlabs
|
|
10
|
-
from
|
|
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
|
|
31
|
-
http_client =
|
|
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 "
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
40
|
-
http_client =
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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")
|