Qwen Audio TTS (Text-to-Speech) Synthesize natural speech from text using Qwen TTS models. This skill is part of QianWen-AI/qianwen-ai . Skill directory Use this skill's internal files to execute and learn. Load reference files on demand when the default path fails or you need details. | Location | Purpose | |----------|---------| | | Qwen TTS (HTTP API) — qwen3-tts- , qwen-audio-3.0-tts- | | | CosyVoice (WebSocket / HTTP NRT) — requires SDK | | | CosyVoice setup, voices, examples, errors | | | Fallback: curl (standard, instruct, streaming), code generation | | | Text formatting for speech, i…

\n for candidate in candidates:\n m = re.search(pattern, candidate)\n if m:\n agent, skill = m.group(1) or \"default\", m.group(2)\n if len(agent) \u003c= 32 and len(skill) \u003c= 32:\n return json.dumps({\"channel\": \"qianwenai-skill\", \"tags\": {\"t1\": skill, \"t2\": agent}}, separators=(',', ':'))\n return None\n\n\n# ---------------------------------------------------------------------------\n# Section 6: HTTP infrastructure\n# ---------------------------------------------------------------------------\n\n_RETRYABLE_CODES = frozenset({429, 500, 502, 503, 504})\n\n\ndef http_request(\n method: str,\n url: str,\n api_key: str,\n payload: dict[str, Any] | None = None,\n *,\n extra_headers: dict[str, str] | None = None,\n timeout: int = 120,\n retries: int = 2,\n backoff: float = 1.5,\n) -> dict[str, Any]:\n \"\"\"Generic HTTP request with retry and exponential backoff.\n\n Handles JSON serialisation, provider header injection, and retryable\n HTTP codes.\n \"\"\"\n hdrs = get_provider().make_headers(api_key, payload)\n if extra_headers:\n hdrs.update(extra_headers)\n data = json.dumps(payload).encode(\"utf-8\") if payload else None\n last_err = \"\"\n for attempt in range(retries + 1):\n req = urllib.request.Request(url, data=data, headers=hdrs, method=method)\n try:\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n return json.loads(resp.read().decode(\"utf-8\"))\n except urllib.error.HTTPError as exc:\n body = \"\"\n try:\n body = exc.read().decode(\"utf-8\", errors=\"replace\")\n except Exception:\n pass\n last_err = f\"HTTP {exc.code}: {body[:500]}\"\n if api_key and len(api_key) > 8 and api_key in body:\n last_err = last_err.replace(api_key, mask_key(api_key))\n if exc.code not in _RETRYABLE_CODES or attempt >= retries:\n raise RuntimeError(last_err) from exc\n time.sleep(backoff * (2 ** attempt))\n except urllib.error.URLError as exc:\n last_err = str(exc.reason)\n if attempt >= retries:\n raise RuntimeError(f\"Network error: {last_err}\") from exc\n time.sleep(backoff * (2 ** attempt))\n raise RuntimeError(last_err)\n\n\ndef http_post(\n url: str,\n api_key: str,\n payload: dict[str, Any],\n *,\n timeout: int = 120,\n retries: int = 2,\n backoff: float = 1.5,\n) -> dict[str, Any]:\n \"\"\"Convenience: non-streaming POST, returns parsed JSON.\"\"\"\n return http_request(\n \"POST\", url, api_key, payload,\n timeout=timeout, retries=retries, backoff=backoff,\n )\n\n\ndef _sse_error_message(\n chunk: Any,\n event_type: str,\n api_key: str,\n) -> Optional[str]:\n \"\"\"Return a safe error message for an SSE error payload, if present.\"\"\"\n error_payload = chunk.get(\"error\") if isinstance(chunk, dict) else None\n has_top_level_error = isinstance(chunk, dict) and (\n not chunk.get(\"choices\")\n and chunk.get(\"code\") is not None\n and chunk.get(\"message\") is not None\n )\n if event_type.lower() != \"error\" and error_payload is None and not has_top_level_error:\n return None\n\n payload = error_payload if error_payload is not None else chunk\n if isinstance(payload, dict):\n code = payload.get(\"code\") or payload.get(\"type\")\n message = payload.get(\"message\")\n if code and message:\n detail = f\"{code}: {message}\"\n elif code or message:\n detail = str(code or message)\n else:\n detail = json.dumps(payload, ensure_ascii=False)\n else:\n detail = str(payload)\n\n if api_key and len(api_key) > 8 and api_key in detail:\n detail = detail.replace(api_key, mask_key(api_key))\n return f\"SSE error: {sanitize_diagnostic(detail)[:500]}\"\n\n\ndef stream_sse(\n url: str,\n api_key: str,\n payload: dict[str, Any],\n *,\n timeout: int = 180,\n) -> Iterator[dict[str, Any]]:\n \"\"\"Streaming POST. Yields parsed SSE ``data:`` chunks.\n\n Automatically sets ``stream: true`` in the payload and sends the\n appropriate ``Accept`` header.\n \"\"\"\n payload[\"stream\"] = True\n hdrs = get_provider().make_headers(api_key, payload)\n hdrs[\"Accept\"] = \"text/event-stream\"\n data = json.dumps(payload).encode(\"utf-8\")\n req = urllib.request.Request(url, data=data, headers=hdrs, method=\"POST\")\n try:\n resp = urllib.request.urlopen(req, timeout=timeout)\n except urllib.error.HTTPError as exc:\n body = \"\"\n try:\n body = exc.read().decode(\"utf-8\", errors=\"replace\")\n except Exception:\n pass\n if api_key and len(api_key) > 8 and api_key in body:\n body = body.replace(api_key, mask_key(api_key))\n raise RuntimeError(f\"HTTP {exc.code}: {sanitize_diagnostic(body)[:500]}\") from None\n except urllib.error.URLError as exc:\n raise RuntimeError(f\"Network error: {sanitize_diagnostic(str(exc.reason))}\") from None\n\n buf = b\"\"\n event_type = \"\"\n try:\n while True:\n raw = resp.read(4096)\n if not raw:\n break\n buf += raw\n while b\"\\n\" in buf:\n line_bytes, buf = buf.split(b\"\\n\", 1)\n line = line_bytes.decode(\"utf-8\", errors=\"replace\").strip()\n if not line:\n event_type = \"\"\n continue\n if line.startswith(\"event:\"):\n event_type = line[6:].strip()\n continue\n if not line.startswith(\"data:\"):\n continue\n json_str = line[5:].strip()\n if json_str == \"[DONE]\":\n return\n try:\n chunk = json.loads(json_str)\n except json.JSONDecodeError:\n if event_type.lower() == \"error\":\n detail = json_str\n if api_key and len(api_key) > 8 and api_key in detail:\n detail = detail.replace(api_key, mask_key(api_key))\n detail = sanitize_diagnostic(detail)[:500]\n raise RuntimeError(f\"SSE error: {detail}\") from None\n pass\n else:\n error_message = _sse_error_message(chunk, event_type, api_key)\n if error_message:\n raise RuntimeError(error_message)\n yield chunk\n event_type = \"\"\n finally:\n resp.close()\n\n\n# ---------------------------------------------------------------------------\n# Section 7: File upload / download\n# ---------------------------------------------------------------------------\n\n_BASE64_FILE_LIMIT = 7 * 1024 * 1024 # 7 MB (base64 adds ~33%; API limit is 10 MB)\n\n\ndef upload_local_file(api_key: str, model: str, fp: Path) -> str:\n \"\"\"Upload a local file to the active provider's temp storage.\n\n Returns a provider-managed URL (e.g. ``oss://`` for DashScope).\n \"\"\"\n return get_provider().upload_file(api_key, model, fp)\n\n\ndef resolve_file(\n value: str,\n *,\n api_key: str | None = None,\n model: str | None = None,\n) -> str:\n \"\"\"Resolve a file reference for API consumption.\n\n URLs (``http``, ``https``, ``data``, and provider-managed schemes)\n pass through unchanged. Local files are handled based on context:\n\n - *api_key* + *model* provided: upload to temp storage.\n - Otherwise: convert to ``data:`` base64 URI (must be \u003c 7 MB).\n \"\"\"\n provider = get_provider()\n pass_through = (\"http://\", \"https://\", \"data:\") + provider.managed_url_schemes()\n if value.startswith(pass_through):\n return value\n if value.startswith(\"file://\"):\n parsed = urllib.parse.urlparse(value)\n if parsed.netloc and parsed.netloc.lower() != \"localhost\":\n raise ValueError(\n f\"Cannot resolve non-local file URI authority {parsed.netloc!r}; \"\n \"only an empty authority or 'localhost' is supported.\"\n )\n value = urllib.request.url2pathname(parsed.path)\n p = Path(value)\n if not (p.exists() and p.is_file()):\n return value\n\n file_size = p.stat().st_size\n\n if api_key and model:\n managed_url = provider.upload_file(api_key, model, p)\n tag = \"48 h TTL\" if managed_url.startswith(\"oss://\") else \"custom OSS\"\n print(sanitize_diagnostic(f\"Uploaded {p.name} -> {managed_url} ({tag})\"), file=sys.stderr)\n return managed_url\n\n if file_size > _BASE64_FILE_LIMIT:\n print(\n f\"Warning: {p.name} is {file_size / 1024 / 1024:.1f} MB -- \"\n \"base64 may exceed the 10 MB API limit. \"\n \"Use --upload-files to auto-upload, or provide an online URL.\",\n file=sys.stderr,\n )\n\n mime = mimetypes.guess_type(p.name)[0] or \"application/octet-stream\"\n b64 = base64.b64encode(p.read_bytes()).decode(\"ascii\")\n return f\"data:{mime};base64,{b64}\"\n\n\ndef download_file(url: str, dest: Path, *, timeout: int = 120) -> Path:\n \"\"\"Download a file from *url* to *dest*, creating parent dirs as needed.\"\"\"\n dest.parent.mkdir(parents=True, exist_ok=True)\n req = urllib.request.Request(url, headers={\"User-Agent\": \"qianwenai-skill\"})\n try:\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n dest.write_bytes(resp.read())\n except urllib.error.URLError as exc:\n raise RuntimeError(f\"Download failed: {sanitize_diagnostic(str(exc))}\") from None\n return dest\n\n\n# ---------------------------------------------------------------------------\n# Section 8: Request / response utilities\n# ---------------------------------------------------------------------------\n\ndef load_request(args: Any) -> dict[str, Any]:\n \"\"\"Load request dict from ``--request`` or ``--file`` CLI argument.\"\"\"\n if getattr(args, \"request\", None):\n return json.loads(args.request)\n if getattr(args, \"file\", None):\n return json.loads(Path(args.file).read_text(encoding=\"utf-8\"))\n raise ValueError(\"Provide --request '{...}' or --file path/to/request.json\")\n\n\ndef save_result(result: dict[str, Any], output_path: str | Path) -> None:\n \"\"\"Write *result* as JSON to *output_path*, creating parent dirs.\"\"\"\n p = Path(output_path)\n p.parent.mkdir(parents=True, exist_ok=True)\n p.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding=\"utf-8\")\n\n\ndef extract_text(content: Any) -> str:\n \"\"\"Extract plain text from a message ``content`` field.\n\n Handles ``str``, ``list[{type, text}]``, and ``None``.\n \"\"\"\n if isinstance(content, str):\n return content\n if isinstance(content, list):\n for item in content:\n if isinstance(item, dict) and item.get(\"type\") == \"text\":\n text = item.get(\"text\")\n if isinstance(text, str):\n return text\n return json.dumps(content, ensure_ascii=False)\n if content is None:\n return \"\"\n return json.dumps(content, ensure_ascii=False)\n\n\ndef try_parse_json(text: str) -> Any | None:\n \"\"\"Attempt to parse *text* as JSON; return ``None`` on failure.\"\"\"\n text = text.strip()\n if not text:\n return None\n try:\n return json.loads(text)\n except json.JSONDecodeError:\n return None\n\n\n# ---------------------------------------------------------------------------\n# Section 9: Async task polling\n# ---------------------------------------------------------------------------\n\ndef poll_task(\n task_id: str,\n api_key: str,\n *,\n timeout_s: int = 600,\n interval: int = 10,\n verbose: bool = True,\n) -> dict[str, Any]:\n \"\"\"Poll an async task until it reaches a terminal state.\n\n Returns the final result dict. Raises ``TimeoutError`` if\n *timeout_s* is exceeded.\n \"\"\"\n provider = get_provider()\n url = provider.task_poll_url(task_id)\n terminals = provider.terminal_statuses()\n start = time.time()\n poll_count = 0\n while True:\n result = http_request(\"GET\", url, api_key)\n elapsed = int(time.time() - start)\n poll_count += 1\n status = provider.extract_task_status(result)\n if verbose:\n print(f\" [{elapsed}s] task={task_id} status={status}\", file=sys.stderr)\n if status in terminals:\n return result\n if time.time() - start > timeout_s:\n raise TimeoutError(\n f\"Task {task_id} timed out after {timeout_s}s \"\n f\"({poll_count} polls, last status: {status})\"\n )\n time.sleep(interval)\n\n\n# ---------------------------------------------------------------------------\n# Section 10: Update-check signal\n# ---------------------------------------------------------------------------\n\ntry:\n from gossamer import run as _run_update_signal_impl\nexcept ImportError:\n _run_update_signal_impl = None # type: ignore[assignment]\n\n\ndef run_update_signal(caller: str | Path | None = None) -> None:\n \"\"\"Emit update-check signals to stderr (non-blocking, failure-safe).\n\n Parameters\n ----------\n caller : str or Path, optional\n ``__file__`` of the calling script.\n \"\"\"\n if _run_update_signal_impl:\n try:\n _run_update_signal_impl(caller=caller or __file__)\n except Exception:\n pass\n","content_type":"text/x-python; charset=utf-8","language":"python","size":43758,"content_sha256":"9343069363f8e31b7178d2462c79e2e7d56cb7a30665544d9072b1d39589d8b0"},{"filename":"scripts/tts_cosyvoice.py","content":"#!/usr/bin/env python3\n\"\"\"Synthesize speech from text via CosyVoice models (WebSocket or HTTP NRT API).\n\nCosyVoice models require the DashScope SDK.\nRun with --help for usage.\n\nDependencies:\n pip install dashscope>=1.25.17\n\nOr with venv:\n python3 -m venv .venv && source .venv/bin/activate && pip install dashscope>=1.25.17\n\"\"\"\nfrom __future__ import annotations\n\nimport sys\n\nif sys.version_info \u003c (3, 9):\n print(f\"Error: Python 3.9+ required (found {sys.version}).\", file=sys.stderr)\n sys.exit(1)\n\n# Check dashscope dependency before other imports\ntry:\n import dashscope\n from dashscope.audio.tts_v2 import SpeechSynthesizer\nexcept ImportError:\n print(\n \"Error: dashscope SDK not installed.\\n\\n\"\n \"Install with:\\n\"\n \" pip install dashscope>=1.25.17\\n\\n\"\n \"Or use venv:\\n\"\n \" python3 -m venv .venv\\n\"\n \" source .venv/bin/activate # Windows: .venv\\\\Scripts\\\\activate\\n\"\n \" pip install dashscope>=1.25.17\",\n file=sys.stderr,\n )\n sys.exit(1)\n\nimport argparse\nimport json\nimport urllib.parse\nfrom pathlib import Path\nfrom typing import Any\n\nimport requests\n\nsys.path.insert(0, str(Path(__file__).resolve().parent))\n\nfrom qianwen_lib import ( # noqa: E402\n build_source_config,\n download_file,\n is_token_plan_key,\n load_cdn_model_config,\n native_base_url,\n require_api_key,\n run_update_signal,\n)\n\n# ---------------------------------------------------------------------------\n# Constants\n# ---------------------------------------------------------------------------\n\nSKILL_USER_AGENT = \"qianwenai-skill\"\nDEFAULT_VOICE = \"longanyang\"\n\n# Native endpoint paths appended to the shared native base URL (see qianwen_lib).\n# Endpoints are derived at runtime so a custom QWEN_BASE_URL routes both the\n# HTTP NRT and WebSocket paths through the configured host. With no override,\n# native_base_url() defaults to https://dashscope.aliyuncs.com/api/v1, keeping\n# the resolved endpoints identical to the previous hardcoded constants.\n_HTTP_NRT_PATH = \"/services/audio/tts/SpeechSynthesizer\"\n_WEBSOCKET_PATH = \"/api-ws/v1/inference\"\n\n\ndef _http_nrt_url() -> str:\n \"\"\"HTTP NRT endpoint derived from the shared native base URL.\"\"\"\n return f\"{native_base_url()}{_HTTP_NRT_PATH}\"\n\n\ndef _websocket_url() -> str:\n \"\"\"WebSocket endpoint derived from the shared native base URL host.\n\n The ws/wss scheme mirrors the base URL's http/https scheme.\n \"\"\"\n parsed = urllib.parse.urlparse(native_base_url())\n scheme = \"wss\" if parsed.scheme == \"https\" else \"ws\"\n return f\"{scheme}://{parsed.netloc}{_WEBSOCKET_PATH}\"\n\nVOICES = {\n \"longanyang\": \"Sunny young man (male)\",\n \"longanhuan\": \"Energetic cheerful female\",\n \"longhuhu_v3\": \"Innocent lively girl\",\n}\n\n# System voices (built-in) — only supported by v3 models\nSYSTEM_VOICES = set(VOICES.keys())\n\n\ndef _model_config() -> dict[str, Any]:\n config = load_cdn_model_config(\n \"qianwen-audio-tts-config.json\",\n required_keys=(\"cosyvoice\",),\n )[\"cosyvoice\"]\n if not isinstance(config, dict):\n raise RuntimeError(\"Invalid audio model configuration: cosyvoice must be an object\")\n return config\n\n\ndef _default_model() -> str:\n model = _model_config().get(\"default_model\")\n if not isinstance(model, str) or not model:\n raise RuntimeError(\"Invalid audio model configuration: default_model must be a string\")\n return model\n\n\ndef _model_ids(key: str) -> set[str]:\n values = _model_config().get(key)\n if not isinstance(values, list) or not all(isinstance(item, str) for item in values):\n raise RuntimeError(f\"Invalid audio model configuration: {key} must be a string array\")\n return set(values)\n\n\ndef _build_headers(\n api_key: str,\n source_config: str | None,\n) -> dict[str, str]:\n headers = {\n \"Authorization\": f\"Bearer {api_key}\",\n \"Content-Type\": \"application/json\",\n \"User-Agent\": SKILL_USER_AGENT,\n }\n if source_config:\n headers[\"X-DashScope-Source-Config\"] = source_config\n return headers\n\n\ndef _build_payload(\n *,\n model: str,\n text: str,\n voice: str,\n audio_format: str,\n sample_rate: int,\n instruction: str | None,\n language_hints: list[str] | None,\n) -> dict[str, Any]:\n input_data: dict[str, Any] = {\n \"text\": text,\n \"voice\": voice,\n \"format\": audio_format,\n \"sample_rate\": sample_rate,\n }\n if instruction:\n input_data[\"instruction\"] = instruction\n if language_hints:\n input_data[\"language_hints\"] = language_hints\n return {\"model\": model, \"input\": input_data}\n\n\ndef _http_error(response: requests.Response, api_key: str) -> RuntimeError:\n request_id = response.headers.get(\"x-request-id\", \"\")\n code = \"\"\n message = \"\"\n try:\n payload = response.json()\n except ValueError:\n payload = None\n if isinstance(payload, dict):\n code = str(payload.get(\"code\") or \"\")\n message = str(payload.get(\"message\") or \"\")\n if api_key:\n message = message.replace(api_key, \"\u003credacted>\")\n\n details = [f\"HTTP {response.status_code}\"]\n if code:\n details.append(f\"code={code}\")\n if request_id:\n details.append(f\"request_id={request_id}\")\n if message:\n details.append(f\"message={message}\")\n return RuntimeError(\"HTTP NRT request failed: \" + \", \".join(details))\n\n\ndef _synthesize_http_nrt(\n *,\n api_key: str,\n model: str,\n text: str,\n voice: str,\n audio_format: str,\n sample_rate: int,\n instruction: str | None = None,\n language_hints: list[str] | None = None,\n source_config: str | None = None,\n timeout: int = 120,\n) -> dict[str, Any]:\n headers = _build_headers(api_key, source_config)\n payload = _build_payload(\n model=model,\n text=text,\n voice=voice,\n audio_format=audio_format,\n sample_rate=sample_rate,\n instruction=instruction,\n language_hints=language_hints,\n )\n try:\n response = requests.post(\n _http_nrt_url(),\n headers=headers,\n json=payload,\n timeout=timeout,\n )\n except requests.RequestException as exc:\n detail = str(exc).replace(api_key, \"\u003credacted>\")\n raise RuntimeError(f\"HTTP NRT request failed: {detail}\") from exc\n\n if response.status_code != 200:\n raise _http_error(response, api_key)\n try:\n result = response.json()\n except ValueError as exc:\n raise RuntimeError(\"HTTP NRT response was not valid JSON\") from exc\n if not isinstance(result, dict):\n raise RuntimeError(\"HTTP NRT response must be a JSON object\")\n return result\n\n\n# ---------------------------------------------------------------------------\n# Main\n# ---------------------------------------------------------------------------\n\ndef main() -> None:\n run_update_signal(caller=__file__)\n\n parser = argparse.ArgumentParser(\n description=\"CosyVoice via DashScope SDK\",\n formatter_class=argparse.RawDescriptionHelpFormatter,\n epilog=f\"\"\"\\\nmodels:\n cosyvoice-v3-flash (PAYG default) High quality, fast, supports system voices\n cosyvoice-v3-plus Highest quality, supports system voices\n cosyvoice-v3.5-flash High-performance, instruction control, 11 langs\n ⚠️ Custom voices only (no system voice support)\n cosyvoice-v3.5-plus Ultra-expressive, instruction control, 11 langs\n ⚠️ Custom voices only (no system voice support)\n\nsystem voices (cosyvoice-v3-flash / v3-plus only):\n longanyang (default) Sunny young man\n longanhuan Energetic cheerful female\n longhuhu_v3 Innocent lively girl\n\nnote:\n cosyvoice-v3.5 models do NOT support system voices. You must provide a\n custom voice ID created via Voice Cloning or Voice Design on the platform.\n\n For Qwen-Audio-TTS (qwen-audio-3.0-tts-plus/flash), use tts.py instead.\n\nexamples:\n # Basic synthesis (v3, default)\n python {Path(__file__).name} --text \"Hello, world!\"\n\n # Chinese with specific voice (v3)\n python {Path(__file__).name} --text \"你好世界\" --voice longanhuan\n\n # v3.5 with custom voice ID\n python {Path(__file__).name} --text \"Hello\" --model cosyvoice-v3.5-flash --voice \u003cyour-custom-voice-id>\n\n # With instruction control (v3.5 + custom voice)\n python {Path(__file__).name} --text \"欢迎光临\" --model cosyvoice-v3.5-flash --voice \u003cid> --instruction \"用热情洋溢的声音\"\n\n # Save to specific file\n python {Path(__file__).name} --text \"Hello\" --output hello.mp3\n\"\"\",\n )\n parser.add_argument(\"--text\", \"-t\", required=True, help=\"Text to synthesize\")\n parser.add_argument(\"--model\", \"-m\", default=None,\n help=\"Model (PAYG default loaded from CDN; Token Plan not supported \\u2014 use tts.py)\")\n parser.add_argument(\"--voice\", \"-v\", default=None,\n help=f\"Voice (PAYG default: {DEFAULT_VOICE}; key-specific default when omitted)\")\n parser.add_argument(\"--output\", \"-o\", type=Path, default=Path(\"output/qianwen-audio-tts/cosyvoice.mp3\"), help=\"Output file (default: output/qianwen-audio-tts/cosyvoice.mp3)\")\n parser.add_argument(\"--format\", \"-f\", default=\"mp3\", choices=[\"mp3\", \"wav\", \"pcm\"], help=\"Audio format (default: mp3)\")\n parser.add_argument(\"--sample-rate\", type=int, default=24000,\n help=\"Sample rate in Hz (default: 24000; options: 8000/16000/22050/24000/44100/48000)\")\n parser.add_argument(\"--instruction\", type=str, default=None, help=\"Free-style instruction for speech control (v3.5 and v3-flash models)\")\n parser.add_argument(\"--language-hints\", type=str, default=None, help=\"Target language hint (e.g. zh, en)\")\n args = parser.parse_args()\n\n api_key = require_api_key(script_file=__file__, domain=\"CosyVoice TTS\")\n token_plan = is_token_plan_key(api_key)\n if token_plan:\n print(\n \"Error: Token Plan is not supported by tts_cosyvoice.py.\\n\"\n \"Use tts.py with a Token Plan supported TTS model instead.\\n\"\n \"Read more: https://platform.qianwenai.com/docs/token-plan/overview\",\n file=sys.stderr,\n )\n sys.exit(1)\n\n # Model priority: CLI > PAYG default\n model = args.model or _default_model()\n voice = args.voice or DEFAULT_VOICE\n custom_voice_only_models = _model_ids(\"custom_voice_only_models\")\n instruction_models = _model_ids(\"instruction_models\")\n nrt_models = _model_ids(\"nrt_models\")\n\n # Validate: v3.5 models do not support system voices\n if model in custom_voice_only_models and voice in SYSTEM_VOICES:\n print(\n f'ERROR: Model \"{model}\" does not support system voices.\\n'\n \"You must provide a custom voice ID created via Voice Cloning or Voice Design.\\n\"\n \"Steps: Visit https://platform.qianwenai.com/docs/developer-guides/speech/voice-cloning → Upload 10-20s audio → Get voice ID\\n\"\n f\"Then: python3 {Path(__file__).name} -t \\\"text\\\" -m {model} -v \u003cyour-custom-voice-id>\",\n file=sys.stderr,\n )\n sys.exit(1)\n\n # Setup\n dashscope.api_key = api_key\n websocket_url = _websocket_url()\n dashscope.base_websocket_api_url = websocket_url\n\n # Validate instruction parameter\n if args.instruction and model not in instruction_models:\n print(f\"Warning: --instruction is not supported by model '{model}'. \"\n f\"Supported models: {', '.join(sorted(instruction_models))}\",\n file=sys.stderr)\n\n # Choose API path: HTTP NRT for supported models, WebSocket for others\n if model in nrt_models:\n # Use the public HTTP NRT API so the Skill User-Agent is preserved.\n print(f\"Synthesizing (HTTP NRT): model={model}, voice={voice}\", file=sys.stderr)\n try:\n result = _synthesize_http_nrt(\n api_key=api_key,\n model=model,\n text=args.text,\n voice=voice,\n audio_format=args.format,\n sample_rate=args.sample_rate,\n instruction=(\n args.instruction\n if args.instruction and model in instruction_models\n else None\n ),\n language_hints=(\n [args.language_hints] if args.language_hints else None\n ),\n source_config=build_source_config(__file__),\n )\n except Exception as e:\n print(f\"Error: {e}\", file=sys.stderr)\n sys.exit(1)\n\n audio_url = (\n ((result.get(\"output\") or {}).get(\"audio\") or {}).get(\"url\")\n if isinstance(result, dict)\n else None\n )\n if not audio_url:\n request_id = result.get(\"request_id\", \"\") if isinstance(result, dict) else \"\"\n suffix = f\" (request_id={request_id})\" if request_id else \"\"\n print(f\"Error: No audio URL in response{suffix}.\", file=sys.stderr)\n sys.exit(1)\n\n # Download audio from URL\n output = args.output\n if output.suffix.lower() not in {\".mp3\", \".wav\", \".pcm\"}:\n output = output.with_suffix(f\".{args.format}\")\n\n try:\n download_file(audio_url, output)\n except Exception as e:\n print(f\"Warning: Could not download audio: {e}\", file=sys.stderr)\n print(f\"Audio URL (manual download): {audio_url}\", file=sys.stderr)\n print(json.dumps({\"audio_url\": audio_url}))\n return\n\n print(f\"Audio saved to {output}\", file=sys.stderr)\n print(json.dumps({\"audio_file\": str(output), \"audio_url\": audio_url,\n \"size_bytes\": output.stat().st_size}))\n else:\n # Use WebSocket API (SpeechSynthesizer) for legacy or unsupported models\n print(f\"Synthesizing (WebSocket): model={model}, voice={voice}\", file=sys.stderr)\n try:\n headers = {\"User-Agent\": \"qianwenai-skill\"}\n source_config = build_source_config(__file__)\n if source_config:\n headers[\"X-DashScope-Source-Config\"] = source_config\n synth_kwargs: dict = {\n \"model\": model,\n \"voice\": voice,\n \"headers\": headers,\n \"url\": websocket_url,\n }\n synthesizer = SpeechSynthesizer(**synth_kwargs)\n audio_data = synthesizer.call(args.text)\n except Exception as e:\n print(f\"Error: {e}\", file=sys.stderr)\n sys.exit(1)\n\n if not audio_data:\n response = synthesizer.get_response()\n header = response.get(\"header\", {}) if isinstance(response, dict) else {}\n if header.get(\"event\") == \"task-failed\":\n error_code = header.get(\"error_code\", \"UnknownError\")\n error_message = header.get(\"error_message\", \"Unknown error\")\n task_id = header.get(\"task_id\", \"\")\n suffix = f\" (task_id={task_id})\" if task_id else \"\"\n print(\n f\"Error: WebSocket task failed: {error_code}: \"\n f\"{error_message}{suffix}\",\n file=sys.stderr,\n )\n else:\n print(\"Error: No audio data returned.\", file=sys.stderr)\n sys.exit(1)\n\n # Save\n output = args.output\n if output.suffix.lower() not in {\".mp3\", \".wav\", \".pcm\"}:\n output = output.with_suffix(f\".{args.format}\")\n output.parent.mkdir(parents=True, exist_ok=True)\n output.write_bytes(audio_data)\n\n print(f\"Audio saved to {output}\", file=sys.stderr)\n print(json.dumps({\"audio_file\": str(output), \"size_bytes\": len(audio_data)}))\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":15803,"content_sha256":"354dbdf6df7dcdd381ecda0c0dded5a899f22864c183c833956be4e23cc622f1"},{"filename":"scripts/tts.py","content":"#!/usr/bin/env python3\n\"\"\"Synthesize speech from text via Qwen TTS models on DashScope API.\n\nSupports multiple voices, instruction-controlled style, and automatic audio\ndownload. Self-contained, stdlib only.\n\"\"\"\nfrom __future__ import annotations\n\nimport sys\n\nif sys.version_info \u003c (3, 9):\n print(f\"Error: Python 3.9+ required (found {sys.version}). \"\n \"Install: https://www.python.org/downloads/\", file=sys.stderr)\n sys.exit(1)\n\nimport argparse\nimport json\nfrom pathlib import Path\nfrom typing import Any\nfrom urllib.parse import urlparse\n\nsys.path.insert(0, str(Path(__file__).resolve().parent))\n\nfrom qianwen_lib import ( # noqa: E402\n download_file,\n http_request,\n load_cdn_model_config,\n load_request,\n native_base_url,\n require_api_key,\n run_update_signal,\n validate_token_plan_model,\n)\n\n# ---------------------------------------------------------------------------\n# TTS constants\n# ---------------------------------------------------------------------------\n\nTTS_GENERATION_PATH = \"/services/aigc/multimodal-generation/generation\"\n# NRT endpoint path (Qwen-Audio-TTS models: qwen-audio-3.0-tts-plus/flash)\nNRT_TTS_PATH = \"/services/audio/tts/SpeechSynthesizer\"\nDEFAULT_VOICE = \"Cherry\"\n\nNRT_FALLBACK_VOICE = \"longanlingxin\"\n\n\n# ---------------------------------------------------------------------------\n# Helpers\n# ---------------------------------------------------------------------------\n\ndef _model_config() -> dict[str, Any]:\n config = load_cdn_model_config(\n \"qianwen-audio-tts-config.json\",\n required_keys=(\"tts\",),\n )[\"tts\"]\n if not isinstance(config, dict):\n raise RuntimeError(\"Invalid audio model configuration: tts must be an object\")\n return config\n\n\ndef _default_model() -> str:\n model = _model_config().get(\"default_model\")\n if not isinstance(model, str) or not model:\n raise RuntimeError(\"Invalid audio model configuration: default_model must be a string\")\n return model\n\n\ndef _nrt_default_voices() -> dict[str, str]:\n voices = _model_config().get(\"nrt_default_voices\")\n if not isinstance(voices, dict) or not all(\n isinstance(model, str) and isinstance(voice, str)\n for model, voice in voices.items()\n ):\n raise RuntimeError(\"Invalid audio model configuration: nrt_default_voices must be an object\")\n return voices\n\n\ndef _guess_audio_ext(url: str, default: str = \".wav\") -> str:\n \"\"\"Extract audio extension from URL path, ignoring query params.\"\"\"\n path = urlparse(url).path\n for ext in (\".wav\", \".mp3\", \".flac\", \".ogg\", \".pcm\", \".aac\"):\n if path.endswith(ext):\n return ext\n return default\n\n\ndef _is_qwen3_tts(model: str) -> bool:\n \"\"\"Qwen3-TTS models use the multimodal-generation endpoint.\"\"\"\n return model.startswith(\"qwen3-tts-\")\n\n\n# ---------------------------------------------------------------------------\n# Main\n# ---------------------------------------------------------------------------\n\ndef main() -> None:\n run_update_signal(caller=__file__)\n parser = argparse.ArgumentParser(\n description=\"Synthesize speech from text via Qwen TTS\",\n formatter_class=argparse.RawDescriptionHelpFormatter,\n epilog=\"\"\"\\\nrequest JSON fields (--request / --file):\n text (required) Text to synthesize into speech\n voice Voice ID — overridden by --voice flag\n model Model ID — overridden by --model flag\n language_type Force language (Qwen3-TTS models only):\n \"Chinese\" / \"English\" / \"Japanese\" / \"Korean\" /\n \"French\" / \"German\" / \"Russian\" / \"Italian\" /\n \"Spanish\" / \"Portuguese\"\n language_hints Force language (NRT models: qwen-audio-3.0-tts-*):\n [\"zh\"] / [\"en\"] / [\"ja\"] / [\"ko\"] etc. (ISO array)\n instructions Style instructions for instruct models, e.g.\n \"Speak slowly with a warm, gentle tone\"\n optimize_instructions true/false — auto-optimize instruction text\n\nmodels:\n qwen-audio-3.0-tts-plus (default) High-quality NRT, voice longanlingxin\n qwen-audio-3.0-tts-flash Low-latency NRT, voice longanhuan_v3.6 (PAYG-only)\n qwen3-tts-flash Fast multimodal-generation, voice Cherry\n qwen3-tts-instruct-flash Instruction-controlled style/emotion/pace\n\n Note: For CosyVoice (v3.5/v3), realtime streaming, etc.,\n use tts_cosyvoice.py or see https://www.qianwenai.com/models.\n\noutput:\n --output can be a directory (audio saved as audio.wav/mp3 inside)\n or a specific file path (e.g. output/speech.mp3). Extension is\n auto-detected from the API response URL.\n\nenvironment variables:\n DASHSCOPE_API_KEY (required) API key — also loaded from .env\n QIANWEN_API_KEY (alternative) Alias for DASHSCOPE_API_KEY\n QWEN_REGION cn-beijing (default)\n\nexamples:\n # Simple TTS\n python scripts/tts.py --request '{\"text\":\"Hello, world!\"}'\n\n # Chinese text with specific voice\n python scripts/tts.py --request '{\"text\":\"你好世界\"}' --voice Chelsie\n\n # Instruction-controlled style\n python scripts/tts.py --request '{\"text\":\"Breaking news today...\",\n \"instructions\":\"Speak in a serious news anchor tone, moderate pace\"}' \\\\\n --model qwen3-tts-instruct-flash\n\n # Save to specific file\n python scripts/tts.py --request '{\"text\":\"Hello\"}' --output output/hello.mp3\n\"\"\",\n )\n parser.add_argument(\"--request\", type=str,\n help=\"Inline JSON: must contain 'text' field\")\n parser.add_argument(\"--file\", type=Path,\n help=\"Path to JSON file containing request body\")\n parser.add_argument(\"--output\", type=Path, default=Path(\"output/qianwen-audio-tts\"),\n help=\"Output directory or file path (default: %(default)s)\")\n parser.add_argument(\"--print-response\", action=\"store_true\",\n help=\"Print audio URL and file path JSON to stdout\")\n parser.add_argument(\"--model\", type=str, default=None,\n help=\"Model ID (default loaded from CDN; see epilog for model examples)\")\n parser.add_argument(\"--voice\", type=str, default=None,\n help=f\"Voice ID (default: {DEFAULT_VOICE} for qwen3-tts-*)\")\n parser.add_argument(\"--format\", default=\"mp3\", choices=[\"mp3\", \"wav\", \"pcm\"],\n help=\"Audio format (Qwen-Audio-TTS models only, default: mp3)\")\n parser.add_argument(\"--sample-rate\", type=int, default=24000,\n help=\"Sample rate in Hz (Qwen-Audio-TTS models only, default: 24000)\")\n args = parser.parse_args()\n\n try:\n request = load_request(args)\n except ValueError as e:\n print(f\"Error: {e}\", file=sys.stderr)\n sys.exit(1)\n\n text = request.get(\"text\")\n if not text:\n print(\"Error: text is required.\", file=sys.stderr)\n sys.exit(1)\n\n # Model priority: CLI > request body > default\n if args.model:\n request[\"model\"] = args.model\n elif \"model\" not in request or not request.get(\"model\"):\n request[\"model\"] = _default_model()\n model = request[\"model\"]\n\n api_key = require_api_key(script_file=__file__, domain=\"TTS\")\n validate_token_plan_model(api_key, model)\n\n if _is_qwen3_tts(model):\n # Qwen3-TTS: multimodal-generation endpoint\n voice = request.get(\"voice\") or args.voice or DEFAULT_VOICE\n input_obj: dict[str, Any] = {\"text\": text, \"voice\": voice}\n if request.get(\"language_type\"):\n input_obj[\"language_type\"] = request[\"language_type\"]\n if request.get(\"instructions\") and \"instruct\" in model.lower():\n input_obj[\"instructions\"] = request[\"instructions\"]\n if request.get(\"optimize_instructions\") is not None:\n input_obj[\"optimize_instructions\"] = request[\"optimize_instructions\"]\n url = f\"{native_base_url()}{TTS_GENERATION_PATH}\"\n else:\n # Qwen-Audio-TTS series: NRT endpoint\n voice = request.get(\"voice\") or args.voice or _nrt_default_voices().get(model, NRT_FALLBACK_VOICE)\n input_obj = {\n \"text\": text,\n \"voice\": voice,\n \"format\": args.format,\n \"sample_rate\": args.sample_rate,\n }\n if request.get(\"instruction\"):\n input_obj[\"instruction\"] = request[\"instruction\"]\n if request.get(\"language_hints\"):\n hints = request[\"language_hints\"]\n if isinstance(hints, str):\n hints = [hints]\n input_obj[\"language_hints\"] = hints\n url = f\"{native_base_url()}{NRT_TTS_PATH}\"\n\n payload: dict[str, Any] = {\"model\": model, \"input\": input_obj}\n\n try:\n resp = http_request(\"POST\", url, api_key, payload, timeout=60)\n except RuntimeError as e:\n print(f\"API error: {e}\", file=sys.stderr)\n sys.exit(1)\n\n audio_url = (resp.get(\"output\") or {}).get(\"audio\", {}).get(\"url\")\n if not audio_url:\n print(f\"Error: No audio URL in response: {resp}\", file=sys.stderr)\n sys.exit(1)\n\n out = args.output\n _AUDIO_EXTS = {\".wav\", \".mp3\", \".flac\", \".ogg\", \".pcm\", \".aac\"}\n\n if out.suffix.lower() in _AUDIO_EXTS:\n audio_file = out\n out_dir = out.parent\n else:\n out_dir = out\n ext = _guess_audio_ext(audio_url)\n audio_file = out_dir / f\"audio{ext}\"\n\n out_dir.mkdir(parents=True, exist_ok=True)\n\n resp_file = out_dir / \"response.json\"\n resp_file.write_text(json.dumps(resp, ensure_ascii=False, indent=2), encoding=\"utf-8\")\n print(f\"Response saved to {resp_file}\", file=sys.stderr)\n\n try:\n download_file(audio_url, audio_file)\n except Exception as e:\n print(f\"Warning: Could not download audio: {e}\", file=sys.stderr)\n print(f\"Audio URL (manual download): {audio_url}\", file=sys.stderr)\n else:\n print(f\"Audio saved to {audio_file}\", file=sys.stderr)\n\n if args.print_response:\n print(json.dumps({\"audio_url\": audio_url, \"audio_file\": str(audio_file)}, ensure_ascii=False))\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":10088,"content_sha256":"f8c36426a980b4eb115c5b9949443565954d4ddfe798eadc4d7ed07532f5d206"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Qwen Audio TTS (Text-to-Speech)","type":"text"}]},{"type":"paragraph","content":[{"text":"Synthesize natural speech from text using Qwen TTS models. This skill is part of ","type":"text"},{"text":"QianWen-AI/qianwen-ai","type":"text","marks":[{"type":"strong"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Skill directory","type":"text"}]},{"type":"paragraph","content":[{"text":"Use this skill's internal files to execute and learn. Load reference files on demand when the default path fails or you need details.","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Location","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Purpose","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/tts.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Qwen TTS (HTTP API) — qwen3-tts-","type":"text"},{"text":", qwen-audio-3.0-tts-","type":"text","marks":[{"type":"em"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/tts_cosyvoice.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CosyVoice (WebSocket / HTTP NRT) — requires ","type":"text"},{"text":"dashscope","type":"text","marks":[{"type":"code_inline"}]},{"text":" SDK","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/cosyvoice-guide.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CosyVoice setup, voices, examples, errors","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/execution-guide.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fallback: curl (standard, instruct, streaming), code generation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/prompt-guide.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Text formatting for speech, instructions templates, voice selection","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/api-guide.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"API supplement","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/sources.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Official documentation URLs","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Security","type":"text"}]},{"type":"paragraph","content":[{"text":"NEVER output any API key or credential in plaintext.","type":"text","marks":[{"type":"strong"}]},{"text":" Always use variable references (","type":"text"},{"text":"$DASHSCOPE_API_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":" in shell, ","type":"text"},{"text":"os.environ[\"DASHSCOPE_API_KEY\"]","type":"text","marks":[{"type":"code_inline"}]},{"text":" in Python). Any check or detection of credentials must be ","type":"text"},{"text":"non-plaintext","type":"text","marks":[{"type":"strong"}]},{"text":": report only status (e.g. \"set\" / \"not set\", \"valid\" / \"invalid\"), never the value. Never display contents of ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" or config files that may contain secrets.","type":"text"}]},{"type":"paragraph","content":[{"text":"When the API key is not configured, NEVER ask the user to provide it directly.","type":"text","marks":[{"type":"strong"}]},{"text":" Instead, help create a ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" file with a placeholder (","type":"text"},{"text":"DASHSCOPE_API_KEY=sk-your-key-here","type":"text","marks":[{"type":"code_inline"}]},{"text":") and instruct the user to replace it with their actual key from the ","type":"text"},{"text":"QianWen Console","type":"text","marks":[{"type":"link","attrs":{"href":"https://platform.qianwenai.com/home/api-keys","title":null}}]},{"text":". Only write the actual key value if the user explicitly requests it.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Key Compatibility","type":"text"}]},{"type":"paragraph","content":[{"text":"Both PAYG (","type":"text"},{"text":"sk-ws-...","type":"text","marks":[{"type":"code_inline"}]},{"text":"; legacy ","type":"text"},{"text":"sk-...","type":"text","marks":[{"type":"code_inline"}]},{"text":") and Token Plan (","type":"text"},{"text":"sk-sp-...","type":"text","marks":[{"type":"code_inline"}]},{"text":") keys are supported. Detect the API key type without exposing the Key:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 -c \"\nimport sys; sys.path.insert(0, 'scripts')\nfrom qianwen_lib import detect_api_key_type\nprint(detect_api_key_type('scripts/qianwen_lib.py'))\n\"","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Output","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Meaning","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"token-plan","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Token Plan key — use only models from the Token Plan catalog below.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"payg","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pay-as-you-go key — full model catalog available.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"not-set","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No key configured.","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"For Token Plan, fetch and read the current ","type":"text"},{"text":"Token Plan model catalog","type":"text","marks":[{"type":"link","attrs":{"href":"https://alioth.alicdn.com/skills-info/models/references/qianwen-token-plan-models.md","title":null}}]},{"text":", then use an exact listed model with ","type":"text"},{"text":"scripts/tts.py","type":"text","marks":[{"type":"code_inline"}]},{"text":". If CDN access fails, use the ","type":"text"},{"text":"local fallback","type":"text","marks":[{"type":"link","attrs":{"href":"cdn/references/qianwen-token-plan-models.md","title":null}}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Pre-Execution: Key-Based Model Decision","type":"text"}]},{"type":"paragraph","content":[{"text":"Before calling a TTS script, determine the key type by its prefix (non-plaintext check via ","type":"text"},{"text":"scripts/qianwen_lib.py","type":"text","marks":[{"type":"code_inline"}]},{"text":"), then fetch and read the current ","type":"text"},{"text":"Qwen audio TTS model catalog","type":"text","marks":[{"type":"link","attrs":{"href":"https://alioth.alicdn.com/skills-info/models/references/qianwen-audio-tts-models.md","title":null}}]},{"text":" for script compatibility, recommendations, and defaults. If CDN access fails, use the ","type":"text"},{"text":"local fallback","type":"text","marks":[{"type":"link","attrs":{"href":"cdn/references/qianwen-audio-tts-models.md","title":null}}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Token Plan supports only specific models — use exactly a model from the references above; do not guess or probe model availability. For PAYG, continue below.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Model Selection","type":"text"}]},{"type":"paragraph","content":[{"text":"Before selecting, recommending, or defaulting a model, use the Qwen audio TTS model catalog linked above. It contains the model list, basic model information, script and voice compatibility, recommendations, and defaults.","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"⚠️ Important","type":"text","marks":[{"type":"strong"}]},{"text":": The model catalog is a ","type":"text"},{"text":"point-in-time snapshot","type":"text","marks":[{"type":"strong"}]},{"text":" and may be outdated. Model availability changes frequently. ","type":"text"},{"text":"Always check the ","type":"text","marks":[{"type":"strong"}]},{"text":"official model list","type":"text","marks":[{"type":"link","attrs":{"href":"https://www.qianwenai.com/models","title":null}},{"type":"strong"}]},{"text":" for the authoritative, up-to-date catalog before making model decisions.","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Model details","type":"text","marks":[{"type":"strong"}]},{"text":": For more information about a specific model, direct the user to ","type":"text"},{"text":"https://www.qianwenai.com/models/\u003cmodel-name>","type":"text","marks":[{"type":"code_inline"}]},{"text":". Replace ","type":"text"},{"text":"\u003cmodel-name>","type":"text","marks":[{"type":"code_inline"}]},{"text":" with the exact model ID; never modify or guess it.","type":"text"}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Dynamic model queries","type":"text","marks":[{"type":"strong"}]},{"text":": If the ","type":"text"},{"text":"qianwen-model-selector","type":"text","marks":[{"type":"strong"}]},{"text":" skill or ","type":"text"},{"text":"QianWen CLI","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"qianwen models info \u003cmodel>","type":"text","marks":[{"type":"code_inline"}]},{"text":") is available, use it for real-time model data. CLI requires authentication — see the ","type":"text"},{"text":"qianwen-usage","type":"text","marks":[{"type":"strong"}]},{"text":" skill for login flow.","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Available Voices","type":"text"}]},{"type":"paragraph","content":[{"text":"Use the Qwen audio TTS model catalog linked above for current model-to-voice compatibility and defaults. For complete voice inventories, use the official voice-list links in ","type":"text"},{"text":"sources.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/sources.md","title":null}}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Custom Voices (Required for v3.5)","type":"text"}]},{"type":"paragraph","content":[{"text":"cosyvoice-v3.5 models require a custom voice ID. To create one:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Visit ","type":"text"},{"text":"Voice Cloning","type":"text","marks":[{"type":"link","attrs":{"href":"https://platform.qianwenai.com/docs/developer-guides/speech/voice-cloning","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Upload a 10-20s clean speech sample","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Obtain the custom voice ID","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pass via ","type":"text"},{"text":"--voice \u003cid>","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"\"voice\": \"\u003cid>\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" in request JSON","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Execution","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"⚠️ Multiple artifacts","type":"text","marks":[{"type":"strong"}]},{"text":": When generating multiple files in a single session, you MUST append a numeric suffix to each filename (e.g. ","type":"text"},{"text":"out_1.wav","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"out_2.wav","type":"text","marks":[{"type":"code_inline"}]},{"text":") to prevent overwrites.","type":"text"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Qwen TTS (HTTP API) — ","type":"text"},{"text":"tts.py","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"Prerequisites","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"API Key","type":"text","marks":[{"type":"strong"}]},{"text":": Use the non-plaintext detector in ","type":"text"},{"text":"Key Compatibility","type":"text","marks":[{"type":"strong"}]},{"text":"; do not replace it with a variable-presence check. If no Key is found, use qianwen-ops-auth when available or guide the user to configure ","type":"text"},{"text":"DASHSCOPE_API_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"QIANWEN_API_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":" in ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":". Skills may be installed independently.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Python 3.9+ (stdlib only, ","type":"text"},{"text":"no pip install needed","type":"text","marks":[{"type":"strong"}]},{"text":")","type":"text"}]}]}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"Environment Check","type":"text"}]},{"type":"paragraph","content":[{"text":"Before first execution, verify Python is available:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 --version # must be 3.9+","type":"text"}]},{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"python3","type":"text","marks":[{"type":"code_inline"}]},{"text":" is not found, try ","type":"text"},{"text":"python --version","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"py -3 --version","type":"text","marks":[{"type":"code_inline"}]},{"text":". If Python is unavailable or below 3.9, skip to ","type":"text"},{"text":"Path 2 (curl)","type":"text","marks":[{"type":"strong"}]},{"text":" in ","type":"text"},{"text":"execution-guide.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/execution-guide.md","title":null}}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"Default: Run Script","type":"text"}]},{"type":"paragraph","content":[{"text":"Script path","type":"text","marks":[{"type":"strong"}]},{"text":": Scripts are in the ","type":"text"},{"text":"scripts/","type":"text","marks":[{"type":"code_inline"}]},{"text":" subdirectory ","type":"text"},{"text":"of this skill's directory","type":"text","marks":[{"type":"strong"}]},{"text":" (the directory containing this SKILL.md). ","type":"text"},{"text":"You MUST first locate this skill's installation directory, then ALWAYS use the full absolute path to execute scripts.","type":"text","marks":[{"type":"strong"}]},{"text":" Do NOT assume scripts are in the current working directory. Do NOT use ","type":"text"},{"text":"cd","type":"text","marks":[{"type":"code_inline"}]},{"text":" to switch directories before execution.","type":"text"}]},{"type":"paragraph","content":[{"text":"Execution note:","type":"text","marks":[{"type":"strong"}]},{"text":" Run all scripts in the ","type":"text"},{"text":"foreground","type":"text","marks":[{"type":"strong"}]},{"text":" — wait for stdout; do not background.","type":"text"}]},{"type":"paragraph","content":[{"text":"Discovery:","type":"text","marks":[{"type":"strong"}]},{"text":" Run ","type":"text"},{"text":"python3 \u003cthis-skill-dir>/scripts/tts.py --help","type":"text","marks":[{"type":"code_inline"}]},{"text":" first to see all available arguments.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 \u003cthis-skill-dir>/scripts/tts.py \\\n --request '{\"text\":\"Hello, this is a test.\",\"voice\":\"longanlingxin\"}' \\\n --output output/qianwen-audio-tts/ \\\n --print-response","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Argument","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Description","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--request '{...}'","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"JSON request body","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--file path.json","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Load request from file","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--output path","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Save audio and response JSON to directory, or specify an audio file path (e.g. ","type":"text"},{"text":"speech.mp3","type":"text","marks":[{"type":"code_inline"}]},{"text":"); use distinct filenames across calls to avoid overwriting","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--print-response","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Print response to stdout","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--model ID","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Override model","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--voice NAME","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Override voice","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--format","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Audio format for Qwen-Audio-TTS NRT models (","type":"text"},{"text":"mp3","type":"text","marks":[{"type":"code_inline"}]},{"text":" default, ","type":"text"},{"text":"wav","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"pcm","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"--sample-rate","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sample rate in Hz for Qwen-Audio-TTS NRT models (default: 24000)","type":"text"}]}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Model priority","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"--model","type":"text","marks":[{"type":"code_inline"}]},{"text":" CLI flag > ","type":"text"},{"text":"\"model\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" field in ","type":"text"},{"text":"--request","type":"text","marks":[{"type":"code_inline"}]},{"text":" JSON > built-in default.","type":"text"}]}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"Verify Result","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Exit code ","type":"text"},{"text":"0","type":"text","marks":[{"type":"code_inline"}]},{"text":" + output contains valid JSON with ","type":"text"},{"text":"output.audio","type":"text","marks":[{"type":"code_inline"}]},{"text":" field → ","type":"text"},{"text":"success","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Non-zero exit, HTTP error, empty response, or error JSON → ","type":"text"},{"text":"fail","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Post-execution check","type":"text","marks":[{"type":"strong"}]},{"text":": Verify the output audio file exists and has non-zero size (","type":"text"},{"text":"ls -la \u003coutput_dir>","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"MANDATORY — stderr signal check","type":"text","marks":[{"type":"strong"}]},{"text":": After confirming the result, scan the command's stderr output for ","type":"text"},{"text":"[ACTION_REQUIRED]","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"[UPDATE_AVAILABLE]","type":"text","marks":[{"type":"code_inline"}]},{"text":". If either signal is present, you ","type":"text"},{"text":"MUST","type":"text","marks":[{"type":"strong"}]},{"text":" follow the instructions in ","type":"text"},{"text":"Update Check","type":"text","marks":[{"type":"link","attrs":{"href":"#update-check-mandatory-post-execution","title":null}}]},{"text":" below before responding to the user.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"On Failure","type":"text"}]},{"type":"paragraph","content":[{"text":"If the script fails, match the error output against the diagnostic table below to determine the resolution. If no match, read ","type":"text"},{"text":"execution-guide.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/execution-guide.md","title":null}}]},{"text":" for alternative paths: curl commands (Path 2 — standard, instruct, streaming), code generation (Path 3), and autonomous resolution (Path 5).","type":"text"}]},{"type":"paragraph","content":[{"text":"If Python is not available at all","type":"text","marks":[{"type":"strong"}]},{"text":" → skip directly to Path 2 (curl) in ","type":"text"},{"text":"execution-guide.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/execution-guide.md","title":null}}]},{"text":".","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"CosyVoice — ","type":"text"},{"text":"tts_cosyvoice.py","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"CosyVoice requires ","type":"text"},{"text":"dashscope","type":"text","marks":[{"type":"code_inline"}]},{"text":" SDK. Quick start:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"pip install dashscope>=1.25.17\npython3 \u003cthis-skill-dir>/scripts/tts_cosyvoice.py --text \"Hello\"","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Full guide","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"cosyvoice-guide.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/cosyvoice-guide.md","title":null}}]},{"text":" (setup, voices, examples, errors)","type":"text"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Error Pattern","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Diagnosis","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Resolution","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"command not found: python3","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Python not on PATH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Try ","type":"text"},{"text":"python","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"py -3","type":"text","marks":[{"type":"code_inline"}]},{"text":"; install Python 3.9+ if missing","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Python 3.9+ required","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Script version check failed","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Upgrade Python to 3.9+","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SyntaxError","type":"text","marks":[{"type":"code_inline"}]},{"text":" near type hints","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Python \u003c 3.9","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Upgrade Python to 3.9+","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"QIANWEN_API_KEY/DASHSCOPE_API_KEY not found","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Missing API key","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Obtain key from ","type":"text"},{"text":"QianWen Console","type":"text","marks":[{"type":"link","attrs":{"href":"https://platform.qianwenai.com/home/api-keys","title":null}}]},{"text":"; add to ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":": ","type":"text"},{"text":"echo 'DASHSCOPE_API_KEY=sk-...' >> .env","type":"text","marks":[{"type":"code_inline"}]},{"text":"; or run ","type":"text"},{"text":"qianwen-ops-auth","type":"text","marks":[{"type":"strong"}]},{"text":" if available","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HTTP 401","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Invalid or mismatched key","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"qianwen-ops-auth","type":"text","marks":[{"type":"strong"}]},{"text":" (non-plaintext check only); verify key is valid","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SSL: CERTIFICATE_VERIFY_FAILED","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SSL cert issue (proxy/corporate)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"macOS: run ","type":"text"},{"text":"Install Certificates.command","type":"text","marks":[{"type":"code_inline"}]},{"text":"; else set ","type":"text"},{"text":"SSL_CERT_FILE","type":"text","marks":[{"type":"code_inline"}]},{"text":" env var","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"URLError","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"ConnectionError","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Network unreachable","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check internet; set ","type":"text"},{"text":"HTTPS_PROXY","type":"text","marks":[{"type":"code_inline"}]},{"text":" if behind proxy","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HTTP 429","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rate limited","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wait and retry with backoff","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HTTP 5xx","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Server error","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Retry with backoff","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PermissionError","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Can't write output","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"--output","type":"text","marks":[{"type":"code_inline"}]},{"text":" to specify writable directory","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Reference","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Request Fields (Qwen3-TTS HTTP API — ","type":"text"},{"text":"tts.py","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Field","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Type","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Description","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"text","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Required","type":"text","marks":[{"type":"strong"}]},{"text":" — text to synthesize (max 600 chars)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"voice","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Required","type":"text","marks":[{"type":"strong"}]},{"text":" — voice ID (e.g. ","type":"text"},{"text":"Cherry","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"Ethan","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"model","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Model ID; check the model catalog above for the current default","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"language_type","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Auto","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"Chinese","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"English","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"Japanese","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"Korean","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"French","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"German","type":"text","marks":[{"type":"code_inline"}]},{"text":", etc.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"instructions","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tone/style instructions; check the model catalog above for current model compatibility and limits","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"optimize_instructions","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bool","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"When true, the system semantically enhances ","type":"text"},{"text":"instructions","type":"text","marks":[{"type":"code_inline"}]},{"text":" for better naturalness. Requires ","type":"text"},{"text":"instructions","type":"text","marks":[{"type":"code_inline"}]},{"text":" to be set. Default: false","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"stream","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bool","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Enable streaming (Base64 chunks)","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Request Fields (CosyVoice NRT API — ","type":"text"},{"text":"tts_cosyvoice.py","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Field","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Type","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Description","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"text","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Required","type":"text","marks":[{"type":"strong"}]},{"text":" — text to synthesize (max 20,000 chars per call)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"voice","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Required","type":"text","marks":[{"type":"strong"}]},{"text":" — voice ID (model-specific, see voice lists)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"model","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Model ID; check the model catalog above for the current default","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"instruction","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Free-style instruction for speech control; check the model catalog above for current model compatibility","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"language_hints","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"list","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Target language hint: ","type":"text"},{"text":"zh","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"en","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"fr","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"de","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ja","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ko","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ru","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"pt","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"th","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"id","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"vi","type":"text","marks":[{"type":"code_inline"}]},{"text":", etc.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"format","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Audio format: ","type":"text"},{"text":"mp3","type":"text","marks":[{"type":"code_inline"}]},{"text":" (default), ","type":"text"},{"text":"wav","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"pcm","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"opus","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sample_rate","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"int","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sample rate (Hz): 8000, 16000, 22050 (default), 24000, 44100, 48000","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Response Fields","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Field","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Description","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"audio_url","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"URL of generated audio (valid 24h)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"audio_format","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Format (e.g. wav)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sample_rate","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sample rate (e.g. 24000)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"usage","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Character usage","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Important Notes","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"text","type":"text","marks":[{"type":"strong"}]},{"text":": Max 600 characters per request (Qwen3-TTS). Max 20,000 characters (CosyVoice/Qwen-Audio-TTS NRT).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"instructions / instruction model compatibility","type":"text","marks":[{"type":"strong"}]},{"text":": Check the model catalog above, then use the parameter form documented for the selected model.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"language_type","type":"text","marks":[{"type":"strong"}]},{"text":" (Qwen3-TTS): ","type":"text"},{"text":"Auto","type":"text","marks":[{"type":"code_inline"}]},{"text":" for mixed language; specify for better pronunciation.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"language_hints","type":"text","marks":[{"type":"strong"}]},{"text":" (CosyVoice/Qwen-Audio-TTS): Specify target language code (","type":"text"},{"text":"zh","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"en","type":"text","marks":[{"type":"code_inline"}]},{"text":", etc.) for improved synthesis quality.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"audio_url","type":"text","marks":[{"type":"strong"}]},{"text":": Valid for 24 hours — download promptly.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Real-time/streaming TTS","type":"text","marks":[{"type":"strong"}]},{"text":": For WebSocket-based real-time TTS (CosyVoice, qwen3-tts-flash-realtime), a WebSocket client is required. This skill covers the HTTP-based non-real-time API. For real-time streaming use cases, refer to the official docs in ","type":"text"},{"text":"sources.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/sources.md","title":null}}]},{"text":".","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Cross-Skill Chaining","type":"text"}]},{"type":"paragraph","content":[{"text":"When passing generated audio to another skill (e.g., video-gen audio overlay):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pass ","type":"text","marks":[{"type":"strong"}]},{"text":"audio_url","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" directly","type":"text","marks":[{"type":"strong"}]},{"text":" — scripts detect URL prefixes and pass through without re-upload","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"audio_file","type":"text","marks":[{"type":"code_inline"}]},{"text":" only for local playback or non-API operations","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Error Handling","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Error","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cause","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Action","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"401 Unauthorized","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Invalid or missing API key","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"qianwen-ops-auth","type":"text","marks":[{"type":"strong"}]},{"text":" if available; else prompt user to set key (non-plaintext check only)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"400","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Invalid parameters (missing text/voice)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Validate request body","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"429","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"5xx","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rate limit or server error","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Retry with backoff","type":"text"}]}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Usage & billing","type":"text","marks":[{"type":"strong"}]},{"text":": Use the ","type":"text"},{"text":"qianwen-usage","type":"text","marks":[{"type":"strong"}]},{"text":" skill to check usage, free tier quota, and billing directly. Alternatively, the user can visit the QianWen console: ","type":"text"},{"text":"Usage Analytics","type":"text","marks":[{"type":"link","attrs":{"href":"https://platform.qianwenai.com/home/analytics","title":null}}]},{"text":" | ","type":"text"},{"text":"Pay-as-you-go Billing","type":"text","marks":[{"type":"link","attrs":{"href":"https://platform.qianwenai.com/home/billing/pay-as-you-go","title":null}}]},{"text":" | ","type":"text"},{"text":"Token Plan Subscription","type":"text","marks":[{"type":"link","attrs":{"href":"https://platform.qianwenai.com/home/billing/subscription/token-plan","title":null}}]}]},{"type":"paragraph","content":[{"text":"NEVER fabricate, guess, or construct usage/billing/console URLs.","type":"text","marks":[{"type":"strong"}]},{"text":" Only provide the exact links listed in this skill. If a URL is not listed here, do not invent one.","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Output Location","type":"text"}]},{"type":"paragraph","content":[{"text":"Prefer the ","type":"text"},{"text":"current working directory","type":"text","marks":[{"type":"strong"}]},{"text":". Default subdirectory: ","type":"text"},{"text":"./output/qianwen-audio-tts/","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Write prohibition","type":"text","marks":[{"type":"strong"}]},{"text":": NEVER write output files into this skill's installation directory or any ","type":"text"},{"text":"skills/","type":"text","marks":[{"type":"code_inline"}]},{"text":" hierarchy. All generated content must go to ","type":"text"},{"text":"output/","type":"text","marks":[{"type":"code_inline"}]},{"text":" under the current working directory or a user-specified path.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Update Check (MANDATORY Post-Execution)","type":"text"}]},{"type":"paragraph","content":[{"text":"After script execution, check stderr for action signals. The script automatically detects update status and emits structured signals. You MUST act on them. This section also applies when the user explicitly asks to check for updates.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Signal: ","type":"text"},{"text":"[ACTION_REQUIRED] qianwen-update-check skill is not installed","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"When stderr contains this signal, you MUST present the following options to the user and ","type":"text"},{"text":"wait for their response before proceeding","type":"text","marks":[{"type":"strong"}]},{"text":". Do NOT silently skip this.","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"The update-check skill is not installed. It keeps you informed about new QianWen-AI/qianwen-ai features and bug fixes.","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Install now","type":"text","marks":[{"type":"strong"}]},{"text":" — I'll run the install command for you","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Skip for now","type":"text","marks":[{"type":"strong"}]},{"text":" — do nothing this time","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never remind me","type":"text","marks":[{"type":"strong"}]},{"text":" — permanently suppress this prompt","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"After the user responds:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User chooses ","type":"text"},{"text":"1","type":"text","marks":[{"type":"strong"}]},{"text":" → run: ","type":"text"},{"text":"npx skills add QianWen-AI/qianwen-ai --skill qianwen-update-check -y","type":"text","marks":[{"type":"code_inline"}]},{"text":", then run: ","type":"text"},{"text":"python3 \u003cthis-skill-scripts-dir>/gossamer.py --dismiss","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User chooses ","type":"text"},{"text":"2","type":"text","marks":[{"type":"strong"}]},{"text":" → run: ","type":"text"},{"text":"python3 \u003cthis-skill-scripts-dir>/gossamer.py --dismiss","type":"text","marks":[{"type":"code_inline"}]},{"text":", then continue normally","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User chooses ","type":"text"},{"text":"3","type":"text","marks":[{"type":"strong"}]},{"text":" → run: ","type":"text"},{"text":"python3 \u003cthis-skill-scripts-dir>/gossamer.py --never-install","type":"text","marks":[{"type":"code_inline"}]},{"text":", then continue normally","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Signal: ","type":"text"},{"text":"[UPDATE_AVAILABLE]","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"When stderr contains this signal, you MUST append a brief update notice to your response, including the version info and the update command shown in the stderr output.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"No signal in stderr","type":"text"}]},{"type":"paragraph","content":[{"text":"If stderr contains neither ","type":"text"},{"text":"[ACTION_REQUIRED]","type":"text","marks":[{"type":"code_inline"}]},{"text":" nor ","type":"text"},{"text":"[UPDATE_AVAILABLE]","type":"text","marks":[{"type":"code_inline"}]},{"text":", no action is needed — the skill is installed and up to date (or cached within 24h).","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Explicit user request","type":"text"}]},{"type":"paragraph","content":[{"text":"When the user explicitly asks to check for updates (e.g. \"check for updates\", \"check version\"):","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Look for ","type":"text"},{"text":"qianwen-update-check/SKILL.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" in sibling skill directories.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If found — run: ","type":"text"},{"text":"python3 \u003cqianwen-update-check-dir>/scripts/check_update.py --print-response","type":"text","marks":[{"type":"code_inline"}]},{"text":" and report the result.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If not found — present the install options above.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"References","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"execution-guide.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/execution-guide.md","title":null}}]},{"text":" — Fallback paths (curl standard/instruct/streaming, code generation, autonomous)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"api-guide.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/api-guide.md","title":null}}]},{"text":" — API supplementary guide","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"sources.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/sources.md","title":null}}]},{"text":" — Official documentation URLs","type":"text"}]}]}]}]},"metadata":{"date":"2026-11-02","name":"qianwen-audio-tts","author":"@skillopedia","source":{"stars":120,"repo_name":"qianwen-ai","origin_url":"https://github.com/QianWen-AI/qianwen-ai/blob/HEAD/skills/audio/qianwen-audio-tts/SKILL.md","repo_owner":"QianWen-AI","body_sha256":"54c728c99269602cd2fc3444dc92056dbbe3b2ae09f9b1e20db0b1c860f75144","cluster_key":"1967409ce8b17f767a8c82c8de32001429f29a917e7c103dbe4092a2503b0ca6","clean_bundle":{"format":"clean-skill-bundle-v1","source":"QianWen-AI/qianwen-ai/skills/audio/qianwen-audio-tts/SKILL.md","text_bytes":118738,"source_commit":"11b1805167d91bc931dbaad85e17036a72e98ae0","skills_sh_rank":3912,"attachment_count":14,"text_attachments":14,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"skills/audio/qianwen-audio-tts/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"media-content","category_label":"Media"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"media-content","import_tag":"skills-sh-top20k-2026-11","description":"Synthesize speech from text with Qwen TTS models. TRIGGER when: user wants to convert text to speech, create voiceovers, generate audio narration, read text aloud, build TTS applications, mentions speech synthesis/voice generation/audio output from text, or explicitly invokes this skill by name (e.g. use qianwen-audio-tts). DO NOT TRIGGER when: user wants speech recognition/ASR, text generation without audio, non-Qwen audio tasks.","compatibility":"Requires Python 3.9+ and curl. Cursor: auto-loaded. Claude Code: read this skill's SKILL.md before first use."}},"renderedAt":1793666767046}

Qwen Audio TTS (Text-to-Speech) Synthesize natural speech from text using Qwen TTS models. This skill is part of QianWen-AI/qianwen-ai . Skill directory Use this skill's internal files to execute and learn. Load reference files on demand when the default path fails or you need details. | Location | Purpose | |----------|---------| | | Qwen TTS (HTTP API) — qwen3-tts- , qwen-audio-3.0-tts- | | | CosyVoice (WebSocket / HTTP NRT) — requires SDK | | | CosyVoice setup, voices, examples, errors | | | Fallback: curl (standard, instruct, streaming), code generation | | | Text formatting for speech, i…