DeepSeek 网页版「朗读」的非官方客户端 + CLI。 点个star谢谢喵
把 DeepSeek 网页上的朗读功能,搬到命令行里。
Node 包 + CLI,命令名:
dstts当前版本:0.2.0
环境要求:Node.js >= 22
运行时零依赖,直接用 Node 自带的 fetch 和 WebSocket。
GitHub · 个人主页 / 打赏 · @Eyeing0721
# 看音色
dstts voices
# 下载试听
dstts demo mira -o 试听.mp3
# 检查账号
dstts probe
# 直接念一段文字
dstts say "今天辛苦了,早点睡。"
# 念已经聊过的某条回答
dstts read --session <id> --message <id>
# PCM 封成 WAV
dstts wav in.pcm out.wav就这么简单。
先看下面的「登录态」。 所有命令都要登录态,一条都不能少 —— 包括
voices和demo。 没设 token 直接跑,只会收到一句缺少登录态。
现在 npm 还没发布,所以先从 GitHub 跑:
# 不用安装,直接跑
npx github:Eyeing0721/deepseek-tts-api voices也可以全局装:
npm install -g .
dstts voices或者克隆下来:
node bin/dstts.mjs voices需要一个 userToken。
登录 chat.deepseek.com,按 F12 打开 DevTools。
Mac:
Option + Command + I
打开 Console / 控制台,输入:
JSON.parse(localStorage.getItem('userToken')).value拿到的就是 userToken。
它是一串 64 字符的字符串。
Console 不让粘贴的时候,先手打:
allow pasting
回车,再贴上面的代码。
bash / macOS:
export DS_TOKEN='那串东西'Windows PowerShell:
$env:DS_TOKEN = '那串东西'也可以直接:
dstts voices --token <值>所有命令都需要登录态,包括 voices 和 demo。
token 只从 DS_TOKEN 和 --token 读,不写文件,也不打进日志。
目前有这几只:
| ID | 中文名 | 英文名 | 性别 | 描述 | 语言 |
|---|---|---|---|---|---|
mira |
贝壳 | Mira | female | 百变活泼(可以唱歌) | 29 |
echo |
白浪 | Echo | male | 明朗坚定 | 29 |
stella |
海星 | Stella | female | 俏皮甜美 | 10 |
tide |
暗潮 | Tide | male | 低沉浑厚 | 10 |
默认是:
mira
海星和暗潮支持:
ar en id ja ko ms th vi yue zh
还带粤语。
看看现在有哪些音色:
dstts voices想拿 JSON:
dstts voices --json输出大概这样:
音色列表(官方接口 /api/v0/chat/tts/voices)
默认 mira 当前 mira
mira 贝壳 female 百变活泼 29 种语言 试听 29 个 (默认)
echo 白浪 male 明朗坚定 29 种语言 试听 29 个
stella 海星 female 俏皮甜美 10 种语言 试听 10 个
tide 暗潮 male 低沉浑厚 10 种语言 试听 10 个
想先听听某个音色长什么样:
dstts demo mira -o 试听.mp3指定语言:
dstts demo tide --lang yue -o 粤语试听.mp3不写 -o 的话,默认文件名:
demo-<音色>-<语言>.mp3
先测一下账号这边能不能正常走:
dstts probe也可以带上会话和消息:
dstts probe --session <id> --message <id>会依次检查音色、票据和 WebSocket,然后给结果。
dstts probe --json还能直接拿结构化结果。
最适合日常玩的就是这个。
dstts say "今天天气不错,适合睡觉。"换音色:
dstts say "念这句" --voice tide -o tide.wav想把临时会话留下:
dstts say "别删会话" --keep也能调等待时间和 PoW:
dstts say "..." --wait 60000 --max-iterations 5000000额外请求头:
dstts say "..." --header "名字: 值"--header 可以重复传。
默认输出:
ds-say-<时间戳>.wav
DeepSeek 的 TTS 接口只接收:
chat_session_id
message_id
服务端会自己根据这两个 id 找内容。
同时,它只念模型消息,用户自己发的那条消息不能直接拿去合成。
所以 say 会先建一个临时会话,让模型把你给的文字原样复述出来:
请把下面这段文字原样输出一遍,不要翻译、不要总结、不要解释、不要加引号或任何前后缀,一个字都不要改:
<你给的文本>
拿到模型消息的 message_id 后,再交给 TTS。
整个过程:
建会话
→ 解 PoW
→ 发复述提示语
→ 拉 history
→ 拿 message_id
→ 合成
→ 删掉临时会话
所以你写:
dstts say "今天早点睡"它真的会自己走完这一整套,然后给你一个可以直接播放的 WAV。
已经有会话和消息了,就直接读:
dstts read --session <会话id> --message <消息id> -o out.wav换音色:
dstts read --session <id> --message <id> --voice tide --format pcm其中:
session是网页 URL 里的/a/chat/s/<uuid>那一段message是对应消息的message_id
不写 -o:
ds-tts-<message_id>.wav
裸 PCM 套个 WAV 头:
dstts wav in.pcm out.wav默认:
24000 Hz
单声道
16 bit
也可以自己指定:
dstts wav in.pcm out.wav --rate 16000 --channels 2长度对不上 blockAlign 的话会直接报错。
| 选项 | 说明 |
|---|---|
-o, --out <路径> |
输出文件 |
--format <格式> |
pcm(默认)或 opus |
--voice <id> |
切换音色 |
--token <值> |
userToken |
--timeout <ms> |
超时,默认 read=90000 / probe=15000 |
--json |
JSON 输出 |
-h, --help |
帮助 |
--version |
版本 |
真遇到报错,CLI 会直接给中文提示,对着看就好:
| 码 | 名字 | 中文 |
|---|---|---|
0 |
SUCCESS |
— |
1 |
INTERNAL_ERROR |
服务端内部错误 |
2 |
INVALID_INPUT |
请求参数不对(会话 id / 消息 id 大概率是错的) |
3 |
SERVICE_ERROR |
服务端业务异常 |
4 |
QUOTA_EXCEEDED |
朗读额度用完了(今日限额) |
5 |
RATE_LIMIT_REACHED |
请求太频繁,被限流了 |
6 |
NO_CONTENT |
这条消息没有可朗读的正文 |
7 |
UNSUPPORTED_LANGUAGE |
当前语言不支持朗读 |
8 |
VOICE_UNSUPPORTED_LANGUAGE |
这个音色不支持当前语言 |
9 |
NOT_AVAILABLE |
服务端未对该账号放行(NOT_AVAILABLE,灰测/地区限制) |
10 |
RESUME_EXPIRED |
续传票据过期了 |
11 |
FORBIDDEN |
被拒绝(FORBIDDEN) |
12 |
CONTENT_FILTER |
内容被安全过滤挡了 |
另外还有:
auth:
缺少登录态:没有 userToken(DS_TOKEN 环境变量和 --token 都是空的)。取 userToken 的办法见 README 的「上手」一节
以及普通参数错误:
参数错误:不认识的选项:--via
票有效期是 600 秒,而且只能用一次。
所以每次建立 WebSocket 之前,程序都会重新取票。
账号是否能用,由服务端决定。
碰到:
code=9 NOT_AVAILABLE
或者:
code=11 FORBIDDEN
就是当前账号没拿到权限。
format=opus 拿到的是裸 Opus 包,没有 Ogg 容器。
想拿来直接播放,默认用 pcm 就行。它会自动封成 WAV。
每次 say 都会创建一个临时会话。
默认念完就删,--keep 可以留下。
每次合成前都要做一次工作量证明。
一般大约:
300 ~ 900ms
默认最多算:
5000000 次
实测大概是这个感觉:
| 命令 | 耗时 |
|---|---|
dstts voices |
0.22 s |
dstts probe |
0.26 s |
dstts say 短句(11 字 → 1.8s 音频) |
3.2 ~ 4.2 s |
dstts say 长文本(101 字 → 19.0s 音频) |
7.1 s |
npx github:... help 首次运行 |
11.1 s |
短句主要是固定流程占时间。
长一点的文本,整体会快不少。
import { writeFile } from 'node:fs/promises';
import { synthesize, pcmToWav } from 'deepseek-tts-api';
const r = await synthesize({
sessionId: '...',
messageId: '...'
});
await writeFile('out.wav', pcmToWav(r.audio));不传 token 的话,会直接读:
DS_TOKEN
synthesize() 会返回音频、帧信息、票据、会话信息等完整结果。
流式播放也可以:
onChunk: (buf) => play(buf)常用导出:
issueTicket
listVoices
setVoice
resolveDemoUrl
probeProtocol
createSession
deleteSession
fetchMessages
putText
say
pcmToWav
wavHeader
parseWavHeader
DeepSeekTtsError
ErrorCode
codeName
codeHint
VOICES
PCM
FORMATS
TOKEN_ENV
parseArgv
错误统一是:
catch (e) {
e.kind;
e.code;
e.codeName;
e.describe();
}bin/dstts.mjs CLI
src/constants.mjs 端点、音色表、错误码、PCM 参数
src/http.mjs 取票 / 音色列表 / 切音色 / demo
src/frames.mjs WebSocket 帧处理
src/tts.mjs synthesize()
src/wav.mjs PCM → WAV
src/probe.mjs probeProtocol()
src/deepseek-hash.mjs PoW 哈希
src/pow.mjs Challenge / PoW
src/session.mjs 会话与 say()
src/cli-args.mjs 参数解析
src/errors.mjs 错误码与中文提示
src/index.mjs 导出
test/ 测试
docs/PROTOCOL.md 协议细节
MIT
版权:
Eyeing0721
仅供个人学习和技术研究
这个项目折腾了不少细节,觉得有点意思的话,帮忙点个 ⭐ Star。
Star 对作者来说真的很有用,更新的时候也更有动力。
也可以来我的主页逛逛:
喜欢的话,顺手请我喝杯奶茶也好呀。