# Qwen3-TTS 台灣腔語音合成 API

> 把訓練好的台灣腔 Qwen3-TTS LoRA 聲音封裝成 HTTP REST API：送文字、回音檔。
> 服務端自動套台灣繁體前處理（修正破音字／台灣特有讀音，如 滷肉飯、軟體、雨/語=yǔ），送原文即可。
> 本檔供 LLM agent 自動串接；人類用互動文件見下方連結。

## Base
- 對外網址：https://ttls.markkulab.net
- 內網網址：http://192.168.50.57:7866
- 互動文件（Swagger）：https://ttls.markkulab.net/docs
- OpenAPI schema：https://ttls.markkulab.net/openapi.json

## 認證
- 所有 `/v1/*` 端點必帶 HTTP header：`X-API-Key: <金鑰>`（向服務管理者索取）。
- 豁免（免金鑰）：`/`、`/healthz`、`/llms.txt`、`/docs`、`/redoc`、`/openapi.json`。
- 金鑰缺/錯 → 401 `{"detail":"缺少或錯誤的 X-API-Key"}`。請放後端環境變數，勿寫死在前端。

## 可用聲音
9 個 speaker id（GET /v1/speakers 取即時清單與狀態）：
佑希、凡萱、柔洢、渝函、璦廷、立安、詠芯、采芸、雨榛

## 端點
- `GET /healthz` — 健康檢查（免金鑰、不碰 GPU）。`degraded:true` 表示需重啟。
- `GET /v1/engines` — 列出可用合成引擎與預設引擎（qwen3 / cosyvoice3）。單引擎模式下只有目前 LIVE 引擎為 true。
- `GET /v1/speakers` — 列出所有可用聲音（含 ready/training 狀態）。
- `GET /v1/speakers/{id}` — 單一聲音資訊。
- `GET /v1/speakers/{id}/checkpoints` — 該聲音可試聽版本（進階）。
- `POST /v1/tts` — 【主要】文字轉語音。
- `POST /v1/podcast` — 多角色對話一次合成串接成單檔。
- `POST /v1/clone` — 零樣本聲音克隆（上傳參考音檔，不需訓練）。
- `POST /v1/asr` — 語音辨識（音檔轉文字，可算 CER）。
- `POST /v1/convert` / `POST /v1/merge` — 音檔格式轉換 / 多檔串接（純 CPU）。
- `GET /v1/queue` — 列出目前合成佇列（排隊中／合成中／編碼中的工作）。
- `POST /v1/queue/{job_id}/cancel` — 取消工作（排隊中＝完全跳過；合成中＝下一分段邊界停；找不到回 found:false，非 404）。
- `GET /v1/pronunciation` — 取得目前發音修正表（整詞替換 + 多音字台灣讀音）。
- `POST /v1/pronunciation` — 儲存發音修正表並即時生效（body：{phrase_override, tw_reading_rules}）。
- `POST /v1/pronunciation/preview` — 預覽某段文字經發音修正後實際送進模型的版本（body：{text}）。
- `POST /v1/pronunciation/diff` — 文章發音稽核：帶 {fixed_text, asr_text} 比對念歪的字 + 同音字修正建議（純 CPU、單次 ≤20000 字）。
- `GET /v1/stats` — 使用統計。

## POST /v1/tts（主要）
Content-Type: application/json。欄位：
- `speaker` (string, 必填) — 聲音 id，如 "雨榛"。
- `text` (string, 必填) — 要合成的文字，≤10000 字；>1000 字長文自動依句子切段、逐句合成再串接。
- `response_mode` (string, 必填) — "stream"=回音檔 binary；"base64"=回 JSON 含 audio_base64。
- `format` (string, 必填) — mp3/wav/flac/m4a/ogg/aac/opus（無效自動回 mp3）。
- `lora_scale` (float, 必填) — 0 = 預設合併模型（推薦、最穩）；0.2–0.5 = 改用 adapter 並套此強度。勿用 2.0。
- `temperature` (float, 必填) — 預設 0.8（0–1.5，須 > 0）。
- `top_p` (float, 必填) — 預設 0.85。
- `top_k` (float, 必填) — 預設 30。
- `repetition_penalty` (float, 必填) — 預設 1.05（不可為 0）。
- `max_new_tokens` (int, 選填) — 留空=預設 10000（≈14 分鐘，唸到 EOS 自然提早停）；範圍 16–10000。長文切段後每段各自套用。
- `speed` (float, 選填) — 語速 0.5–2.0，保音高，預設 1.0。
- `pitch` (float, 選填) — 音高位移 -12~+12 半音，預設 0。
- `normalize` (bool, 選填) — 響度正規化，預設 false。
- `breath_pause` (int, 選填) — 標點停頓毫秒數，於 。！？，、． 等段句標點後插入靜音模擬換氣。預設 150（啟用）；0=關閉；建議 150–400。
- `space_pause` (int, 選填) — 中文字間空白停頓毫秒數（空白轉頓號處插靜音）。預設 100（比標點短）；0=不插（仍保留頓號軟停頓）。
- `instruct` (string, 選填) — 情緒/風格指令，如 "用生氣憤怒的語氣說"，≤200 字。
- `style_preset` (string, 選填) — reading（朗讀長文）/ conversational（口語對話）/ lively（生動去 AI 味，走 x-vector→聲線略降）/ none。
- `best_of` (int, 選填) — 1–5；同句多次取樣選頻譜最乾淨那次，1=關閉。延遲約 N 倍。
- `use_icl` (bool, 選填) — adapter 是否用 ICL（聲線更像）；留空=用伺服器設定，有 instruct/preset 時自動退 x-vector。
- `seed` (int, 選填) — 隨機種子；固定它→同輸入每次結果一致、長文多段音色更穩。預設 42；傳 null=每次隨機。
- `engine` (string, 選填) — 合成引擎：`qwen3`=Qwen3-TTS（台灣腔 LoRA）、`cosyvoice3`=CosyVoice3（FunAudioLLM 零樣本）。留空=伺服器預設（TTS_DEFAULT_ENGINE，預設 qwen3）。本服務為單引擎模式（一個 process 只跑一個引擎，切換需改 TTS_DEFAULT_ENGINE 後重啟），目前 LIVE 引擎與可用清單見 GET /v1/engines、未啟用引擎回 503。cosyvoice3 不支援 lora_scale/checkpoint/use_icl/instruct/style_preset（會被忽略並記在 X-Param-Adjusted）。
- `checkpoint` (string, 選填) — 指定版本，正式合成請留空。

### CosyVoice3 細粒度標記（fine-grained tags，僅 engine=cosyvoice3）
直接把標記內嵌在 `text` 裡，CosyVoice3 會在該處表現對應的非語言聲音或強調；標記原樣送進模型（台灣腔前處理會自動保護不破壞）。
- 非語言聲音（方括號）：`[laughter]` 笑聲、`[breath]` 呼吸、`[cough]` 咳嗽、`[sigh]` 嘆氣、`[noise]`、`[clucking]`、`[accent]`、`[quick_breath]`、`[hissing]`、`[vocalized-noise]`、`[lipsmack]`、`[mn]`。
- 強調/笑（成對標籤）：`<strong>關鍵字</strong>` 強調字詞、`<laughter>邊笑邊說的內容</laughter>`。
- 範例 text：`他突然[laughter]停下來，因為這是<strong>關鍵</strong>。`
- 注意：效果為機率性、依 speaker / adapter 而定，不保證每次都出現；`engine=qwen3` 不支援（會被當文字唸出亂碼），請勿在 Qwen3 引擎下使用。

超範圍/無效值伺服器自動改用預設（不報錯），並於回應 header `X-Param-Adjusted` 回報。

回應：
- response_mode="stream" → 音檔 binary，Content-Type 依 format；額外資訊在 header：
  X-Sample-Rate、X-Source（checkpoint/adapter）、X-Cache-Hit、X-Audio-Format、
  X-Speed、X-Pitch、X-Breath-Pause、X-Space-Pause、X-Breath-Chunks（呼吸切段數，1=未切）、
  X-Fixed-Text（前處理後實際送入模型的文字，percent-encoded）。
- response_mode="base64" → JSON：{speaker, sample_rate, audio_format, audio_base64,
  fixed_text, duration_sec, cache_hit, source, lora_scale, param_adjustments}。

## 錯誤碼
401 金鑰缺/錯｜404 speaker 不存在｜409 speaker 暫不可用｜422 參數驗證失敗（如 text 超 10000 字）｜503 伺服器忙碌（佇列滿/訓練中/GPU OOM，退避重試）。

## 範例：最小 curl
```
curl -X POST https://ttls.markkulab.net/v1/tts \
  -H "Content-Type: application/json" -H "X-API-Key: <金鑰>" -o out.mp3 \
  -d '{"speaker":"雨榛","text":"今天天氣真好，我們去吃滷肉飯。","response_mode":"stream","format":"mp3","lora_scale":0,"temperature":0.8,"top_p":0.85,"top_k":30,"repetition_penalty":1.05,"max_new_tokens":4096}'
```

## 範例：POST /v1/podcast
```
{
  "turns": [
    {"role":"主持人","text":"歡迎收聽今天的節目。","style_preset":"conversational"},
    {"role":"來賓","text":"今天想聊台灣小吃。","instruct":"用興奮的語氣說"}
  ],
  "voices": {"主持人":"渝函","來賓":"雨榛"},
  "response_mode":"base64","format":"mp3","gap_ms":400,"normalize":true
}
```
回 JSON 含 audio_base64（整段）+ segments（各段資訊）。
全域 `gap_ms`=段間停頓；全域 `breath_pause`(int, 預設 0)=每段台詞「句內」標點停頓 ms；全域 `space_pause`(int, 預設 0)=中文字間空白停頓 ms；兩者每段可用同名欄位覆寫。

## 串接須知
- 首次合成某聲音較慢（CUDA 暖機可能 ~100 秒），之後毫秒～數秒；client timeout 建議 ≥120 秒。
- lora_scale 一般用 0（合併模型、最穩）；要更強聲線/情緒才用 0.2~0.5 走 adapter。
- ≤10000 字可直接送，服務會自動切段逐句合成串接（長文耗時約句數倍、client timeout 設大）；多角色對話用 /v1/podcast。
- 收到 503「佇列已滿」時指數退避重試。
- 用 GET /healthz 做 readiness/liveness 探針（免金鑰）。

完整欄位與線上 Try it out：https://ttls.markkulab.net/docs
