
Chanjing Tts Voice Clone
- 187 installs
- 18 repo stars
- Updated March 28, 2026
- chanjing-ai/chan-skills
Synthesizes speech in a cloned voice via the Chanjing TTS API using a user-provided public reference-audio URL.
About
Creates a cloned voice from a publicly accessible reference-audio URL and synthesizes speech with it via the Chanjing TTS API. A developer uses it to generate text-to-speech in a specific provided voice.
- Clones a voice from a user-provided public reference-audio URL
- Creates voice then synthesizes and polls; downloads from returned URLs
Chanjing Tts Voice Clone by the numbers
- 187 all-time installs (skills.sh)
- Ranked #637 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/chanjing-ai/chan-skills --skill chanjing-tts-voice-cloneAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 187 |
|---|---|
| repo stars | ★ 18 |
| Last updated | March 28, 2026 |
| Repository | chanjing-ai/chan-skills ↗ |
What it does
Synthesizes speech in a cloned voice via the Chanjing TTS API using a user-provided public reference-audio URL.
Files
Chanjing TTS Voice Clone
功能说明
基于用户提供的参考音频公网 URL(由蝉镜服务端拉取)创建音色并合成语音;轮询任务并从接口返回 URL 下载结果。脚本不依赖 ffmpeg/ffprobe。凭据、令牌写回与权限见 `manifest.yaml`;`access_token` / `expire_in` 刷新时会覆盖同文件字段,勿将凭证提交版本库。
运行依赖
- python3 与同仓库
scripts/*.py - 无 ffmpeg/ffprobe 门控
环境变量与机器可读声明
- 环境变量键名与说明:`manifest.yaml`(
environment段)及本文 - 变量、凭据、`permissions.userContent`(含参考音公网 URL 由蝉镜拉取):`manifest.yaml`
使用命令
- ClawHub(slug 以注册表为准):
clawhub run chanjing-tts-voice-clone - 本仓库:
python skills/chanjing-tts-voice-clone/scripts/create_voice.py …(见正文 How to Use)
---
登记与审稿(单一事实来源)
主凭据、`expire_in` 写回与覆盖行为、用户参考音 URL 边界等:以 `manifest.yaml` 为准。本篇 How to Use 起为 API 步骤与英文接口说明。
When to Use This Skill
Use this skill when the user needs to generate speech from text, with a user-provided reference voice. The reference audio needs to be provided as a publicly accessible url.
This TTS service supports:
- bilingual Chinese and English
- adjustment of speech rate
- sentence-level timestamp
How to Use This Skill
前置条件(权限验证):执行本 Skill 前,必须先通过 chanjing-credentials-guard 完成 AK/SK 与 Token 校验。脚本与 guard 使用同一套凭证;无凭证时会执行 open_login_page.py 打开注册/登录页。凭据与审稿对表见 `manifest.yaml`。
Security & credentials(引用)
Purpose / Credentials / user-supplied reference URL / downloads:见 `manifest.yaml` 中 `credentials` 与 `clientPermissions`(及合规 `permissions`);勿与本节以下 API 文档重复维护表格。
Chanjing-TTS-Voice-Clone provides an asynchronous speech synthesis API. Hostname for all APIs is: "https://open-api.chanjing.cc". All requests communicate using json. You should use utf-8 to encode and decode text throughout this task.
1. Obtain an access\_token, which is required for subsequent requests 2. Call the Create Voice API, which accepts a url to an audio file as reference voice 3. Poll the Query Voice API until the result is success; keep the voice ID 4. Call the Create Speech Generation Task API, using the voice ID, record the task_id 5. Poll the Query Speech Generation Task Status API until the result is success 6. When the task status is complete, use the corresponding url in API response to download the generated audio file
Get Access Token API
从 ~/.chanjing/credentials.json 读取 app_id 和 secret_key,若无有效 Token 则调用:
POST /open/v1/access_token
Content-Type: application/json请求体(使用本地配置的 app_id、secret_key):
{
"app_id": "<从 credentials.json 读取>",
"secret_key": "<从 credentials.json 读取>"
}Response example:
{
"trace_id": "8ff3fcd57b33566048ef28568c6cee96",
"code": 0,
"msg": "success",
"data": {
"access_token": "1208CuZcV1Vlzj8MxqbO0kd1Wcl4yxwoHl6pYIzvAGoP3DpwmCCa73zmgR5NCrNu",
"expire_in": 1721289220
}
}Response field description:
| First-level Field | Second-level Field | Description |
|---|---|---|
| code | Response status code | |
| msg | Response message | |
| data | Response data | |
| access\_token | Valid for one day, previous token will be invalidated | |
| expire\_in | Token expiration time |
Response status code description:
| code | Description |
|---|---|
| 0 | Success |
| 400 | Parameter format error |
| 40000 | Parameter error |
| 50000 | System internal error |
Create Voice API
Post to the following endpoint to create a voice.
POST /open/v1/create_customised_audio
access_token: {{access_token}}
Content-Type: application/jsonRequest body example:
{
"name": "example",
"url": "https://example.com/abc.mp3"
}Request field description:
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | A name for this voice |
| url | string | Yes | url to the reference audio file, format must be one of mp3, wav or m4a. Supported mime: audio/x-wav, audio/mpeg, audio/m4a, video/mp4. Size must not exceed 100MB. Recommended audio length: 30s-5min |
| model\_type | string | Yes | Use "Cicada3.0-turbo" |
| language | string | No | Either "cn" or "en", default to "cn" |
Response example:
{
"trace_id": "2f0f50951d0bae0a3be3569097305424",
"code": 0,
"msg": "success",
"data": "C-Audio-53e4e53ba1bc40de91ffaa74f20470fc"
}Response field description:
| Field | Description |
|---|---|
| code | Status Code |
| msg | Message |
| data | Voice ID, to be used in following steps |
Response status code description:
| Code | Description |
|---|---|
| 0 | Success |
| 400 | Parameter Format Error |
| 10400 | AccessToken Error |
| 40000 | Parameter Error |
| 40001 | QPS Exceeded |
| 50000 | Internal System Error |
Poll Voice API
Send a GET request to the following endpoint to query whether the voice is ready to be used, voice ID is obtained in the previous step. The polling process may take a few minutes, keep polling until the status indicates the voice is ready.
GET /open/v1/customised_audio?id={{voice_id}}
access_token: {{access_token}}Response example:
{
"trace_id": "7994cedae0f068d1e9e4f4abdf99215b",
"code": 0,
"msg": "success",
"data": {
"id": "C-Audio-53e4e53ba1bc40de91ffaa74f20470fc",
"name": "声音克隆",
"type": "cicada1.0",
"progress": 0,
"audio_path": "",
"err_msg": "不支持的音频格式,请阅读接口文档",
"status": 2
}
}Response field description:
| First-level Field | Second-level Field | Description |
|---|---|---|
| code | Status Code | |
| msg | Response Message | |
| data | ||
| id | Voice ID | |
| progress | Progress: range 0-100 | |
| type | ||
| name | ||
| err\_msg | Error Message | |
| audio\_path | ||
| status | 0-queued; 1-in progress; 2-done; 3-expired; 4-failed; 99-deleted |
Response status code description:
| Code | Description |
|---|---|
| 0 | Success |
| 10400 | AccessToken Error |
| 40000 | Parameter Error |
| 40001 | QPS Exceeded |
| 50000 | Internal System Error |
Create Speech Generation Task API
Post to the following endpoint to submit a speech generation task:
POST /open/v1/create_audio_task
access_token: {{access_token}}
Content-Type: application/jsonRequest body example:
{
"audio_man": "C-Audio-53e4e53ba1bc40de91ffaa74f20470fc",
"speed": 1,
"pitch": 1,
"text": {
"text": "Hello, I am your AI assistant."
}
}Request field description:
| Parameter Name | Type | Nested Key | Required | Example | Description |
|---|---|---|---|---|---|
| audio\_man | string | Yes | C-Audio-53e4e53ba1bc40de91ffaa74f20470fc | Voice ID, obtained from previous step | |
| speed | number | Yes | 1 | Speech rate, range: 0.5 (slow) to 2 (fast) | |
| pitch | number | Yes | 1 | Pitch (always set to 1) | |
| text | object | text | Yes | Hello, I am your Cicada digital human | Rich text, text length limit less than 4,000 characters |
| aigc\_watermark | bool | No | false | Whether to add visible watermark to audio, default is false |
Response field description:
| Field | Description |
|---|---|
| code | Response status code |
| msg | Response message |
| task\_id | Speech synthesis task ID |
Example Response
{
"trace_id": "dd09f123a25b43cf2119a2449daea6de",
"code": 0,
"msg": "success",
"data": {
"task_id": "88f635dd9b8e4a898abb9d4679e0edc8"
}
}Response status code description:
| code | Description |
|---|---|
| 0 | Success |
| 400 | Incoming parameter format error |
| 10400 | AccessToken verification failed |
| 40000 | Parameter error |
| 40001 | Exceeds QPS limit |
| 40002 | Production duration reached limit |
| 50000 | System internal error |
Query Speech Generation Task Status API
Post a request to the following endpoint:
POST /open/v1/audio_task_state
access_token: {{access_token}}
Content-Type: application/jsonRequest body example:
{
"task_id": "88f635dd9b8e4a898abb9d4679e0edc8"
}Request field description:
| Parameter Name | Type | Required | Example | Description |
|---|---|---|---|---|
| task\_id | string | Yes | 88f789dd9b8e4a121abb9d4679e0edc8 | Task ID obtained in previous step |
Response body example:
{
"trace_id": "ab18b14574bbcc31df864099d474080e",
"code": 0,
"msg": "success",
"data": {
"id": "9546a0fb1f0a4ae3b5c7489b77e4a94d",
"type": "tts",
"status": 9,
"text": [
"猫在跌落时能够在空中调整身体,通常能够四脚着地,这种”猫右自己“反射显示了它们惊人的身体协调能力和灵活性。核磁共振成像技术通过利用人体细胞中氢原子的磁性来生成详细的内部图像,为医学诊断提供了重要工具。"
],
"full": {
"url": "https://cy-cds-test-innovation.cds8.cn/chanjing/res/upload/tts/2025-04-08/093a59021d85a72d28a491f21820ece4.wav",
"path": "093a59013d85a72d28a491f21820ece4.wav",
"duration": 18.81
},
"slice": null,
"errMsg": "",
"errReason": "",
"subtitles": [
{
"key": "20c53ff8cce9831a8d9c347263a400a54d72be15",
"start_time": 0,
"end_time": 2.77,
"subtitle": "猫在跌落时能够在空中调整身体"
},
{
"key": "e19f481b6cd2219225fa4ff67836448e054b2271",
"start_time": 2.77,
"end_time": 4.49,
"subtitle": "通常能够四脚着地"
},
{
"key": "140beae4046bd7a99fbe4706295c19aedfeeb843",
"start_time": 4.49,
"end_time": 5.73,
"subtitle": "这种,猫右自己"
},
{
"key": "e851881271876ab5a90f4be754fde2dc6b5498fd",
"start_time": 5.73,
"end_time": 7.97,
"subtitle": "反射显示了它们惊人的身体"
},
{
"key": "fbb0b4138bad189b9fc02669fe1f95116e9991b4",
"start_time": 7.97,
"end_time": 9.45,
"subtitle": "协调能力和灵活性"
},
{
"key": "f73404d135feaf84dd8fbea13af32eac847ac26d",
"start_time": 9.45,
"end_time": 12.49,
"subtitle": "核磁共振成像技术通过利用人体"
},
{
"key": "e18827931223962e477b14b2b8046947039ac222",
"start_time": 12.49,
"end_time": 14.77,
"subtitle": "细胞中氢原子的磁性来生成"
},
{
"key": "d137bf2b0c8b7a39e3f6753b7cf5d92bd877d2d9",
"start_time": 14.77,
"end_time": 15.97,
"subtitle": "详细的内部图像"
},
{
"key": "0773911ae0dbaa763a64352abdb6bdac3ff8f149",
"start_time": 15.97,
"end_time": 18.41,
"subtitle": "为医学诊断提供了重要工具"
}
]
}
}Response field description:
| First-level Field | Second-level Field | Third-level Field | Description |
|---|---|---|---|
| code | Response status code | ||
| msg | Response message | ||
| data | id | Audio ID | |
| type | Speech type | ||
| status | Status: 1 - in progress, 9 - done | ||
| text | Speech text | ||
| full | url | url to download generated audio file | |
| path | |||
| duration | Audio duration | ||
| slice | |||
| errMsg | Error message | ||
| errReason | Error reason | ||
| subtitles(array type) | key | Subtitle ID | |
| start\_time | Subtitle start time point | ||
| end\_time | Subtitle end time point | ||
| subtitle | Subtitle text |
Response field description:
| Code | Description |
|---|---|
| 0 | Response successful |
| 10400 | AccessToken verification failed |
| 40000 | Parameter error |
| 50000 | System internal error |
Scripts
本 Skill 提供脚本(skills/chanjing-tts-voice-clone/scripts/),与 chanjing-credentials-guard 使用同一配置文件;无 AK/SK 时会执行 `open_login_page.py` 脚本打开注册/登录页。
| 脚本 | 说明 |
|---|---|
create_voice.py | 提交定制声音任务(参考音频 URL),输出 voice_id |
poll_voice.py | 轮询定制声音直到就绪(status=2),输出 voice_id |
create_task.py | 使用定制声音创建 TTS 任务,输出 task_id |
poll_task.py | 轮询 TTS 任务直到完成,输出音频下载 URL |
示例(在项目根或 skill 目录下执行):
# 1. 创建定制声音(参考音频需为公开 URL)
VOICE_ID=$(python skills/chanjing-tts-voice-clone/scripts/create_voice.py --name "我的声音" --url "https://example.com/ref.mp3")
# 2. 轮询直到声音就绪
python skills/chanjing-tts-voice-clone/scripts/poll_voice.py --voice-id "$VOICE_ID"
# 3. 创建 TTS 任务
TASK_ID=$(python skills/chanjing-tts-voice-clone/scripts/create_task.py --audio-man "$VOICE_ID" --text "Hello, I am your AI assistant.")
# 4. 轮询到完成,得到音频下载链接
python skills/chanjing-tts-voice-clone/scripts/poll_task.py --task-id "$TASK_ID"# 合规:根目录 合规规则.md §1–§2
name: chanjing-tts-voice-clone
version: 0.1.0
vendor: chanjing
runtime:
interpreter: python3
dependencies: []
env:
required: []
optional:
- CHANJING_OPENAPI_CREDENTIALS_DIR
- CHANJING_OPENAPI_BASE_URL
permissions:
network_mode: allowlist
allowed_hosts:
- open-api.chanjing.cc
- www.chanjing.cc
filesystem:
read_roots:
- "${WORKSPACE_ROOT}"
- "${SKILL_DIR}"
- "${CHANJING_OPENAPI_CREDENTIALS_DIR}"
write_roots:
- "${WORKSPACE_ROOT}"
- "${CHANJING_OPENAPI_CREDENTIALS_DIR}"
allowed_commands:
- python3
schemaVersion: 1
skill:
id: chanjing-tts-voice-clone
author: chan-skills
category: 媒体处理
tags:
- TTS
- 声音克隆
- ChanjingAPI
- 蝉镜
summary: >-
参考音频公网 URL 创建音色并 TTS 合成;轮询任务并从接口 URL 下载结果。
skillDoc: SKILL.md
siblingSkills:
- chanjing-credentials-guard
environment:
variables:
- name: CHANJING_OPENAPI_CREDENTIALS_DIR
required: false
description: 存放 credentials.json 的目录(兼容 CHANJING_CONFIG_DIR),默认 ~/.chanjing
- name: CHANJING_OPENAPI_BASE_URL
required: false
description: Open API 基址(兼容 CHANJING_API_BASE),默认 https://open-api.chanjing.cc
credentials:
model: credentials_json
defaultPath: "~/.chanjing/credentials.json"
directoryEnv: CHANJING_OPENAPI_CREDENTIALS_DIR
fileName: credentials.json
sensitiveFields:
- app_id
- secret_key
- access_token
- expire_in
persistAccessTokenOnDisk: true
primaryEnvIntentionallyOmitted: true
doNotCommitToVcs:
- credentials.json
clientPermissions:
network:
httpsOutbound: true
documentedHosts:
- open-api.chanjing.cc
filesystem:
read:
- "${CHANJING_OPENAPI_CREDENTIALS_DIR or CHANJING_CONFIG_DIR or ~/.chanjing}/credentials.json"
write:
- "${CHANJING_OPENAPI_CREDENTIALS_DIR or CHANJING_CONFIG_DIR or ~/.chanjing}/credentials.json"
browser:
mayOpenForAuth: true
documentedHosts:
- www.chanjing.cc
subprocess:
allowedPatterns:
- python3
userContent:
mayDownloadFromApiResponseUrls: true
userSuppliedPublicReferenceAudioUrlFetchedByChanjingServers: true
metadata:
openclaw:
homepage: https://doc.chanjing.cc
agentPolicy:
alwaysSkill: false
modifiesOtherSkillsOrGlobalAgent: false
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
import subprocess
import sys
import time
import urllib.request
from pathlib import Path
_DEFAULT_OPENAPI_BASE = "https://open-api.chanjing.cc"
def credentials_config_dir() -> Path:
raw = os.environ.get("CHANJING_OPENAPI_CREDENTIALS_DIR") or os.environ.get("CHANJING_CONFIG_DIR")
return Path(raw).expanduser() if raw else Path.home() / ".chanjing"
def openapi_base_url() -> str:
return (
os.environ.get("CHANJING_OPENAPI_BASE_URL")
or os.environ.get("CHANJING_API_BASE")
or _DEFAULT_OPENAPI_BASE
).rstrip("/")
CONFIG_DIR = credentials_config_dir()
CONFIG_FILE = CONFIG_DIR / "credentials.json"
API_BASE = openapi_base_url()
BUFFER_SECONDS = 300
LOGIN_URL = "https://www.chanjing.cc/openapi/login"
NO_CREDENTIALS_MSG = """已在浏览器打开蝉镜登录/注册页。
获取秘钥后请执行:
python skills/chanjing-credentials-guard/scripts/chanjing_config.py --ak <你的app_id> --sk <你的secret_key>
设置完毕后请重新执行您之前的操作。"""
def _run_open_login_page():
"""执行 credentials-guard 的 open_login_page.py,在默认浏览器打开注册/登录页。"""
try:
skills_dir = Path(__file__).resolve().parent.parent.parent
script = skills_dir / "chanjing-credentials-guard" / "scripts" / "open_login_page.py"
if script.exists():
subprocess.run([sys.executable, str(script)], check=False, timeout=5)
else:
import webbrowser
webbrowser.open(LOGIN_URL)
except Exception:
try:
import webbrowser
webbrowser.open(LOGIN_URL)
except Exception:
pass
def read_config():
if CONFIG_FILE.exists():
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
return json.load(f)
return {}
def write_config(data):
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
def get_token():
"""返回 (token, None) 或 (None, error_msg)。"""
data = read_config()
app_id = (data.get("app_id") or "").strip()
secret_key = (data.get("secret_key") or "").strip()
if not app_id or not secret_key:
_run_open_login_page()
return None, NO_CREDENTIALS_MSG
now = int(time.time())
token = data.get("access_token")
expire_in = data.get("expire_in")
try:
expire_in = int(expire_in) if expire_in is not None else 0
except (ValueError, TypeError):
expire_in = 0
if token and expire_in > now + BUFFER_SECONDS:
return token, None
url = API_BASE + "/open/v1/access_token"
req = urllib.request.Request(
url,
data=json.dumps({"app_id": app_id, "secret_key": secret_key}).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
body = json.loads(resp.read().decode("utf-8"))
except Exception as e:
return None, str(e)
if body.get("code") != 0:
return None, body.get("msg", "获取 Token 失败")
d = body.get("data", {})
new_token = d.get("access_token")
if not new_token:
return None, "API 返回无 access_token"
data["access_token"] = new_token
data["expire_in"] = d.get("expire_in")
write_config(data)
return new_token, None
#!/usr/bin/env python3
"""
使用定制声音创建 TTS 语音合成任务。voice_id 需先通过 create_voice + poll_voice 获得。
与 chanjing-credentials-guard 使用同一配置文件;无 AK/SK 时执行 open_login_page.py 打开登录页。
用法: create_task.py --audio-man <voice_id> --text "要合成的文本" [--speed 1] [--pitch 1] [--aigc-watermark]
输出: task_id(一行)或错误到 stderr
"""
import argparse
import json
import sys
import urllib.request
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from _auth import get_token
API_BASE = (__import__("os").environ.get("CHANJING_OPENAPI_BASE_URL") or __import__("os").environ.get("CHANJING_API_BASE") or "https://open-api.chanjing.cc").rstrip("/")
def main():
parser = argparse.ArgumentParser(description="使用定制声音创建 TTS 任务")
parser.add_argument("--audio-man", required=True, help="定制声音 ID(来自 poll_voice)")
parser.add_argument("--text", required=True, help="要合成的文本,不超过 4000 字")
parser.add_argument("--speed", type=float, default=1, help="语速 0.5-2,默认 1")
parser.add_argument("--pitch", type=float, default=1, help="语调,默认 1")
parser.add_argument("--aigc-watermark", action="store_true", help="是否添加 AIGC 水印")
args = parser.parse_args()
if len(args.text) > 4000:
print("文本长度不能超过 4000 字符", file=sys.stderr)
sys.exit(1)
body = {
"audio_man": args.audio_man.strip(),
"speed": args.speed,
"pitch": args.pitch,
"text": {"text": args.text},
"aigc_watermark": args.aigc_watermark,
}
token, err = get_token()
if err:
print(err, file=sys.stderr)
sys.exit(1)
url = f"{API_BASE}/open/v1/create_audio_task"
req = urllib.request.Request(
url,
data=json.dumps(body).encode("utf-8"),
headers={"access_token": token, "Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=30) as resp:
res = json.loads(resp.read().decode("utf-8"))
if res.get("code") != 0:
print(res.get("msg", res), file=sys.stderr)
sys.exit(1)
data = res.get("data", {})
task_id = data.get("task_id")
if not task_id:
print("响应无 task_id", file=sys.stderr)
sys.exit(1)
print(task_id)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
创建定制声音(声音克隆)。需提供参考音频的公开 URL。
与 chanjing-credentials-guard 使用同一配置文件;无 AK/SK 时执行 open_login_page.py 打开登录页。
用法: create_voice.py --name "我的声音" --url "https://example.com/ref.mp3" [--model-type Cicada3.0-turbo] [--language cn]
输出: voice_id(一行)或错误到 stderr
"""
import argparse
import json
import sys
import urllib.request
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from _auth import get_token
API_BASE = (__import__("os").environ.get("CHANJING_OPENAPI_BASE_URL") or __import__("os").environ.get("CHANJING_API_BASE") or "https://open-api.chanjing.cc").rstrip("/")
def main():
parser = argparse.ArgumentParser(description="创建定制声音(参考音频 URL)")
parser.add_argument("--name", required=True, help="声音名称")
parser.add_argument("--url", required=True, help="参考音频的公开 URL(mp3/wav/m4a,建议 30s–5min)")
parser.add_argument("--model-type", default="Cicada3.0-turbo", help="模型类型,默认 Cicada3.0-turbo")
parser.add_argument("--language", default="cn", help="cn 或 en,默认 cn")
args = parser.parse_args()
body = {
"name": args.name,
"url": args.url,
"model_type": args.model_type,
"language": args.language,
}
token, err = get_token()
if err:
print(err, file=sys.stderr)
sys.exit(1)
url = f"{API_BASE}/open/v1/create_customised_audio"
req = urllib.request.Request(
url,
data=json.dumps(body).encode("utf-8"),
headers={"access_token": token, "Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=60) as resp:
res = json.loads(resp.read().decode("utf-8"))
if res.get("code") != 0:
print(res.get("msg", res), file=sys.stderr)
sys.exit(1)
voice_id = res.get("data")
if not voice_id:
print("响应无 data(voice_id)", file=sys.stderr)
sys.exit(1)
print(voice_id)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
轮询 TTS 任务状态,直到完成或失败。status: 1-生成中 9-完成
用法: poll_task.py --task-id <task_id> [--interval 3]
输出: 成功时打印音频下载 URL(data.full.url),失败时打印错误到 stderr 并 exit 1
"""
import argparse
import json
import sys
import time
import urllib.request
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from _auth import get_token
API_BASE = (__import__("os").environ.get("CHANJING_OPENAPI_BASE_URL") or __import__("os").environ.get("CHANJING_API_BASE") or "https://open-api.chanjing.cc").rstrip("/")
def main():
parser = argparse.ArgumentParser(description="轮询 TTS 任务直到完成")
parser.add_argument("--task-id", required=True, help="任务 ID(来自 create_task)")
parser.add_argument("--interval", type=int, default=3, help="轮询间隔秒数,默认 3")
args = parser.parse_args()
token, err = get_token()
if err:
print(err, file=sys.stderr)
sys.exit(1)
url = f"{API_BASE}/open/v1/audio_task_state"
body = json.dumps({"task_id": args.task_id}).encode("utf-8")
while True:
req = urllib.request.Request(
url,
data=body,
headers={"access_token": token, "Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=30) as resp:
res = json.loads(resp.read().decode("utf-8"))
if res.get("code") != 0:
print(res.get("msg", res), file=sys.stderr)
sys.exit(1)
data = res.get("data", {})
status = data.get("status")
if status == 9:
full = data.get("full") or {}
audio_url = full.get("url")
if audio_url:
print(audio_url)
return 0
print("任务完成但无 full.url", file=sys.stderr)
sys.exit(1)
if status not in (1, None):
err_msg = data.get("errMsg") or data.get("errReason") or f"status={status}"
print(f"任务失败: {err_msg}", file=sys.stderr)
sys.exit(1)
time.sleep(args.interval)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
轮询定制声音任务状态,直到完成或失败。status: 0-排队 1-进行中 2-完成 3-过期 4-失败 99-已删除
用法: poll_voice.py --voice-id <voice_id> [--interval 15]
输出: 成功时打印 voice_id(与输入一致),失败时打印错误到 stderr 并 exit 1
"""
import argparse
import json
import sys
import time
import urllib.parse
import urllib.request
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from _auth import get_token
API_BASE = (__import__("os").environ.get("CHANJING_OPENAPI_BASE_URL") or __import__("os").environ.get("CHANJING_API_BASE") or "https://open-api.chanjing.cc").rstrip("/")
def main():
parser = argparse.ArgumentParser(description="轮询定制声音直到就绪")
parser.add_argument("--voice-id", required=True, help="定制声音 ID(来自 create_voice)")
parser.add_argument("--interval", type=int, default=15, help="轮询间隔秒数,默认 15")
args = parser.parse_args()
token, err = get_token()
if err:
print(err, file=sys.stderr)
sys.exit(1)
url = f"{API_BASE}/open/v1/customised_audio?id={urllib.parse.quote(args.voice_id)}"
while True:
req = urllib.request.Request(url, headers={"access_token": token}, method="GET")
with urllib.request.urlopen(req, timeout=30) as resp:
body = json.loads(resp.read().decode("utf-8"))
if body.get("code") != 0:
print(body.get("msg", body), file=sys.stderr)
sys.exit(1)
data = body.get("data", {})
status = data.get("status")
if status == 2:
print(args.voice_id)
return 0
if status in (3, 4, 99):
err_msg = data.get("err_msg") or data.get("errReason") or f"status={status}"
print(f"声音任务失败: {err_msg}", file=sys.stderr)
sys.exit(1)
time.sleep(args.interval)
if __name__ == "__main__":
main()