Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ dg login
# Transcribe an audio file
dg listen recording.wav

# Text-to-speech
dg speak "Hello from Deepgram" -o hello.mp3
# Text-to-speech (Flux TTS by default)
dg speak "Hello from Deepgram" -o hello.wav

# Live microphone transcription
dg listen --mic
Expand Down Expand Up @@ -132,8 +132,9 @@ dg read earnings.txt --sentiment --summarize --topics
### Text-to-speech

```bash
# Stream directly to a player
dg speak "Hello from Deepgram" | ffplay -nodisp -autoexit -
# Stream directly to a player (Flux TTS streams a WAV; -loglevel error hides
# ffmpeg's cosmetic end-of-stream notice)
dg speak "Hello from Deepgram" | ffplay -loglevel error -nodisp -autoexit -
```

### Account & project management
Expand Down Expand Up @@ -173,22 +174,22 @@ cat audio.raw | dg listen --encoding linear16 --sample-rate 16000

Convert text to natural speech. Supports file output and piping.

`aura-*` models use the Speak v1 batch REST API. `flux-*` (Flux TTS) models use
the Speak v2 WebSocket API and stream by default; their raw `linear16` output is
wrapped in a WAV container so it is directly playable.
By default `dg speak` uses Flux TTS (`flux-alexis-en`) — the Speak v2 WebSocket
API, which streams and emits raw audio; its `linear16` output is wrapped in a WAV
container so it is directly playable. Pass an `aura-*` model to use the Speak v1
batch REST API instead, which supports containerized formats like MP3.

```bash
# Aura (v1, batch REST)
dg speak "Welcome to Deepgram" -o welcome.mp3
dg speak --file script.txt -o output.mp3 -m aura-2-luna-en
echo "Hello" | dg speak -o greeting.mp3
dg speak "Stream me" | ffplay -nodisp - # pipe to audio player

# Flux TTS (v2, WebSocket streaming)
dg speak "Hello from Flux" -m flux-alexis-en -o hello.wav
# Flux TTS (v2, WebSocket streaming) — the default
dg speak "Hello from Flux" -o hello.wav
# Piped audio is a streaming WAV; -loglevel error hides ffmpeg's cosmetic
# end-of-stream notice (the audio is complete).
dg speak "Hello from Flux" -m flux-alexis-en | ffplay -loglevel error -nodisp -autoexit -
dg speak "Hello from Flux" | ffplay -loglevel error -nodisp -autoexit -

# Aura (v1, batch REST) — opt in with -m aura-*; needed for MP3 output
dg speak "Welcome to Deepgram" -o welcome.mp3 -m aura-2-asteria-en
dg speak --file script.txt -o output.mp3 -m aura-2-luna-en
echo "Hello" | dg speak -o greeting.mp3 -m aura-2-asteria-en
```

### Text Intelligence
Expand Down Expand Up @@ -292,7 +293,7 @@ export DEEPGRAM_PROJECT_ID="your-project-id"

# Non-interactive usage
dg listen recording.wav
dg speak "Deploy complete" -o notification.mp3
dg speak "Deploy complete" -o notification.mp3 -m aura-2-asteria-en
dg keys --create --comment "ci-key" --scopes member
dg keys --delete KEY_ID --yes
dg read --file report.txt --summarize
Expand Down
39 changes: 21 additions & 18 deletions packages/deepctl-cmd-speak/src/deepctl_cmd_speak/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,26 +162,29 @@ class SpeakCommand(BaseCommand):
ci_friendly = True

examples = [
# Flux TTS is the default (flux-alexis-en) — WebSocket streaming,
# raw audio wrapped in WAV. Piped audio is a streaming WAV (unknown
# length up front), so pass `-loglevel error` to silence ffmpeg's
# cosmetic end-of-stream notice.
'dg speak "Hello world"',
'dg speak "Hello world" -o hello.mp3',
"dg speak --file message.txt -o output.mp3",
'echo "Hello" | dg speak -o hello.mp3',
'dg speak "Hello" | ffplay -nodisp -',
'dg speak "Hello world" -o hello.wav',
"dg speak --file message.txt -o output.wav",
'dg speak "Hello" | ffplay -loglevel error -nodisp -autoexit -',
# Aura (Speak v1, batch REST) — opt in with -m aura-*; needed for
# containerized formats like mp3.
'dg speak "Hello" -m aura-2-asteria-en -o hello.mp3',
'dg speak "Hello" -m aura-2-luna-en -o hello.wav --encoding linear16 --container wav',
# Flux TTS — WebSocket streaming (flux-* models), streaming by default.
# Piped audio is a streaming WAV (unknown length up front), so pass
# `-loglevel error` to silence ffmpeg's cosmetic end-of-stream notice.
'dg speak "Hello from Flux" -m flux-alexis-en -o hello.wav',
'dg speak "Hello from Flux" -m flux-alexis-en | ffplay -loglevel error -nodisp -autoexit -',
'echo "Hello" | dg speak -o hello.mp3 -m aura-2-asteria-en',
]
agent_help = (
"Convert text to speech using Deepgram's TTS API. "
"Text can be provided as an argument, from a file, or piped via stdin. "
"Audio is written to a file (--output) or stdout for piping. "
"aura-* models use Speak v1 (batch REST). flux-* (Flux TTS) models use "
"Speak v2, streaming over WebSocket by default and emitting raw audio; "
"linear16 output is wrapped in a WAV container so it is directly playable. "
"Supports model selection and audio format options."
"By default (flux-* models, e.g. flux-alexis-en) uses Flux TTS / Speak "
"v2, streaming over WebSocket and emitting raw audio; linear16 output is "
"wrapped in a WAV container so it is directly playable. Pass an aura-* "
"model to use Speak v1 (batch REST), which supports containerized "
"formats like mp3. Supports model selection and audio format options."
)

def get_arguments(self) -> list[dict[str, Any]]:
Expand All @@ -201,13 +204,13 @@ def get_arguments(self) -> list[dict[str, Any]]:
{
"names": ["--model", "-m"],
"help": (
"TTS model. aura-* = Speak v1 (REST batch; default "
"aura-2-asteria-en); flux-* = Flux TTS / Speak v2 (WebSocket "
"streaming, e.g. flux-alexis-en)."
"TTS model. flux-* = Flux TTS / Speak v2 (WebSocket "
"streaming; default flux-alexis-en); aura-* = Speak v1 "
"(REST batch, e.g. aura-2-asteria-en)."
),
"type": str,
"is_option": True,
"default": "aura-2-asteria-en",
"default": "flux-alexis-en",
},
{
"names": ["--encoding"],
Expand Down Expand Up @@ -251,7 +254,7 @@ def handle(
) -> BaseResult | None:
text = kwargs.get("text")
output_path = kwargs.get("output")
model = kwargs.get("model") or "aura-2-asteria-en"
model = kwargs.get("model") or "flux-alexis-en"
encoding = kwargs.get("encoding")
container = kwargs.get("container")
sample_rate = kwargs.get("sample_rate")
Expand Down
32 changes: 32 additions & 0 deletions packages/deepctl-cmd-speak/tests/unit/test_speak_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,38 @@ def test_handle_write_to_file(

assert output_file.read_bytes() == b"chunk1chunk2"

@patch("deepctl_cmd_speak.command.sys")
def test_handle_default_model_routes_to_flux(
self, mock_sys, command, mock_config, mock_auth_manager, mock_client, tmp_path
):
"""With no -m, the default is Flux (v2 streaming), not Aura (v1 REST)."""
mock_sys.stdin.isatty.return_value = True
mock_sys.stdout.isatty.return_value = True

pcm = b"\x01\x00\x02\x00" # raw 16-bit PCM
mock_client.speak_text_stream.return_value = iter([pcm])

output_file = tmp_path / "hello.wav"
result = command.handle(
config=mock_config,
auth_manager=mock_auth_manager,
client=mock_client,
text="Hello world",
output=str(output_file),
model=None,
encoding=None,
container=None,
sample_rate=None,
file=None,
)

assert isinstance(result, SpeakResult)
assert result.model == "flux-alexis-en"
# Routed to streaming (v2), not batch REST (v1).
mock_client.speak_text_stream.assert_called_once()
mock_client.speak_text.assert_not_called()
assert output_file.read_bytes()[:4] == b"RIFF"

@patch("deepctl_cmd_speak.command.sys")
def test_handle_flux_streams_and_wraps_wav(
self,
Expand Down
Loading