OA0
OA0 是一个探索 AI 的社区
现在注册
已注册用户请  登录
OA0  ›  代码  ›  Kokoro-FastAPI — 轻量好用的开源 TTS 服务封装

Kokoro-FastAPI — 轻量好用的开源 TTS 服务封装

 
  kaleidoscope ·  2026-10-27 11:00:26 · 31 次点击  · 0 条评论  

FastKoko: 容器化 Kokoro-82M TTS,兼容 OpenAI 的 API

remsky%2FKokoro-FastAPI | Trendshift

更新日志
覆盖率 CI

Kokoro
Misaki
模型测试提交

在 Spaces 上体验 拉取量

Kokoro-82M 文本转语音模型的 Docker 化 FastAPI 封装。几分钟即可生成数小时的高质量语音。

  • 兼容 OpenAI 的语音端点,支持多语言
  • 英语(美式/英式)、西班牙语、法语、印地语、意大利语、日语、巴西葡萄牙语、普通话
  • 通过 Inno Clone-Tuner 自定义语音包
  • 可选集成 WebUI;支持长文本逐句跟读
  • 内联多说话人生成与声音混合 + 加权组合别名,支持 SSML
  • 按词或按块生成带时间戳的字幕
  • 音素端点:从文本生成音素,或从音素生成音频
  • 预构建多平台镜像
  • CPU 和 NVIDIA GPU(CUDA):linux/amd64 + linux/arm64
  • AMD GPU(ROCm,实验性):仅 linux/amd64
  • 通过 UV 直接运行时支持 Apple Silicon(MPS)(无需镜像)

集成与指南

Ask DeepWiki Ask CodeWiki

Helm Chart DigitalOcean SillyTavern
OpenWebUI

🌐 将 Kokoro-FastAPI 作为后端使用的社区项目 - Home Assistant - [wyoming_openai](https://github.com/roryeckel/wyoming_openai)、[openai_tts](https://github.com/sfortis/openai_tts)、[Kokoro-TTS](https://github.com/beecho01/Kokoro-TTS)、[Kokoro-FastAPI App](https://github.com/YaannnTech/HA-Apps/tree/main/kokoro_fastapi) - 应用商店与模板 - [Umbrel](https://github.com/getumbrel/umbrel-apps/tree/master/kokoro)、[Unraid Apps](https://github.com/nwithan8/unraid_templates)、[GPUStack](https://github.com/gpustack/gpustack)、[jetson-containers](https://github.com/dusty-nv/jetson-containers/tree/master/packages/speech/kokoro-tts) - 阅读器与有声书 - [openreader](https://github.com/richardr1126/openreader)、[epub_to_audiobook](https://github.com/p0n1/epub_to_audiobook)、[audiobook-creator](https://github.com/prakharsr/audiobook-creator)、[Zotero-OpenReader](https://github.com/xujialiu/Zotero-OpenReader) - 助手与智能体 - [xiaozhi-esp32-server](https://github.com/xinnan-tech/xiaozhi-esp32-server)、[call-me](https://github.com/ZeframLou/call-me)、[agent-cli](https://github.com/basnijholt/agent-cli)、[voice-chat-ai](https://github.com/bigsk1/voice-chat-ai) - 浏览器 - [kokoro-extension](https://github.com/Fooftilly/kokoro-extension)、[customtts](https://github.com/BassGaming/customtts)

快速开始

最快启动(docker run) 预构建的多架构镜像,已内置模型。 `:latest` 可用,但建议固定到发布标签以获得稳定体验。 **无 GPU**(笔记本、纯 CPU 服务器)
docker run -p 8880:8880 ghcr.io/remsky/kokoro-fastapi-cpu:latest
**NVIDIA**(GTX 900 系列至 RTX 40;内置 cu126)
docker run --gpus all -p 8880:8880 ghcr.io/remsky/kokoro-fastapi-gpu:latest
**NVIDIA RTX 50 系列 / Blackwell**(内置 cu128)
docker run --gpus all -p 8880:8880 ghcr.io/remsky/kokoro-fastapi-gpu:latest-cu128
**NVIDIA arm64**(Jetson、GH200;同一标签,内置 cu129)
docker run --gpus all -p 8880:8880 ghcr.io/remsky/kokoro-fastapi-gpu:latest
**AMD GPU**(ROCm,实验性,仅 x86_64)
docker run --device=/dev/kfd --device=/dev/dri -p 8880:8880 ghcr.io/remsky/kokoro-fastapi-rocm:latest
**Apple Silicon**(原生 MPS 克隆;CPU 镜像同样可用)
./start-gpu_mac.sh
`gpu:latest` 与 `gpu:latest-cu126` 为同一镜像。通过环境变量进行配置,详见[配置指南](docs/configuration.md)。
快速开始(docker compose) 1. 安装前置依赖,并使用 Docker Compose 启动服务(含 UI 的完整配置): - 安装 [Docker](https://www.docker.com/products/docker-desktop/) - 克隆仓库: ```bash git clone https://github.com/remsky/Kokoro-FastAPI.git cd Kokoro-FastAPI cd docker/gpu # 使用 NVIDIA GPU # 或 cd docker/cpu # 使用 CPU # 或 cd docker/rocm # 使用 AMD GPU(ROCm,实验性,仅 amd64) docker compose up --build # *Apple Silicon (M1/M2/M3) 用户注意: # Docker GPU 镜像仅支持 CUDA,无法在 Apple Silicon 上运行。使用 Docker 时请选择 `docker/cpu`。 # 如需原生 MPS(Apple GPU)加速,请通过 UV 直接运行 `./start-gpu_mac.sh`。 cd ../.. # 回到仓库根目录以使用下方路径 # 模型会自动下载;如需要也可手动下载: python docker/scripts/download_model.py --output api/src/models/v1_0 ``` [配置指南](docs/configuration.md)涵盖镜像与构建、卷挂载以及环境变量。
直接运行(通过 uv) 1. 安装前置依赖: - 安装 [astral-uv](https://docs.astral.sh/uv/) - 如需在遇到未知单词/发音时作为后备,请在系统中安装 [espeak-ng](https://github.com/espeak-ng/espeak-ng)。上游库可能会尝试处理,但效果不一。 - 克隆仓库: ```bash git clone https://github.com/remsky/Kokoro-FastAPI.git cd Kokoro-FastAPI ``` 如尚未下载模型,请运行[模型下载脚本](https://github.com/remsky/Kokoro-FastAPI/blob/master/docker/scripts/download_model.py) 通过 UV 直接启动(带热重载) Linux 和 macOS ```bash ./start-cpu.sh OR ./start-gpu.sh ``` Windows ```powershell .\start-cpu.ps1 OR .\start-gpu.ps1 ```
已启动? 作为兼容 OpenAI 的语音端点本地运行
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8880/v1", api_key="not-needed"
)

with client.audio.speech.with_streaming_response.create(
    model="kokoro",
    voice="af_sky+af_bella", #单个或多个语音包组合
    input="Hello world!"
  ) as response:
      response.stream_to_file("output.mp3")
- API 可通过 http://localhost:8880 访问 - API 文档:http://localhost:8880/docs - Web 界面:http://localhost:8880/web
Web UI 截图 API 文档

功能特性

核心

兼容 OpenAI 的语音端点
# 使用 OpenAI 的 Python 库
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8880/v1", api_key="not-needed")
response = client.audio.speech.create(
    model="kokoro",  
    voice="af_bella+af_sky", # 参见 /api/src/core/openai_mappings.json 进行自定义
    input="Hello world!",
    response_format="mp3"
)

response.stream_to_file("output.mp3")
或通过 Requests:
import requests


response = requests.get("http://localhost:8880/v1/audio/voices")
voices = [v["id"] for v in response.json()["voices"]]

# 生成音频
response = requests.post(
    "http://localhost:8880/v1/audio/speech",
    json={
        "model": "kokoro",  
        "input": "Hello world!",
        "voice": "af_bella",
        "response_format": "mp3",  # 支持:mp3、wav、opus、flac、aac、pcm
        "speed": 1.0
    }
)

# 保存音频
with open("output.mp3", "wb") as f:
    f.write(response.content)
快速测试(在另一个终端运行):
python examples/assorted_checks/test_openai/test_openai_tts.py # 测试 OpenAI 兼容性
python examples/assorted_checks/test_voices/test_all_voices.py # 测试所有可用音色
流式支持
# 兼容 OpenAI 的流式
from openai import OpenAI
client = OpenAI(
    base_url="http://localhost:8880/v1", api_key="not-needed")

# 流式写入文件
with client.audio.speech.with_streaming_response.create(
    model="kokoro",
    voice="af_bella",
    input="Hello world!"
) as response:
    response.stream_to_file("output.mp3")

# 流式播放到扬声器(需要 PyAudio)
import pyaudio
player = pyaudio.PyAudio().open(
    format=pyaudio.paInt16, 
    channels=1, 
    rate=24000, 
    output=True
)

with client.audio.speech.with_streaming_response.create(
    model="kokoro",
    voice="af_bella",
    response_format="pcm",
    input="Hello world!"
) as response:
    for chunk in response.iter_bytes(chunk_size=1024):
        player.write(chunk)
或通过 requests:
import requests

response = requests.post(
    "http://localhost:8880/v1/audio/speech",
    json={
        "input": "Hello world!",
        "voice": "af_bella",
        "response_format": "pcm"
    },
    stream=True
)

for chunk in response.iter_content(chunk_size=1024):
    if chunk:
        # 处理流式分块
        pass

GPU 首 token 时间线 CPU 首 token 时间线

关键流式指标: - 首 token 延迟 @ 分块大小 - ~300ms(GPU)@ 400 - ~3500ms(CPU)@ 200(较旧的 i7) - ~<1s(CPU)@ 200(M3 Pro) - 可调节的分块设置用于实时播放 *注意:分块越小,语调伪影可能越明显*
多种输出音频格式 - mp3 - wav - opus - flac - aac - pcm

音频格式对比

音色

音色组合 - 基于比例的加权音色组合(例如 `af_bella(2)+af_heart(1)` 表示 67%/33% 混合) - 比例会自动归一化为总和 100% - 可在任意端点通过括号添加权重 - 保存生成的语音包以供将来使用 组合音色并生成音频:
import requests
response = requests.get("http://localhost:8880/v1/audio/voices")
voices = [v["id"] for v in response.json()["voices"]]

# 加权音色组合(67%/33% 混合)
response = requests.post(
    "http://localhost:8880/v1/audio/speech",
    json={
        "input": "Hello world!",
        "voice": "af_bella(2)+af_sky(1)",  # 2:1 比例 = 67%/33%
        "response_format": "mp3"
    }
)

# 将组合音色下载为 .pt 文件
response = requests.post(
    "http://localhost:8880/v1/audio/voices/combine",
    json="af_bella(2)+af_sky(1)"  # 2:1 比例 = 67%/33%
)

# 保存 .pt 文件
with open("combined_voice.pt", "wb") as f:
    f.write(response.content)

# 使用下载的音色文件
response = requests.post(
    "http://localhost:8880/v1/audio/speech",
    json={
        "input": "Hello world!",
        "voice": "combined_voice",  # 使用保存的音色文件
        "response_format": "mp3"
    }
)

音色分析对比

音色别名 加权混合表达式会变得很长。`voice_aliases` 可按请求将短名称映射到混合表达式,适用于 `voice` 字段和 `[voice:...]` 标签: - 别名优先进行不区分大小写匹配(建议保持小写以避免不一致)。 - 指向不存在音色的别名会返回 400。 - Web UI 的 cast 导出采用相同格式,例如 `{"voice_aliases": {...}}`;可直接用于 API 调用。
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8880/v1", api_key="not-needed")

client.audio.speech.create(
    model="kokoro",
    voice="narrator",
    input="[voice:narrator] Once upon a time. [voice:villain] Never!",
    extra_body={
        "allow_voice_tags": True,
        "voice_aliases": {"narrator": "af_bella(2)+af_sky", "villain": "am_michael"},
    },
)
或
curl -X POST http://localhost:8880/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kokoro",
    "voice": "narrator",
    "input": "[voice:narrator] Once upon a time. [voice:villain] Never!",
    "allow_voice_tags": true,
    "voice_aliases": {"narrator": "af_bella(2)+af_sky", "villain": "am_michael"},
    "response_format": "mp3"
  }' --output aliased.mp3
音色调校(参考片段)🧪 `POST /dev/tune` 接收一位英语说话人 3 至 30 秒的片段,并通过 [inno-kokoro](https://github.com/remsky/inno-kokoro) 使用向其调校过的音色进行说话。这是调校器而非克隆器:预期得到相近的风格,而非完全匹配。请仅在你拥有使用权限的情况下调校音色。
curl -s http://localhost:8880/dev/tune -F audio=@ref.wav -F 'request={"input":"Hello there."}' -o out.mp3
curl -s http://localhost:8880/dev/tune -F audio=@ref.wav -F return_voice_pack=true -o ref.pt
curl -s http://localhost:8880/dev/tune -F audio=@ref.wav -F save_voice=am_ref   # 保存 am_ref_tuned,需要 ALLOW_LOCAL_VOICE_SAVING=true
- `request` 为除 `voice` 外的 `/v1/audio/speech` 请求体;语音包仅在响应期间存在,除非通过 `save_voice` 保留至 `VOICES_DIR` - 名称沿用现有语言前缀模式(`am_`、`bf_`、`ax_` 等),保存的音色会加上 `_tuned` 后缀。内置调校音色以 `_inno` 结尾 - 可调参数:`prosody_head`(默认开启)、`fmax` 音高上限(Hz,默认自动) - 默认关闭: - `ENABLE_INNO_TUNER=true` 启用并带一个 Web 播放器标签页 - `ALLOW_LOCAL_VOICE_SAVING=true` 允许将其保存到运行中的服务器 - 完整参考见 [docs/inno-tune.md](docs/inno-tune.md)
多说话人 / 对话 - `[voice:...]` 标签可在任意接受 `input` 的位置内联切换说话人 - `/v1/audio/speech` 需按请求设置 `allow_voice_tags: true`;`/dev/dialogue` 默认允许 - `ENABLE_VOICE_TAGS=false` 在服务端全局禁用:该参数会被拒绝,`/dev/dialogue` 返回 403
curl -X POST http://localhost:8880/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kokoro",
    "voice": "af_heart",
    "input": "The narrator opens. [voice:af_bella] Did it land? [pause:0.3s] [voice:am_michael] It did.",
    "allow_voice_tags": true,
    "response_format": "mp3"
  }' --output dialogue.mp3
使用官方 OpenAI 客户端时,通过 `extra_body` 传入参数:
client.audio.speech.create(
    model="kokoro",
    voice="af_jadzia",
    input="The narrator opens. [voice:af_bella] Did it land?",
    extra_body={"allow_voice_tags": True},
)
`POST /dev/dialogue` 使用结构化轮次,并可通过参数控制 `pause_between_turns`。
curl -X POST http://localhost:8880/dev/dialogue \
  -H "Content-Type: application/json" \
  -d '{
    "turns": [
      {"voice": "af_bella", "text": "Did the multi speaker support land?"},
      {"voice": "am_michael", "text": "It did. Turns switch voices inline."}
    ],
    "pause_between_turns": 0.4,
    "response_format": "mp3"
  }' --output dialogue.mp3
说明: - 首个标签之前的文本使用请求的 `voice`/默认值。 - 每位说话人根据音色前缀保留各自的语言处理流程。显式 `lang_code` 会覆盖所有说话人。 - 连续使用同一音色的轮次会自动合并。 - 标签可接受上文**音色别名**中的短名称,而非完整的加权混合表达式。
说话人轮次变短时相对单音色基线的吞吐量 两种音色变化频率下随生成增长的吞吐量
音色数量对生成速度影响极小。但对于持续切换,如果每位说话人少于约 2 句,分块需求会拖慢生成。这仍是固定成本,不会随文本增长而累积。可使用 `examples/assorted_checks/test_dialogue/` 重新生成。

文本控制

内联控制标记 可在 `input` 文本中嵌入四种标记,由服务端解析(API、WebUI 或任何客户端): - **停顿**:`[pause:1.5s]` 插入相应时长的静音。必须严格为此形式(冒号、结尾 `s`、不区分大小写)。`[pause=1.5]` 和 `[PAUSE 1.0]` 不会被识别,会被朗读出来。 - **发音**:`[Worcester](/wˈʊstər/)` 使用斜杠间的 IPA 代替该词发音。仅英语;可用 `/dev/phonemize` 查找 IPA。 - **音色**:`[voice:am_michael]` 切换后续内容的说话人。 - 需按请求设置 `allow_voice_tags: true`,且服务端 `ENABLE_VOICE_TAGS` 开启(默认开启)。否则标签会被原样朗读。 - 接受与 `voice` 参数相同的组合语法(`[voice:af_bella(2)+af_sky]`), - 可在 `voice_aliases` 中定义短名称/别名, - 未知值返回 400。 - **速率**:`[rate:1.5]` 缩放下一位说话人的语速,直到下一个速率标记或音色切换;`[rate:1.0]` 恢复。叠加在请求 `speed` 之上,限制在 0.25-4.0。门控条件与音色标记相同。 - 音色别名可携带自然语速:`{"grandpa": {"voice": "am_michael", "rate": 0.8}}` 在别名说话时应用该速率,无论作为 `voice` 参数还是标签。适用于语速过快或过慢的音色,以及同一音色的命名预设(`narrator_fast`、`narrator_slow`)。 - 速率属于说话的音色。`[rate:]` 标签缩放说话人校准后的语速而非替换,因此被调至 `0.8` 的别名在 `[rate:1.1]` 下仍按比例更慢。每个 `[voice:...]` 标签都会重置为新音色自身的速率,因此校准过的说话人无法把语速拖到下一个说话人身上。如需整段请求的语速,请使用 `speed`。
The city of [Worcester](/wˈʊstər/) is easy. [pause:1s] See?
SSML 输入 🧪 在 `/v1/audio/speech` 或 `/dev/captioned_speech` 上同时发送 `ssml: true` 与 `allow_voice_tags: true`,即可一次调用完成翻译与朗读。两个标志都需要,因为翻译会生成 `[voice:]` 与 `[rate:]` 片段,否则会被朗读出来;仅 `ssml` 会返回 400。
{
  "model": "kokoro",
  "input": "<speak>Hi<break time=\"750ms\"/>there</speak>",
  "voice": "af_bella",
  "allow_voice_tags": true,
  "ssml": true
}
当你希望返回标记文本而非音频,或在合成前检查时,`POST /dev/ssml` 可单独进行翻译。发送 `text`,如语音请求使用了 `voice` 也一并发送,然后用 `allow_voice_tags: true` 将结果传回。无 `voice` 时,``/`` 会被剥离但保留内容。 - `` 变为 `[pause:0.75s]`。使用 `strength=` 代替 `time=` 时:none/x-weak 0s、weak 0.25s、medium 0.5s、strong 1s、x-strong 1.5s - `` 变为 `[voice:am_michael]`,在闭合标签处恢复 - `` 变为 `[rate:0.75]`,也接受 `80%` 或 `1.2`。在请求 `speed` 之上缩放说话人的语速,限制 0.25-4.0,随后恢复。`pitch`/`volume` 被忽略 - `Worcester` 变为 `[Worcester](/wˈʊstər/)`,仅支持 IPA 与英语 - `WWW` 朗读别名 - `
自然边界检测 - 在句子边界处自动切分与拼接 - 减少伪影,并允许从按约 30 秒配置的基础模型生成长篇输出 模型每块最多接收 510 个音素化 token,但运行那么长往往会产生「急促」的语音及其他伪影。服务端在此之上增加了自己的分块层,由 `TARGET_MIN_TOKENS`、`TARGET_MAX_TOKENS` 和 `ABSOLUTE_MAX_TOKENS` 设定大小(默认 175、250、450,可通过环境变量设置)。
音素与 Token 路由 将文本转换为音素和/或直接从音素生成音频:
import requests

def get_phonemes(text: str, language: str = "a"):
    """获取输入文本的音素与 token"""
    response = requests.post(
        "http://localhost:8880/dev/phonemize",
        json={"text": text, "language": language}  # "a" 表示美式英语
    )
    response.raise_for_status()
    result = response.json()
    return result["phonemes"], result["tokens"]

def generate_audio_from_phonemes(phonemes: str, voice: str = "af_bella"):
    """从音素生成音频"""
    response = requests.post(
        "http://localhost:8880/dev/generate_from_phonemes",
        json={"phonemes": phonemes, "voice": voice},
        headers={"Accept": "audio/wav"}
    )
    if response.status_code != 200:
        print(f"Error: {response.text}")
        return None
    return response.content

# 示例用法
text = "Hello world!"
try:
    # 文本转音素
    phonemes, tokens = get_phonemes(text)
    print(f"Phonemes: {phonemes}")  # 例如 ðɪs ɪz ˈoʊnli ɐ tˈɛst
    print(f"Tokens: {tokens}")      # 包含起始/结束 token 的 token ID

    # 生成并保存音频
    if audio_bytes := generate_audio_from_phonemes(phonemes):
        with open("speech.wav", "wb") as f:
            f.write(audio_bytes)
        print(f"Generated {len(audio_bytes)} bytes of audio")
except Exception as e:
    print(f"Error: {e}")
示例脚本见 `examples/phoneme_examples/generate_phonemes.py`。

字幕

时间戳(词级) 非流式生成带词级时间戳的音频:
import requests
import base64
import json

response = requests.post(
    "http://localhost:8880/dev/captioned_speech",
    json={
        "model": "kokoro",
        "input": "Hello world!",
        "voice": "af_bella",
        "speed": 1.0,
        "response_format": "mp3",
        "stream": False,
    },
    stream=False
)

with open("output.mp3","wb") as f:

    audio_json=json.loads(response.content)
    chunk_audio=base64.b64decode(audio_json["audio"].encode("utf-8"))
    f.write(chunk_audio)
    print(audio_json["timestamps"])
流式生成带词级时间戳的音频:
import requests
import base64
import json

response = requests.post(
    "http://localhost:8880/dev/captioned_speech",
    json={
        "model": "kokoro",
        "input": "Hello world!",
        "voice": "af_bella",
        "speed": 1.0,
        "response_format": "mp3",
        "stream": True,
    },
    stream=True
)

f=open("output.mp3","wb")
for chunk in response.iter_lines(decode_unicode=True):
    if chunk:
        chunk_json=json.loads(chunk)
        chunk_audio=base64.b64decode(chunk_json["audio"].encode("utf-8"))
        f.write(chunk_audio)
        print(chunk_json["timestamps"])
启用 `"allow_voice_tags": true` 后,每个时间戳还会携带朗读该词所用的 `voice`,多说话人字幕可在客户端无需重新推导切分即可标注。未启用该字段则不存在。
时间戳(流式分块) 设置 `stream`、`return_download_link` 和 `return_timing` 时,响应会携带 `X-Timing-Path` 头,指向按分块计时的 JSON 附属文件。音频正文不变,也不额外计算;这正是 Web UI 跟读功能所依赖的。
response = requests.post(
    "http://localhost:8880/v1/audio/speech",
    json={
        "input": "Hello world! [pause:1s] Again.",
        "voice": "af_bella",
        "stream": True,
        "return_download_link": True,
        "return_timing": True,
    },
    stream=True,
)
audio = b"".join(response.iter_content(1024))

timings = requests.get(f"http://localhost:8880/v1{response.headers['x-timing-path']}").json()
# {"chunks": [{"text": "Hello world!", "start": 0.0, "end": 0.64}, ...]}
- 块级(一个句组,约 10-20 秒音频),非词级。需要词
31 次点击  ∙  0 人收藏  
登录后收藏  
0 条回复
关于 ·  帮助 ·  PING ·  隐私 ·  条款   
OA0 - Omni AI 0 一个探索 AI 的社区
沪ICP备2024103595号-2
耗时 18 ms
Developed with Cursor