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
22 changes: 22 additions & 0 deletions minimax/agent-harness/cli_anything/minimax/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,32 @@ cli-anything-minimax tts --text "Hello, world!" --output hello.mp3
# Use turbo model
cli-anything-minimax tts --text "Fast speech" --model speech-2.8-turbo --output fast.mp3

# Fine-grained voice & audio control
cli-anything-minimax tts --text "Slow and quiet" \
--speed 0.8 --vol 0.5 --pitch -2 \
--sample-rate 44100 --bitrate 256000 --format flac --channel 2 \
--output slow.flac

# List available voices
cli-anything-minimax voices
```

#### TTS options

| Option | Range / Choices | Default | Description |
|--------|-----------------|---------|-------------|
| `--text` / `-t` | (required) | — | Text to synthesize |
| `--model` | model id | `speech-2.8-hd` | TTS model |
| `--voice` | voice id | `English_Graceful_Lady` | Voice preset |
| `--output` / `-o` | path | `output.mp3` | Output audio file |
| `--speed` | 0.5 .. 2.0 | `1.0` | Speech speed multiplier |
| `--vol` | 0.0 .. 10.0 | `1.0` | Volume |
| `--pitch` | -12 .. 12 | `0` | Pitch shift in semitones |
| `--sample-rate` | 8000 / 16000 / 22050 / 24000 / 32000 / 44100 | `32000` | Audio sample rate |
| `--bitrate` | 32000 / 64000 / 128000 / 256000 | `128000` | Audio bitrate |
| `--format` | mp3 / pcm / flac | `mp3` | Output container |
| `--channel` | 1 / 2 | `1` | 1 = mono, 2 = stereo |

### Session & Config

```bash
Expand Down
59 changes: 58 additions & 1 deletion minimax/agent-harness/cli_anything/minimax/minimax_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,59 @@ def on_chunk(chunk_content):
show_default=True,
help="Output audio file path",
)
@click.option(
"--speed",
type=click.FloatRange(0.5, 2.0),
default=1.0,
show_default=True,
help="Speech speed (0.5-2.0).",
)
@click.option(
"--vol",
type=click.FloatRange(0.0, 10.0),
default=1.0,
show_default=True,
help="Volume (0.0-10.0).",
)
@click.option(
"--pitch",
type=click.IntRange(-12, 12),
default=0,
show_default=True,
help="Pitch shift in semitones (-12..12).",
)
@click.option(
"--sample-rate",
type=click.Choice(["8000", "16000", "22050", "24000", "32000", "44100"]),
default="32000",
show_default=True,
help="Audio sample rate.",
)
@click.option(
"--bitrate",
type=click.Choice(["32000", "64000", "128000", "256000"]),
default="128000",
show_default=True,
help="Audio bitrate.",
)
@click.option(
"--format",
"audio_format",
type=click.Choice(["mp3", "pcm", "flac"]),
default="mp3",
show_default=True,
help="Output audio format.",
)
@click.option(
"--channel",
type=click.Choice(["1", "2"]),
default="1",
show_default=True,
help="1=mono, 2=stereo.",
)
@click.pass_context
@handle_error
def tts(ctx, text, model_opt, voice, output_path):
def tts(ctx, text, model_opt, voice, output_path, speed, vol, pitch, sample_rate, bitrate, audio_format, channel):
"""Synthesize text to speech using MiniMax TTS."""
parent_key = ctx.obj.get("api_key") if ctx.obj else None
api_key = get_api_key(parent_key)
Expand All @@ -269,6 +319,13 @@ def tts(ctx, text, model_opt, voice, output_path):
model=model_opt,
voice=voice,
output_path=output_path,
speed=speed,
vol=vol,
pitch=pitch,
sample_rate=int(sample_rate),
bitrate=int(bitrate),
audio_format=audio_format,
channel=int(channel),
)

output_data = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""Tests for the extended tts_synthesize parameters (speed / vol / pitch / audio settings).

The new params were previously hardcoded inside tts_synthesize and only the
backend payload used them. These tests guard the new public surface.
"""

import json
from unittest.mock import patch, MagicMock

from cli_anything.minimax.utils.minimax_backend import tts_synthesize


def _empty_sse_post():
"""Build a requests.post mock that returns an empty SSE stream."""
mock_post = MagicMock()
mock_resp = MagicMock()
mock_resp.status_code = 200
mock_resp.raise_for_status = lambda: None
mock_resp.iter_content.return_value = []
mock_post.return_value = mock_resp
return mock_post


def _captured_payload(mock_post):
return mock_post.call_args.kwargs["json"]


def test_tts_default_voice_audio_settings():
with patch("requests.post", _empty_sse_post()) as mock_post:
tts_synthesize(api_key="key", text="hi")
payload = _captured_payload(mock_post)

assert payload["voice_setting"]["speed"] == 1.0
assert payload["voice_setting"]["vol"] == 1.0
assert payload["voice_setting"]["pitch"] == 0
assert payload["audio_setting"]["sample_rate"] == 32000
assert payload["audio_setting"]["bitrate"] == 128000
assert payload["audio_setting"]["format"] == "mp3"
assert payload["audio_setting"]["channel"] == 1


def test_tts_custom_voice_setting():
with patch("requests.post", _empty_sse_post()) as mock_post:
tts_synthesize(api_key="key", text="hi", speed=1.5, vol=3.0, pitch=4)
payload = _captured_payload(mock_post)

assert payload["voice_setting"]["speed"] == 1.5
assert payload["voice_setting"]["vol"] == 3.0
assert payload["voice_setting"]["pitch"] == 4


def test_tts_custom_audio_setting():
with patch("requests.post", _empty_sse_post()) as mock_post:
tts_synthesize(
api_key="key",
text="hi",
sample_rate=44100,
bitrate=256000,
audio_format="flac",
channel=2,
)
payload = _captured_payload(mock_post)

assert payload["audio_setting"]["sample_rate"] == 44100
assert payload["audio_setting"]["bitrate"] == 256000
assert payload["audio_setting"]["format"] == "flac"
assert payload["audio_setting"]["channel"] == 2


def test_tts_voice_id_propagates():
with patch("requests.post", _empty_sse_post()) as mock_post:
tts_synthesize(
api_key="key", text="hi", voice="English_Lucky_Robot", speed=0.8
)
payload = _captured_payload(mock_post)

assert payload["voice_setting"]["voice_id"] == "English_Lucky_Robot"
assert payload["voice_setting"]["speed"] == 0.8
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,13 @@ def tts_synthesize(
model: str = "speech-2.8-hd",
voice: str = "English_Graceful_Lady",
output_path: Optional[str] = None,
speed: float = 1.0,
vol: float = 1.0,
pitch: int = 0,
sample_rate: int = 32000,
bitrate: int = 128000,
audio_format: str = "mp3",
channel: int = 1,
) -> bytes:
"""Synthesize text to speech using MiniMax TTS API (SSE stream, hex-encoded audio)."""
api_key = _require_api_key(api_key)
Expand All @@ -193,15 +200,15 @@ def tts_synthesize(
"stream": True,
"voice_setting": {
"voice_id": voice,
"speed": 1,
"vol": 1,
"pitch": 0,
"speed": speed,
"vol": vol,
"pitch": pitch,
},
"audio_setting": {
"sample_rate": 32000,
"bitrate": 128000,
"format": "mp3",
"channel": 1,
"sample_rate": sample_rate,
"bitrate": bitrate,
"format": audio_format,
"channel": channel,
},
}
try:
Expand Down
7 changes: 7 additions & 0 deletions skills/cli-anything-minimax/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ cli-anything-minimax tts --text "Hello world" --output hello.mp3
cli-anything-minimax --json chat --prompt "Hello"
```

#### TTS options

`--text` (required) / `--model` / `--voice` / `--output` plus voice and audio
controls: `--speed` (0.5..2.0), `--vol` (0..10), `--pitch` (-12..12),
`--sample-rate` (8k/16k/22.05k/24k/32k/44.1k), `--bitrate` (32k/64k/128k/256k),
`--format` (mp3/pcm/flac), `--channel` (1/2). See `tts --help` for defaults.

## Command Groups

### Chat
Expand Down
Loading