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.
Files changed (25) hide show
  1. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/PKG-INFO +5 -1
  2. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/README.md +4 -0
  3. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/__init__.py +1 -1
  4. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/config_file.py +8 -0
  5. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/providers/elevenlabs.py +8 -12
  6. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/tts_cli.py +40 -18
  7. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/.claude/output-styles/tts-summary.md +0 -0
  8. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/.gitignore +0 -0
  9. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/LICENSE +0 -0
  10. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/pyproject.toml +0 -0
  11. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/config.py +0 -0
  12. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/console.py +0 -0
  13. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/defaults.py +0 -0
  14. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/errors.py +0 -0
  15. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/http_client.py +0 -0
  16. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/install_claude_style.py +0 -0
  17. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/kokoro_cli.py +0 -0
  18. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/model_downloader.py +0 -0
  19. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/providers/__init__.py +0 -0
  20. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/providers/base.py +0 -0
  21. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/providers/kokoro_onnx.py +0 -0
  22. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/providers/openai.py +0 -0
  23. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/py.typed +0 -0
  24. {par_cli_tts-0.4.1 → par_cli_tts-0.4.2}/src/utils.py +0 -0
  25. {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.1
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
@@ -1,3 +1,3 @@
1
1
  """PAR CLI TTS - Command line text-to-speech tool."""
2
2
 
3
- __version__ = "0.4.1"
3
+ __version__ = "0.4.2"
@@ -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, play, save
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
- # For play, we need to convert iterator to bytes
163
- if isinstance(audio_data, bytes):
164
- play(audio_data)
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
- # Note: ElevenLabs play() function doesn't support volume control
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 in environment.
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
- env_var_map = {
49
- "elevenlabs": "ELEVENLABS_API_KEY",
50
- "openai": "OPENAI_API_KEY",
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
- env_var = env_var_map.get(provider)
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 and provider != "kokoro-onnx":
69
+ if not api_key:
60
70
  handle_error(
61
- f"{env_var} not found. Please set {env_var} in your .env file or environment", ErrorType.MISSING_API_KEY
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
- tts_provider.play_audio(audio_data, volume=volume)
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
- tts_provider.play_audio(audio_data, volume=volume)
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
- ] = DEFAULT_PROVIDER,
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 or DEFAULT_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