deepctl-cmd-speak 0.0.3__tar.gz → 0.0.4__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: deepctl-cmd-speak
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: Speak (text-to-speech) command for deepctl
5
5
  Author-email: Deepgram <devrel@deepgram.com>
6
6
  Maintainer-email: Deepgram <devrel@deepgram.com>
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
- Requires-Dist: deepctl-core>=0.1.10
17
+ Requires-Dist: deepctl-core>=0.2.15
18
18
  Requires-Dist: click>=8.0.0
19
19
  Requires-Dist: rich>=13.0.0
20
20
  Requires-Dist: pydantic>=2.0.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepctl-cmd-speak"
7
- version = "0.0.3" # x-release-please-version
7
+ version = "0.0.4" # x-release-please-version
8
8
  description = "Speak (text-to-speech) command for deepctl"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -21,7 +21,7 @@ classifiers = [
21
21
  keywords = ["deepgram", "cli", "tts", "text-to-speech", "speak"]
22
22
  requires-python = ">=3.10"
23
23
  dependencies = [
24
- "deepctl-core>=0.1.10",
24
+ "deepctl-core>=0.2.15",
25
25
  "click>=8.0.0",
26
26
  "rich>=13.0.0",
27
27
  "pydantic>=2.0.0",
@@ -27,6 +27,13 @@ if TYPE_CHECKING:
27
27
 
28
28
  console = Console(stderr=True)
29
29
 
30
+ # Flux (Speak v2) streaming controls, per the /v2/speak API. `speed` is a
31
+ # 0.05-increment multiplier and `expressivity` is a small integer range; both
32
+ # are validated up front so we fail with a clear message instead of surfacing
33
+ # a raw SPEED_OUT_OF_RANGE / server error mid-stream.
34
+ _FLUX_SPEEDS = (0.85, 0.90, 0.95, 1.00, 1.05, 1.10, 1.15)
35
+ _FLUX_EXPRESSIVITY = (-2, -1, 0, 1, 2)
36
+
30
37
 
31
38
  def _fmt_bytes(n: int) -> str:
32
39
  """Human-readable byte count for progress display."""
@@ -162,26 +169,38 @@ class SpeakCommand(BaseCommand):
162
169
  ci_friendly = True
163
170
 
164
171
  examples = [
172
+ # Flux TTS is the default (flux-alexis-en) — WebSocket streaming,
173
+ # raw audio wrapped in WAV. Piped audio is a streaming WAV (unknown
174
+ # length up front), so pass `-loglevel error` to silence ffmpeg's
175
+ # cosmetic end-of-stream notice.
165
176
  'dg speak "Hello world"',
166
- 'dg speak "Hello world" -o hello.mp3',
167
- "dg speak --file message.txt -o output.mp3",
168
- 'echo "Hello" | dg speak -o hello.mp3',
169
- 'dg speak "Hello" | ffplay -nodisp -',
177
+ 'dg speak "Hello world" -o hello.wav',
178
+ "dg speak --file message.txt -o output.wav",
179
+ 'dg speak "Hello" | ffplay -loglevel error -nodisp -autoexit -',
180
+ # Flux TTS streaming controls: --speed (0.85-1.15) and beta
181
+ # --expressivity (-2..2, default 0).
182
+ 'dg speak "A little slower, please" --speed 0.9 -o slow.wav',
183
+ 'dg speak "So exciting!" --expressivity 2 -o lively.wav # beta; default: 0',
184
+ # Aura (Speak v1, batch REST) — opt in with -m aura-*; needed for
185
+ # containerized formats like mp3.
186
+ 'dg speak "Hello" -m aura-2-asteria-en -o hello.mp3',
170
187
  'dg speak "Hello" -m aura-2-luna-en -o hello.wav --encoding linear16 --container wav',
171
- # Flux TTS — WebSocket streaming (flux-* models), streaming by default.
172
- # Piped audio is a streaming WAV (unknown length up front), so pass
173
- # `-loglevel error` to silence ffmpeg's cosmetic end-of-stream notice.
174
- 'dg speak "Hello from Flux" -m flux-alexis-en -o hello.wav',
175
- 'dg speak "Hello from Flux" -m flux-alexis-en | ffplay -loglevel error -nodisp -autoexit -',
188
+ 'echo "Hello" | dg speak -o hello.mp3 -m aura-2-asteria-en',
189
+ # Aura-2 Spanish voice (run `dg models` for the full list).
190
+ 'dg speak "Hola, mundo" -m aura-2-selena-es -o hola.mp3',
176
191
  ]
177
192
  agent_help = (
178
193
  "Convert text to speech using Deepgram's TTS API. "
179
194
  "Text can be provided as an argument, from a file, or piped via stdin. "
180
195
  "Audio is written to a file (--output) or stdout for piping. "
181
- "aura-* models use Speak v1 (batch REST). flux-* (Flux TTS) models use "
182
- "Speak v2, streaming over WebSocket by default and emitting raw audio; "
183
- "linear16 output is wrapped in a WAV container so it is directly playable. "
184
- "Supports model selection and audio format options."
196
+ "By default (flux-* models, e.g. flux-alexis-en) uses Flux TTS / Speak "
197
+ "v2, streaming over WebSocket and emitting raw audio; linear16 output is "
198
+ "wrapped in a WAV container so it is directly playable. Pass an aura-* "
199
+ "model to use Speak v1 (batch REST), which supports containerized "
200
+ "formats like mp3. Supports model selection and audio format options. "
201
+ "Flux TTS models also accept --speed (0.85–1.15) and beta "
202
+ "--expressivity (-2..2; default 0 = nominal) streaming controls; these "
203
+ "are rejected for other models."
185
204
  )
186
205
 
187
206
  def get_arguments(self) -> list[dict[str, Any]]:
@@ -201,13 +220,13 @@ class SpeakCommand(BaseCommand):
201
220
  {
202
221
  "names": ["--model", "-m"],
203
222
  "help": (
204
- "TTS model. aura-* = Speak v1 (REST batch; default "
205
- "aura-2-asteria-en); flux-* = Flux TTS / Speak v2 (WebSocket "
206
- "streaming, e.g. flux-alexis-en)."
223
+ "TTS model. flux-* = Flux TTS / Speak v2 (WebSocket "
224
+ "streaming; default flux-alexis-en); aura-* = Speak v1 "
225
+ "(REST batch, e.g. aura-2-asteria-en)."
207
226
  ),
208
227
  "type": str,
209
228
  "is_option": True,
210
- "default": "aura-2-asteria-en",
229
+ "default": "flux-alexis-en",
211
230
  },
212
231
  {
213
232
  "names": ["--encoding"],
@@ -234,6 +253,25 @@ class SpeakCommand(BaseCommand):
234
253
  "type": float,
235
254
  "is_option": True,
236
255
  },
256
+ {
257
+ "names": ["--speed"],
258
+ "help": (
259
+ "Flux TTS (v2) only. Speech-rate multiplier: 0.85, 0.90, 0.95, "
260
+ "1.00, 1.05, 1.10, or 1.15 (1.00 = nominal)."
261
+ ),
262
+ "type": float,
263
+ "is_option": True,
264
+ },
265
+ {
266
+ "names": ["--expressivity"],
267
+ "help": (
268
+ "Flux TTS (v2) only, beta. Expressive range: -2, -1, 0, 1, "
269
+ "or 2 (default 0 = nominal; negative flatter, positive more "
270
+ "animated). Fixed for the connection."
271
+ ),
272
+ "type": int,
273
+ "is_option": True,
274
+ },
237
275
  {
238
276
  "names": ["--file", "-f"],
239
277
  "help": "Read text from file",
@@ -251,10 +289,12 @@ class SpeakCommand(BaseCommand):
251
289
  ) -> BaseResult | None:
252
290
  text = kwargs.get("text")
253
291
  output_path = kwargs.get("output")
254
- model = kwargs.get("model") or "aura-2-asteria-en"
292
+ model = kwargs.get("model") or "flux-alexis-en"
255
293
  encoding = kwargs.get("encoding")
256
294
  container = kwargs.get("container")
257
295
  sample_rate = kwargs.get("sample_rate")
296
+ speed = kwargs.get("speed")
297
+ expressivity = kwargs.get("expressivity")
258
298
  file_path = kwargs.get("file")
259
299
 
260
300
  # Resolve text input: arg > --file > stdin
@@ -282,8 +322,29 @@ class SpeakCommand(BaseCommand):
282
322
  message="No output specified. Use -o/--output to save to file, or pipe stdout.",
283
323
  )
284
324
 
285
- # Flux models stream over the WebSocket (speak.v2); Aura uses REST (speak.v1).
286
- is_flux = model.lower().startswith("flux")
325
+ # Only the documented flux-* namespace uses speak.v2. Aura and unknown
326
+ # model names pass through to the REST API so the service can resolve them.
327
+ is_flux = model.lower().startswith("flux-")
328
+
329
+ # speed / expressivity are Flux (Speak v2) connect controls; reject them
330
+ # for other models rather than silently dropping them. Raise (not return)
331
+ # so the failure exits non-zero in every output mode.
332
+ if not is_flux and (speed is not None or expressivity is not None):
333
+ raise click.ClickException(
334
+ "--speed and --expressivity are only supported for Flux TTS "
335
+ "(Speak v2) models (flux-*). They are not available for "
336
+ f"Speak v1 model '{model}'."
337
+ )
338
+ if speed is not None and speed not in _FLUX_SPEEDS:
339
+ allowed = ", ".join(f"{s:.2f}" for s in _FLUX_SPEEDS)
340
+ raise click.ClickException(
341
+ f"--speed must be one of: {allowed} (got {speed})."
342
+ )
343
+ if expressivity is not None and expressivity not in _FLUX_EXPRESSIVITY:
344
+ allowed = ", ".join(str(e) for e in _FLUX_EXPRESSIVITY)
345
+ raise click.ClickException(
346
+ f"--expressivity must be one of: {allowed} (got {expressivity})."
347
+ )
287
348
 
288
349
  if is_flux:
289
350
  # WebSocket streaming path. Streaming output is raw audio, so
@@ -310,6 +371,8 @@ class SpeakCommand(BaseCommand):
310
371
  model=model,
311
372
  encoding=eff_encoding,
312
373
  sample_rate=eff_sample_rate,
374
+ speed=speed,
375
+ expressivity=expressivity,
313
376
  )
314
377
  )
315
378
 
@@ -392,7 +455,7 @@ class SpeakCommand(BaseCommand):
392
455
  # to the stderr console.
393
456
  return None
394
457
 
395
- # REST path (Aura v1) — unchanged.
458
+ # REST path (Speak v1, including Aura and unknown-model pass-through).
396
459
  try:
397
460
  console.print(f"[blue]Generating speech with {model}...[/blue]")
398
461
 
@@ -440,6 +503,11 @@ class SpeakCommand(BaseCommand):
440
503
  # summary above went to the stderr console.
441
504
  return None
442
505
 
506
+ except click.ClickException:
507
+ raise
443
508
  except Exception as e:
444
- console.print(f"[red]Error generating speech:[/red] {e}")
445
- return BaseResult(status="error", message=str(e))
509
+ # Raise (not return an error result) so the failure exits non-zero
510
+ # in every output mode — a returned BaseResult is only printed and
511
+ # still exits 0. Reachable now that unknown models (e.g. a bare
512
+ # "flux" typo) route here instead of the raising v2 path.
513
+ raise click.ClickException(f"Error generating speech: {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepctl-cmd-speak
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: Speak (text-to-speech) command for deepctl
5
5
  Author-email: Deepgram <devrel@deepgram.com>
6
6
  Maintainer-email: Deepgram <devrel@deepgram.com>
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
- Requires-Dist: deepctl-core>=0.1.10
17
+ Requires-Dist: deepctl-core>=0.2.15
18
18
  Requires-Dist: click>=8.0.0
19
19
  Requires-Dist: rich>=13.0.0
20
20
  Requires-Dist: pydantic>=2.0.0
@@ -1,4 +1,4 @@
1
- deepctl-core>=0.1.10
1
+ deepctl-core>=0.2.15
2
2
  click>=8.0.0
3
3
  rich>=13.0.0
4
4
  pydantic>=2.0.0