Skip to content
Closed
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Demos with a **Live** link run in your browser at [demos.speechify.ai](https://d
| [`demos/ssml-emotion-tts/`](./demos/ssml-emotion-tts) | TypeScript (SDK) | | Drives emotion, pauses, prosody, emphasis, and pronunciation with SSML in a single POST /v1/audio/speech request. |
| [`demos/web-audio-streaming/`](./demos/web-audio-streaming) | TypeScript (browser) | | Streams Speechify PCM into the browser Web Audio API for low-latency playback, behind a zero-dep proxy that keeps the key server-side. |
| [`demos/deepgram-voice-agent-shim/`](./demos/deepgram-voice-agent-shim) | Go shim + Node | | Points Deepgram Voice Agent at the tts-shims OpenAI-compatible proxy so it speaks with a Speechify voice, key held server-side. |
| [`demos/speechify-tts-cli/`](./demos/speechify-tts-cli) | TypeScript (CLI) | | Generate speech from your terminal: pipe text or a file in, get an MP3 out, with the Speechify API. Voice, model, and format flags included. |
| [`demos/vapi-custom-voice/`](./demos/vapi-custom-voice) | Go shim | | Points Vapi custom voice at the tts-shims Vapi-compatible proxy so it speaks with Speechify raw PCM, key held server-side. |
| [`demos/pipecat-agent-speechify/`](./demos/pipecat-agent-speechify) | Python (Pipecat) | | Real-time voice pipeline using Pipecat with Deepgram STT, Anthropic Claude, and Speechify TTS through SpeechifyTTSService. |
| [`demos/mastra-agent-speechify/`](./demos/mastra-agent-speechify) | TypeScript (Mastra) | | Text-in, speech-out Mastra Agent using an OpenAI LLM for replies and Speechify's simba-3.2 model for TTS via `@mastra/voice-speechify`. |
Expand Down
1 change: 1 addition & 0 deletions demos/speechify-tts-cli/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SPEECHIFY_API_KEY=your_api_key_here
40 changes: 40 additions & 0 deletions demos/speechify-tts-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Text-to-speech CLI

Generate speech from your terminal with the Speechify API — pipe text or a file
in, get an MP3 out. Pairs with the Speechify post *"Text-to-speech from your
terminal: a CLI demo with the Speechify API"*.

## What you get

A single-file Node CLI that calls the Speechify API and writes the audio:

- reads text from `--text`, `--file`, or stdin
- pick the `--voice`, `--model`, and `--format`
- writes the audio to `--output` (or raw bytes to stdout to pipe downstream)

Login-free, zero prompt, drop it in a shell alias or a build step.

## Run it yourself

```bash
cp .env.example .env # paste your key into .env
npm install

# from a string
npm start -- --text "Hello from your terminal" -o hello.mp3

# from a file
npm start -- --file chapter.txt -v lily --model simba-english -o chapter.mp3

# from stdin
cat notes.txt | npm start -- -o notes.mp3
```

Prerequisites: Node 20+, a Speechify API key from
[platform.speechify.ai/api-keys](https://platform.speechify.ai/api-keys).

## Where the code came from

Uses the official `@speechify/api` client — the same `client.audio.speech()`
call used across the [demos repo](../). See the Speechify docs for voice ids and
available `audio_format` values.
6 changes: 6 additions & 0 deletions demos/speechify-tts-cli/demo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"order": 100,
"title": "Text-to-speech CLI",
"stack": "TypeScript (CLI)",
"blurb": "Generate speech from your terminal: pipe text or a file in, get an MP3 out, with the Speechify API. Voice, model, and format flags included."
}
23 changes: 23 additions & 0 deletions demos/speechify-tts-cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "speechify-tts-cli",
"version": "0.1.0",
"private": true,
"type": "module",
"description": "Text-to-speech from your terminal with the Speechify API",
"bin": {
"speechify-tts": "src/index.ts"
},
"scripts": {
"start": "tsx src/index.ts",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@speechify/api": "^3.0.1",
"dotenv": "^16.4.5"
},
"devDependencies": {
"@types/node": "^22.0.0",
"tsx": "^4.19.0",
"typescript": "^5.6.0"
}
}
146 changes: 146 additions & 0 deletions demos/speechify-tts-cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import "dotenv/config";
import fs from "node:fs";
import path from "node:path";
import { SpeechifyClient, SpeechifyError } from "@speechify/api";

type Args = {
text?: string;
file?: string;
voice: string;
model: string;
format: string;
output?: string;
};

const VOICES_HINT =
"george, henry, lily, matilda, simba-english, and any voice_id from platform.speechify.ai";

function parseArgs(argv: string[]): Args {
const out: Args = { voice: "george", model: "simba-english", format: "mp3" };
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
const next = () => {
const v = argv[++i];
if (v === undefined) throw new Error(`Missing value for ${a}`);
return v;
};
switch (a) {
case "--text":
case "-t":
out.text = next();
break;
case "--file":
case "-f":
out.file = next();
break;
case "--voice":
case "-v":
out.voice = next();
break;
case "--model":
case "-m":
out.model = next();
break;
case "--format":
out.format = next();
break;
case "--output":
case "-o":
out.output = next();
break;
case "--help":
case "-h":
printHelp();
process.exit(0);
default:
throw new Error(`Unknown argument: ${a}\nRun with --help for usage.`);
}
}
return out;
}

function printHelp() {
console.log(`speechify-tts — text-to-speech from your terminal

Usage:
speechify-tts --text "Hello world" -o hello.mp3
echo "Read this aloud" | speechify-tts -o out.mp3
speechify-tts --file chapter.txt -v lily -m simba-english

Options:
-t, --text <string> Text to synthesize (ignored if --file given)
-f, --file <path> Read input text from a file
--voice <id> Voice id (default: george)
--model <id> Model id (default: simba-english)
--format <fmt> Output format: mp3, ogg, wav, etc. (default: mp3)
-o, --output <path> Write the audio file (default: stdout, raw bytes)
-h, --help Show this help

Voices include: ${VOICES_HINT}
Requires SPEECHIFY_API_KEY in the environment (copy .env.example to .env).`);
}

async function readStdin(): Promise<string> {
const chunks: Buffer[] = [];
for await (const c of process.stdin) chunks.push(c as Buffer);
return Buffer.concat(chunks).toString("utf8");
}

async function main() {
const args = parseArgs(process.argv.slice(2));

if (!args.file && !args.text && !process.stdin.isTTY) {
args.text = await readStdin();
}
if (!args.file && !args.text) {
printHelp();
process.exit(1);
}

const token = process.env.SPEECHIFY_API_KEY;
if (!token) {
throw new Error("Set SPEECHIFY_API_KEY (copy .env.example to .env).");
}

const input = args.file
? fs.readFileSync(path.resolve(args.file), "utf8")
: (args.text as string);

const client = new SpeechifyClient({ token });

try {
const response = await client.audio.speech({
input,
voice_id: args.voice,
audio_format: args.format as "mp3" | "ogg" | "wav" | "aac" | "pcm",
model: args.model as
| "simba-english"
| "simba-multilingual"
| "simba-3.0"
| "simba-3.2",
});

const audio = Buffer.from(response.audio_data, "base64");

if (args.output) {
fs.writeFileSync(path.resolve(args.output), audio);
console.error(
`Wrote ${args.output} (${audio.length.toLocaleString()} bytes, ` +
`${response.billable_characters_count} billable characters).`,
);
} else {
process.stdout.write(audio);
}
} catch (err) {
if (err instanceof SpeechifyError) {
console.error(err.message);
process.exit(1);
}
throw err;
}
}

main().catch((err) => {
console.error(err instanceof Error ? err.message : err);
process.exit(1);
});
14 changes: 14 additions & 0 deletions demos/speechify-tts-cli/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"noEmit": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
}
4 changes: 2 additions & 2 deletions site/public/index.html

Large diffs are not rendered by default.