par-cli-tts 0.4.1__tar.gz → 0.4.2__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.4.1 → par_cli_tts-0.4.2}/PKG-INFO +5 -1
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/README.md +4 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/__init__.py +1 -1
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/config_file.py +8 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/providers/elevenlabs.py +8 -12
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/tts_cli.py +40 -18
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/.claude/output-styles/tts-summary.md +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/.gitignore +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/LICENSE +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/pyproject.toml +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/config.py +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/console.py +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/defaults.py +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/errors.py +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/http_client.py +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/install_claude_style.py +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/kokoro_cli.py +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/model_downloader.py +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/providers/__init__.py +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/providers/base.py +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/providers/kokoro_onnx.py +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/providers/openai.py +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/py.typed +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/utils.py +0 -0
- {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/voice_cache.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: par-cli-tts
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
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
|
|
@@ -463,6 +463,10 @@ provider: kokoro-onnx
|
|
|
463
463
|
# Default voice
|
|
464
464
|
voice: Rachel
|
|
465
465
|
|
|
466
|
+
# API keys (optional - can also be set via environment variables)
|
|
467
|
+
# elevenlabs_api_key: your-elevenlabs-api-key-here
|
|
468
|
+
# openai_api_key: your-openai-api-key-here
|
|
469
|
+
|
|
466
470
|
# Output settings
|
|
467
471
|
output_dir: ~/Documents/audio
|
|
468
472
|
keep_temp: false
|
|
@@ -400,6 +400,10 @@ provider: kokoro-onnx
|
|
|
400
400
|
# Default voice
|
|
401
401
|
voice: Rachel
|
|
402
402
|
|
|
403
|
+
# API keys (optional - can also be set via environment variables)
|
|
404
|
+
# elevenlabs_api_key: your-elevenlabs-api-key-here
|
|
405
|
+
# openai_api_key: your-openai-api-key-here
|
|
406
|
+
|
|
403
407
|
# Output settings
|
|
404
408
|
output_dir: ~/Documents/audio
|
|
405
409
|
keep_temp: false
|
|
@@ -18,6 +18,10 @@ class ConfigFile(BaseModel):
|
|
|
18
18
|
voice: str | None = Field(None, description="Default voice")
|
|
19
19
|
model: str | None = Field(None, description="Default model")
|
|
20
20
|
|
|
21
|
+
# API keys (optional - can also be set via environment variables)
|
|
22
|
+
elevenlabs_api_key: str | None = Field(None, description="ElevenLabs API key")
|
|
23
|
+
openai_api_key: str | None = Field(None, description="OpenAI API key")
|
|
24
|
+
|
|
21
25
|
# Output settings
|
|
22
26
|
output_dir: str | None = Field(None, description="Default output directory")
|
|
23
27
|
output_format: str | None = Field(None, description="Default output format")
|
|
@@ -102,6 +106,10 @@ class ConfigManager:
|
|
|
102
106
|
"# Default model",
|
|
103
107
|
"# model: eleven_monolingual_v1",
|
|
104
108
|
"",
|
|
109
|
+
"# API keys (optional - can also be set via environment variables)",
|
|
110
|
+
"# elevenlabs_api_key: your-elevenlabs-api-key-here",
|
|
111
|
+
"# openai_api_key: your-openai-api-key-here",
|
|
112
|
+
"",
|
|
105
113
|
"# Output settings",
|
|
106
114
|
"# output_dir: ~/Documents/audio",
|
|
107
115
|
"# output_format: mp3",
|
|
@@ -7,12 +7,14 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
with warnings.catch_warnings():
|
|
9
9
|
warnings.filterwarnings("ignore", message="Core Pydantic V1 functionality", category=UserWarning)
|
|
10
|
-
from elevenlabs import VoiceSettings
|
|
10
|
+
from elevenlabs import VoiceSettings
|
|
11
11
|
from elevenlabs.client import ElevenLabs
|
|
12
|
+
from elevenlabs.play import save
|
|
12
13
|
|
|
13
14
|
from src.defaults import DEFAULT_ELEVENLABS_VOICE
|
|
14
15
|
from src.http_client import create_http_client
|
|
15
16
|
from src.providers.base import TTSProvider, Voice
|
|
17
|
+
from src.utils import play_audio_bytes
|
|
16
18
|
from src.voice_cache import VoiceCache, resolve_voice_identifier
|
|
17
19
|
|
|
18
20
|
|
|
@@ -152,20 +154,14 @@ class ElevenLabsProvider(TTSProvider):
|
|
|
152
154
|
|
|
153
155
|
def play_audio(self, audio_data: bytes | Iterator[bytes], volume: float = 1.0) -> None:
|
|
154
156
|
"""
|
|
155
|
-
Play audio data.
|
|
157
|
+
Play audio data with volume control.
|
|
156
158
|
|
|
157
159
|
Args:
|
|
158
160
|
audio_data: Audio data to play.
|
|
159
161
|
volume: Volume level (0.0 = silent, 1.0 = normal, 2.0 = double volume).
|
|
160
|
-
Note: ElevenLabs play() doesn't support volume, will use system default.
|
|
161
162
|
"""
|
|
162
|
-
#
|
|
163
|
-
if isinstance(audio_data, bytes):
|
|
164
|
-
|
|
165
|
-
else:
|
|
166
|
-
# Convert iterator to bytes for playback
|
|
167
|
-
audio_bytes = b"".join(audio_data)
|
|
168
|
-
play(audio_bytes)
|
|
163
|
+
# Convert iterator to bytes if needed
|
|
164
|
+
if not isinstance(audio_data, bytes):
|
|
165
|
+
audio_data = b"".join(audio_data)
|
|
169
166
|
|
|
170
|
-
|
|
171
|
-
# Consider using system players with volume support if needed
|
|
167
|
+
play_audio_bytes(audio_data, volume=volume, suffix=".mp3")
|
|
@@ -28,48 +28,60 @@ from src.providers import PROVIDERS, TTSProvider
|
|
|
28
28
|
app = typer.Typer(help="Text-to-speech command line tool with multiple provider support")
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def get_api_key(provider: str) -> str | None:
|
|
31
|
+
def get_api_key(provider: str, config_file: Any = None) -> str | None:
|
|
32
32
|
"""
|
|
33
|
-
Get API key for the specified provider from environment.
|
|
33
|
+
Get API key for the specified provider from config file or environment.
|
|
34
34
|
|
|
35
35
|
Args:
|
|
36
36
|
provider: Provider name (elevenlabs, openai, kokoro-onnx).
|
|
37
|
+
config_file: Optional config file with API keys.
|
|
37
38
|
|
|
38
39
|
Returns:
|
|
39
40
|
API key string or None for providers that don't need one.
|
|
40
41
|
|
|
41
42
|
Raises:
|
|
42
|
-
SystemExit: If API key is not found
|
|
43
|
+
SystemExit: If API key is not found anywhere.
|
|
43
44
|
"""
|
|
44
45
|
# kokoro-onnx doesn't need an API key
|
|
45
46
|
if provider == "kokoro-onnx":
|
|
46
47
|
return None
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
49
|
+
# Map provider to config file field and environment variable
|
|
50
|
+
key_map = {
|
|
51
|
+
"elevenlabs": ("elevenlabs_api_key", "ELEVENLABS_API_KEY"),
|
|
52
|
+
"openai": ("openai_api_key", "OPENAI_API_KEY"),
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
if not env_var:
|
|
55
|
+
if provider not in key_map:
|
|
55
56
|
handle_error(f"Unknown provider '{provider}'", ErrorType.INVALID_PROVIDER)
|
|
56
57
|
return None # For type checker, never reached
|
|
57
58
|
|
|
59
|
+
config_field, env_var = key_map[provider]
|
|
60
|
+
|
|
61
|
+
# Check config file first
|
|
62
|
+
if config_file:
|
|
63
|
+
api_key = getattr(config_file, config_field, None)
|
|
64
|
+
if api_key:
|
|
65
|
+
return api_key
|
|
66
|
+
|
|
67
|
+
# Fall back to environment variable
|
|
58
68
|
api_key = os.getenv(env_var)
|
|
59
|
-
if not api_key
|
|
69
|
+
if not api_key:
|
|
60
70
|
handle_error(
|
|
61
|
-
f"{env_var} not found. Please set {env_var} in your
|
|
71
|
+
f"{env_var} not found. Please set {env_var} in your config file or environment",
|
|
72
|
+
ErrorType.MISSING_API_KEY,
|
|
62
73
|
)
|
|
63
74
|
|
|
64
75
|
return api_key
|
|
65
76
|
|
|
66
77
|
|
|
67
|
-
def create_provider(provider_name: str, **kwargs: Any) -> TTSProvider:
|
|
78
|
+
def create_provider(provider_name: str, config_file: Any = None, **kwargs: Any) -> TTSProvider:
|
|
68
79
|
"""
|
|
69
80
|
Create a TTS provider instance.
|
|
70
81
|
|
|
71
82
|
Args:
|
|
72
83
|
provider_name: Name of the provider.
|
|
84
|
+
config_file: Optional config file with API keys.
|
|
73
85
|
**kwargs: Additional provider configuration.
|
|
74
86
|
|
|
75
87
|
Returns:
|
|
@@ -83,7 +95,7 @@ def create_provider(provider_name: str, **kwargs: Any) -> TTSProvider:
|
|
|
83
95
|
f"Unknown provider '{provider_name}'. Available: {', '.join(PROVIDERS.keys())}", ErrorType.INVALID_PROVIDER
|
|
84
96
|
)
|
|
85
97
|
|
|
86
|
-
api_key = get_api_key(provider_name)
|
|
98
|
+
api_key = get_api_key(provider_name, config_file)
|
|
87
99
|
validate_api_key(api_key, provider_name)
|
|
88
100
|
provider_class = PROVIDERS[provider_name]
|
|
89
101
|
|
|
@@ -477,7 +489,10 @@ def handle_speech_generation(
|
|
|
477
489
|
|
|
478
490
|
if play_audio:
|
|
479
491
|
console.print("[cyan]Playing audio...[/cyan]")
|
|
480
|
-
|
|
492
|
+
# Read from saved file since iterator was consumed
|
|
493
|
+
with open(output_path, "rb") as f:
|
|
494
|
+
audio_bytes = f.read()
|
|
495
|
+
tts_provider.play_audio(audio_bytes, volume=volume)
|
|
481
496
|
else:
|
|
482
497
|
if play_audio:
|
|
483
498
|
console.print("[cyan]Playing audio...[/cyan]")
|
|
@@ -496,7 +511,10 @@ def handle_speech_generation(
|
|
|
496
511
|
tts_provider.save_audio(audio_data, tmp_path)
|
|
497
512
|
|
|
498
513
|
try:
|
|
499
|
-
|
|
514
|
+
# Read from saved file since iterator was consumed
|
|
515
|
+
with open(tmp_path, "rb") as f:
|
|
516
|
+
audio_bytes = f.read()
|
|
517
|
+
tts_provider.play_audio(audio_bytes, volume=volume)
|
|
500
518
|
|
|
501
519
|
if keep_temp or temp_dir:
|
|
502
520
|
console.print(f"[green]Audio saved to: {tmp_path}[/green]")
|
|
@@ -543,14 +561,14 @@ def main(
|
|
|
543
561
|
str | None, typer.Argument(help="Text to convert to speech. Use '-' for stdin, '@filename' to read from file")
|
|
544
562
|
] = None,
|
|
545
563
|
provider: Annotated[
|
|
546
|
-
str,
|
|
564
|
+
str | None,
|
|
547
565
|
typer.Option(
|
|
548
566
|
"-P",
|
|
549
567
|
"--provider",
|
|
550
568
|
help="TTS provider to use (elevenlabs, openai, kokoro-onnx)",
|
|
551
569
|
envvar="TTS_PROVIDER",
|
|
552
570
|
),
|
|
553
|
-
] =
|
|
571
|
+
] = None,
|
|
554
572
|
voice: Annotated[
|
|
555
573
|
str | None,
|
|
556
574
|
typer.Option(
|
|
@@ -754,7 +772,7 @@ def main(
|
|
|
754
772
|
|
|
755
773
|
# Apply config file defaults (CLI args override these)
|
|
756
774
|
if config_file:
|
|
757
|
-
provider = provider or config_file.provider
|
|
775
|
+
provider = provider or config_file.provider
|
|
758
776
|
voice = voice or config_file.voice
|
|
759
777
|
model = model or config_file.model
|
|
760
778
|
output = output or (Path(config_file.output_dir) / "output.mp3" if config_file.output_dir else None)
|
|
@@ -771,6 +789,10 @@ def main(
|
|
|
771
789
|
)
|
|
772
790
|
debug = debug or config_file.debug or False
|
|
773
791
|
|
|
792
|
+
# Apply default provider if still not set
|
|
793
|
+
if not provider:
|
|
794
|
+
provider = DEFAULT_PROVIDER
|
|
795
|
+
|
|
774
796
|
# Store debug mode globally for error handler
|
|
775
797
|
sys._debug_mode = debug # type: ignore
|
|
776
798
|
|
|
@@ -794,7 +816,7 @@ def main(
|
|
|
794
816
|
return
|
|
795
817
|
|
|
796
818
|
# Create provider
|
|
797
|
-
tts_provider = create_provider(provider)
|
|
819
|
+
tts_provider = create_provider(provider, config_file)
|
|
798
820
|
|
|
799
821
|
# Handle cache management operations (ElevenLabs only)
|
|
800
822
|
if refresh_cache or clear_cache_samples:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|