Skill: x-jihua/vidu-speech-generate (version 1.0.0)

--- name: vidu-speech-generate version: 1.0.0 description: "Vidu AI 语音合成与声音复刻。支持303个音色TTS语音合成、声音复刻功能。对话式调用，自动推荐音色。" --- # Vidu Speech Generate 🔊 Vidu AI 语音合成与声音复刻工具，专注于音频生成功能。 ## 环境说明 **变量说明**： - `{baseDir}` - 运行时自动替换为本 skill 目录的绝对路径 - 实际路径：`~/.openclaw/workspace/skills/vidu-speech-generate/` **环境变量**： - `VIDU_API_KEY` - Vidu API 密钥（必需） ## 快速开始 直接告诉我你想生成什么语音，我会自动选择合适的音色： ``` "用少女音配音这段话：大家好，欢迎来到我的频道" "用播报男声朗读这段教程内容" "帮我复刻这个音频的声音" ``` ## 支持的音频类型 | 类型 | 触发条件 | 说明 | |------|----------|------| | TTS语音合成 | "配音"、"朗读"、语音描述 | 文字转语音 | | 声音复刻 | "复刻声音"、"克隆音色" | 根据音频复刻音色 | ## 自动识别规则 ``` 用户输入 → 意图识别 ───────────────────────────── "配音" + 文本 → TTS语音合成 "复刻声音" + 音频 → 声音复刻 ``` ## TTS 语音合成 ### 自动音色推荐 根据内容场景自动选择合适音色： | 场景 | 推荐音色 | Voice ID | |------|----------|----------| | 小红书/短视频（女） | 少女音色 | `female-shaonv` | | 小红书/短视频（男） | 精英青年 | `male-qn-jingying` | | 教程/科普 | 播报男声 | `Chinese (Mandarin)_Male_Announcer` | | 情感/故事 | 御姐音色 | `female-yujie` | | 商务/产品 | 沉稳高管 | `Chinese (Mandarin)_Reliable_Executive` | | 可爱/萌系 | 萌萌女童 | `lovely_girl` | | 搞笑/轻松 | 搞笑大爷 | `Chinese (Mandarin)_Humorous_Elder` | | 温馨/治愈 | 温暖少女 | `Chinese (Mandarin)_Warm_Girl` | | 甜美风格 | 甜美女声 | `Chinese (Mandarin)_Sweet_Lady` | | 专业主持 | 新闻女声 | `Chinese (Mandarin)_News_Anchor` | | 英文内容 | 男声 | `English_Trustworthy_Man` | | 英文内容 | 女声 | `English_Graceful_Lady` | | 日文内容 | 男声 | `Japanese_GentleButler` | | 日文内容 | 女声 | `Japanese_KindLady` | | 韩文内容 | 女声 | `Korean_SweetGirl` | | 韩文内容 | 男声 | `Korean_CheerfulBoyfriend` | ### 使用示例 ``` 用户: 用少女音配音这段话：大家好，欢迎来到我的频道 → 自动选择 female-shaonv → 生成音频文件 用户: 用播报男声朗读这段教程内容 → 自动选择 Chinese (Mandarin)_Male_Announcer 用户: 英文配音：Hello, welcome to my channel → 自动选择 English_Trustworthy_Man ``` ### 停顿控制 使用 `<#x#>` 标记控制停顿（x为秒数）： ``` 你好<#2#>我是vidu<#1#>很高兴见到你 ``` ### 参数说明 | 参数 | 范围 | 默认值 | 说明 | |------|------|--------|------| | speed | 0.5-2.0 | 1.0 | 语速 | | volume | 0-10 | 0 | 音量 | | pitch | -12~12 | 0 | 语调 | | emotion | happy/sad/angry/fearful/disgusted/surprised/calm | - | 情绪 | ## 声音复刻 根据音频样本复刻音色，用于后续TTS。 ### 使用示例 ``` 用户: 帮我复刻这个音频的声音 [发送音频文件] → 创建自定义音色 → 返回 voice_id 供后续使用 ``` ### 要求 - 原音频时长：10秒-5分钟 - 音频清晰，无背景噪音 - 复刻音色为临时音色，7天内需在TTS中调用才能永久保留 ### API 调用 ```bash python3 {baseDir}/scripts/vidu_cli.py voice-clone \ --audio-url sample.mp3 \ --voice-id my_voice_001 \ --text "这是复刻的声音样例" ``` ## API 调用 内部使用 Python CLI 工具： ```bash # TTS语音合成 python3 {baseDir}/scripts/vidu_cli.py tts \ --text "配音文本" \ --voice-id "female-shaonv" # 长文本TTS（避免shell引号问题） python3 {baseDir}/scripts/vidu_cli.py tts \ --text-file long_text.txt \ --voice-id "female-shaonv" # 声音复刻 python3 {baseDir}/scripts/vidu_cli.py voice-clone \ --audio-url sample.mp3 \ --voice-id my_voice \ --text "这是复刻的声音样例" ``` ## 输出规范 1. **下载目录**: `{baseDir}/uploads/` 2. **返回格式**: Markdown 格式引用文件 3. **音频链接**: 必须返回音频 URL 让用户直接访问 ## 环境配置 必需环境变量： ```bash VIDU_API_KEY=your_api_key_here ``` 获取 API Key： - Vidu 官方开放平台：https://platform.vidu.cn 或 https://platform.vidu.com - 注册账号后在「API Keys」页面创建 ## API 域名选择 **重要规则**：根据用户语言自动选择 API 域名 | 用户语言 | API 域名 | 说明 | |---------|---------|------| | 简体中文 | `api.vidu.cn` | 国内用户（默认） | | 其他语言 | `api.vidu.com` | 海外用户 | **Base URL 配置**： ```python # 简体中文用户 BASE_URL = "https://api.vidu.cn/ent/v2" # 非简体中文用户（英文、日文、韩文等） BASE_URL = "https://api.vidu.com/ent/v2" ``` **判断逻辑**： - 用户使用简体中文 → 使用 `api.vidu.cn` - 用户使用其他语言（英文、日文、韩文等） → 使用 `api.vidu.com` ## 错误处理 | 错误 | 原因 | 解决方案 | |------|------|----------| | Invalid API key | API密钥错误 | 检查 VIDU_API_KEY 环境变量 | | Voice ID not found | 音色不存在 | 检查音色列表或重新复刻 | | Task failed | 生成失败 | 查看 error 信息重试 | | Audio too long | 音频过长 | 音频需在10秒-5分钟之间 | ## References - [音色列表](references/voice_id_list.md) - 303个可用音色 - [API参考文档](references/api_reference.md) - 所有API详细参数 ## Rules 1. **API Key 检查**: 调用前确认 `VIDU_API_KEY` 已设置 2. **TTS同步**: TTS 为同步接口，立即返回音频URL 3. **长文本处理**: 文本超过30字符必须使用 `--text-file` 参数 4. **音色保留**: 复刻音色7天内需使用否则删除 5. **返回音频链接**: 必须返回音频 URL 让用户直接访问

path: scripts/vidu_cli.py
content: #!/usr/bin/env python3
"""
Vidu Lite CLI - Simplified video/image/audio generation
Supports: text2video, img2video, ref2video, start-end2video, template, 
          nano-image, text2image, tts, voice-clone

域名选择规则：
- 简体中文用户：api.vidu.cn（默认）
- 非简体中文用户：api.vidu.com
"""

import argparse
import base64
import json
import os
import sys
import time
from pathlib import Path
from typing import Optional
import urllib.request
import urllib.error

# Configuration
# 根据用户语言选择域名：简体中文用 api.vidu.cn，其他语言用 api.vidu.com
BASE_URL = "https://api.vidu.cn/ent/v2"  # 默认国内域名
[REDACTED_SECRET]"VIDU_API_KEY", "")

def get_headers():
    if not [REDACTED_SECRET]"Error: VIDU_API_KEY environment variable not set")
        sys.exit(1)
    return {
        "Content-Type": "application/json",
        "Authorization": f"Token {API_KEY}"
    }

def make_request(endpoint: str, data: dict, method: str = "POST") -> dict:
    """Make HTTP request to Vidu API"""
    url = f"{BASE_URL}{endpoint}"
    headers = get_headers()
    
    req_data = json.dumps(data).encode('utf-8')
    req = urllib.request.Request(url, data=req_data, headers=headers, method=method)
    
    try:
        with urllib.request.urlopen(req, timeout=30) as response:
            return json.loads(response.read().decode('utf-8'))
    except urllib.error.HTTPError as e:
        error_body = e.read().decode('utf-8')
        print(f"HTTP Error {e.code}: {error_body}")
        sys.exit(1)
    except Exception as e:
        print(f"Request failed: {e}")
        sys.exit(1)

def image_to_base64(image_path: str) -> str:
    """Convert local image to base64 data URL"""
    path = Path(image_path)
    if not path.exists():
        print(f"Error: Image file not found: {image_path}")
        sys.exit(1)
    
    ext = path.suffix.lower().lstrip('.')
    if ext == 'jpg':
        ext = 'jpeg'
    
    with open(path, 'rb') as f:
        img_data = f.read()
    
    b64_data = base64.b64encode(img_data).decode('utf-8')
    return f"data:image/{ext};base64,{b64_data}"

def process_image_input(image: str) -> str:
    """Process image input (URL or local file)"""
    if image.startswith('http://') or image.startswith('https://'):
        return image
    elif image.startswith('data:'):
        return image
    else:
        return image_to_base64(image)

def download_file(url: str, output_path: str):
    """Download file from URL"""
    print(f"Downloading to: {output_path}")
    urllib.request.urlretrieve(url, output_path)
    print(f"Download complete: {output_path}")

def print_task_result(result: dict):
    """Print task result in a formatted way"""
    print(f"\n{'='*50}")
    print(f"Task ID: {result.get('task_id') or result.get('id', 'N/A')}")
    print(f"State: {result.get('state', 'N/A')}")
    print(f"Model: {result.get('model', 'N/A')}")
    if result.get('prompt'):
        print(f"Prompt: {result.get('prompt', '')[:50]}...")
    if result.get('credits'):
        print(f"Credits: {result['credits']}")
    if result.get('created_at'):
        print(f"Created: {result['created_at']}")
    if result.get('duration'):
        print(f"Duration: {result['duration']}s")
    print('='*50)

# ==================== Video Generation ====================

def cmd_text2video(args):
    """Text to video generation"""
    data = {
        "model": args.model or "viduq3-pro",
        "prompt": args.prompt,
    }
    
    if args.duration:
        data["duration"] = args.duration
    if args.aspect_ratio:
        data["aspect_ratio"] = args.aspect_ratio
    if args.resolution:
        data["resolution"] = args.resolution
    if hasattr(args, 'audio') and args.audio is not None:
        data["audio"] = args.audio
    if args.seed:
        data["seed"] = args.seed
    if hasattr(args, 'off_peak') and args.off_peak:
        data["off_peak"] = True
    
    result = make_request("/text2video", data)
    print_task_result(result)
    return result

def cmd_img2video(args):
    """Image to video generation"""
    images = [process_image_input(args.image)]
    
    data = {
        "model": args.model or "viduq3-pro",
        "images": images,
    }
    
    if args.prompt:
        data["prompt"] = args.prompt
    if args.duration:
        data["duration"] = args.duration
    if args.resolution:
        data["resolution"] = args.resolution
    if hasattr(args, 'audio') and args.audio is not None:
        data["audio"] = args.audio
    if args.seed:
        data["seed"] = args.seed
    if hasattr(args, 'off_peak') and args.off_peak:
        data["off_peak"] = True
    
    result = make_request("/img2video", data)
    print_task_result(result)
    return result

def cmd_ref2video(args):
    """Reference to video"""
    images = [process_image_input(img) for img in args.images]
    
    data = {
        "model": args.model or "viduq3",
        "images": images,
        "prompt": args.prompt,
    }
    
    if args.duration:
        data["duration"] = args.duration
    if args.resolution:
        data["resolution"] = args.resolution
    if hasattr(args, 'audio') and args.audio:
        data["audio"] = True
    if args.seed:
        data["seed"] = args.seed
    
    result = make_request("/reference2video", data)
    print_task_result(result)
    return result

def cmd_start_end2video(args):
    """Start-end frame to video"""
    images = [
        process_image_input(args.start_frame),
        process_image_input(args.end_frame)
    ]
    
    data = {
        "model": args.model or "viduq3-pro",
        "images": images,
    }
    
    if args.prompt:
        data["prompt"] = args.prompt
    if args.duration:
        data["duration"] = args.duration
    if args.resolution:
        data["resolution"] = args.resolution
    if hasattr(args, 'audio') and args.audio is not None:
        data["audio"] = args.audio
    if args.seed:
        data["seed"] = args.seed
    
    result = make_request("/start-end2video", data)
    print_task_result(result)
    return result

def cmd_template(args):
    """Scene template video generation"""
    data = {
        "template": args.template,
        "images": [process_image_input(args.image)],
    }
    
    if args.prompt:
        data["prompt"] = args.prompt
    if args.seed:
        data["seed"] = args.seed
    if args.bgm:
        data["bgm"] = True
    
    result = make_request("/template", data)
    print_task_result(result)
    return result

# ==================== Image Generation ====================

def cmd_nano_image(args):
    """Image generation (nano endpoint)"""
    data = {
        "model": args.model or "q3-fast",
        "prompt": args.prompt,
    }
    
    if args.images:
        data["images"] = [process_image_input(img) for img in args.images]
    if args.aspect_ratio:
        data["aspect_ratio"] = args.aspect_ratio
    if args.resolution:
        data["resolution"] = args.resolution
    
    result = make_request("/reference2image/nano", data)
    print_task_result(result)
    return result

def cmd_text2image(args):
    """Text/reference to image generation (legacy endpoint)"""
    data = {
        "model": args.model or "viduq2",
        "prompt": args.prompt,
    }
    
    if args.images:
        data["images"] = [process_image_input(img) for img in args.images]
    if args.aspect_ratio:
        data["aspect_ratio"] = args.aspect_ratio
    if args.resolution:
        data["resolution"] = args.resolution
    if args.seed:
        data["seed"] = args.seed
    
    result = make_request("/reference2image", data)
    print_task_result(result)
    return result

# ==================== Audio Generation ====================

def cmd_tts(args):
    """Text-to-speech synthesis"""
    text = args.text
    if args.text_file:
        with open(args.text_file, 'r', encoding='utf-8') as f:
            text = f.read().strip()
    if not text:
        print("Error: --text or --text-file is required")
        sys.exit(1)
    
    data = {
        "text": text,
        "voice_setting_voice_id": args.voice_id,
    }
    
    if args.speed:
        data["voice_setting_speed"] = args.speed
    if args.volume is not None:
        data["voice_setting_volume"] = args.volume
    if args.pitch is not None:
        data["voice_setting_pitch"] = args.pitch
    if args.emotion:
        data["voice_setting_emotion"] = args.emotion
    
    result = make_request("/audio-tts", data)
    print_task_result(result)
    
    # TTS is synchronous, download immediately if URL provided
    if result.get("state") == "success" and result.get("file_url"):
        if args.download:
            download_file(result["file_url"], args.download)
        else:
            print(f"Audio URL: {result['file_url']}")
    
    return result

def cmd_voice_clone(args):
    """Voice cloning"""
    text = args.text
    if args.text_file:
        with open(args.text_file, 'r', encoding='utf-8') as f:
            text = f.read().strip()
    
    data = {
        "audio_url": args.audio_url,
        "voice_id": args.voice_id,
        "text": text,
    }
    
    if args.prompt_audio_url:
        data["prompt_audio_url"] = args.prompt_audio_url
    if args.prompt_text:
        data["prompt_text"] = args.prompt_text
    
    result = make_request("/audio-clone", data)
    print_task_result(result)
    return result

# ==================== Task Management ====================

def get_task_by_id(task_id: str) -> Optional[dict]:
    """Get task by ID"""
    result = make_request("/tasks", {}, method="GET")
    tasks = result.get('tasks', [])
    
    for task in tasks:
        if task.get('id') == task_id:
            return task
    
    next_token = result.get('next_page_token')
    while next_token:
        page_result = make_request(f"/tasks?page_token={next_token}", {}, method="GET")
        for task in page_result.get('tasks', []):
            if task.get('id') == task_id:
                return task
        next_token = page_result.get('next_page_token')
    
    return None

def cmd_status(args):
    """Query task status"""
    task = get_task_by_id(args.task_id)
    
    if not task:
        print(f"Task {args.task_id} not found")
        return None
    
    print_task_result(task)
    
    if task.get('state') == 'success':
        creations = task.get('creations', [])
        if creations:
            creation = creations[0]
            if creation.get('url'):
                print(f"Video URL: {creation['url']}")
            if creation.get('cover_url'):
                print(f"Cover URL: {creation['cover_url']}")
    
    # Wait for completion if requested
    if args.wait and task.get('state') not in ['success', 'failed']:
        print(f"\nWaiting for task completion...")
        while True:
            time.sleep(10)
            task = get_task_by_id(args.task_id)
            if not task:
                print("Task not found")
                break
            state = task.get('state')
            print(f"State: {state}")
            
            if state in ['success', 'failed']:
                break
        
        print_task_result(task)
        
        if task and task.get('state') == 'success' and args.download:
            creations = task.get('creations', [])
            if creations:
                url = creations[0].get('url')
                if url:
                    output_path = os.path.join(args.download, f"vidu_{args.task_id}.mp4")
                    download_file(url, output_path)
    
    return task

def cmd_cancel(args):
    """Cancel/delete a task"""
    result = make_request(f"/tasks/{args.task_id}", {}, method="DELETE")
    print(f"Task {args.task_id} cancelled/deleted")
    return result

# ==================== Main CLI ====================

def main():
    parser = argparse.ArgumentParser(
        description="Vidu Lite CLI - Video/Image/Audio Generation",
        formatter_class=argparse.RawDescriptionHelpFormatter
    )
    
    subparsers = parser.add_subparsers(dest="command", help="Commands")
    
    # Text to video
    p_t2v = subparsers.add_parser("text2video", help="Text to video")
    p_t2v.add_argument("--prompt", required=True, help="Video description")
    p_t2v.add_argument("--model", default="viduq3-pro", help="Model (default: viduq3-pro)")
    p_t2v.add_argument("--duration", type=int, default=5, help="Duration in seconds (default: 5)")
    p_t2v.add_argument("--aspect-ratio", default="16:9", help="Aspect ratio (default: 16:9)")
    p_t2v.add_argument("--resolution", default="720p", help="Resolution (default: 720p)")
    p_t2v.add_argument("--audio", type=lambda x: x.lower() == 'true', help="Enable audio")
    p_t2v.add_argument("--seed", type=int, help="Random seed")
    p_t2v.add_argument("--off-peak", action="store_true", help="Off-peak mode")
    
    # Image to video
    p_i2v = subparsers.add_parser("img2video", help="Image to video")
    p_i2v.add_argument("--image", required=True, help="Input image (URL or local file)")
    p_i2v.add_argument("--prompt", help="Video description")
    p_i2v.add_argument("--model", default="viduq3-pro", help="Model (default: viduq3-pro)")
    p_i2v.add_argument("--duration", type=int, default=5, help="Duration (default: 5)")
    p_i2v.add_argument("--resolution", default="720p", help="Resolution")
    p_i2v.add_argument("--audio", type=lambda x: x.lower() == 'true', help="Enable audio")
    p_i2v.add_argument("--seed", type=int, help="Random seed")
    
    # Reference to video
    p_r2v = subparsers.add_parser("ref2video", help="Reference to video (multi-subject)")
    p_r2v.add_argument("--images", nargs='+', required=True, help="Reference images")
    p_r2v.add_argument("--prompt", required=True, help="Video description")
    p_r2v.add_argument("--model", default="viduq3", help="Model (default: viduq3)")
    p_r2v.add_argument("--duration", type=int, default=5, help="Duration")
    p_r2v.add_argument("--resolution", default="720p", help="Resolution")
    p_r2v.add_argument("--audio", action="store_true", help="Enable audio")
    p_r2v.add_argument("--seed", type=int, help="Random seed")
    
    # Start-end to video
    p_se = subparsers.add_parser("start-end2video", help="Start-end frame to video")
    p_se.add_argument("--start-frame", required=True, help="Start frame image")
    p_se.add_argument("--end-frame", required=True, help="End frame image")
    p_se.add_argument("--prompt", help="Video description")
    p_se.add_argument("--model", default="viduq3-pro", help="Model (default: viduq3-pro)")
    p_se.add_argument("--duration", type=int, default=5, help="Duration")
    p_se.add_argument("--resolution", default="720p", help="Resolution")
    p_se.add_argument("--audio", type=lambda x: x.lower() == 'true', help="Enable audio")
    p_se.add_argument("--seed", type=int, help="Random seed")
    
    # Template
    p_tpl = subparsers.add_parser("template", help="Scene template video")
    p_tpl.add_argument("--template", required=True, help="Template name")
    p_tpl.add_argument("--image", required=True, help="Input image")
    p_tpl.add_argument("--prompt", help="Additional prompt")
    p_tpl.add_argument("--seed", type=int, help="Random seed")
    p_tpl.add_argument("--bgm", action="store_true", help="Add BGM")
    
    # Nano image (recommended)
    p_nano = subparsers.add_parser("nano-image", help="Image generation (q3-fast)")
    p_nano.add_argument("--prompt", required=True, help="Image description")
    p_nano.add_argument("--images", nargs='+', help="Reference images (optional)")
    p_nano.add_argument("--model", default="q3-fast", help="Model: q3-fast, q2-fast, q2-pro")
    p_nano.add_argument("--aspect-ratio", default="16:9", help="Aspect ratio")
    p_nano.add_argument("--resolution", default="2K", help="Resolution: 1K, 2K, 4K")
    
    # Text to image (legacy)
    p_t2i = subparsers.add_parser("text2image", help="Image generation (legacy)")
    p_t2i.add_argument("--prompt", required=True, help="Image description")
    p_t2i.add_argument("--images", nargs='+', help="Reference images")
    p_t2i.add_argument("--model", default="viduq2", help="Model")
    p_t2i.add_argument("--aspect-ratio", default="16:9", help="Aspect ratio")
    p_t2i.add_argument("--resolution", default="1080p", help="Resolution")
    p_t2i.add_argument("--seed", type=int, help="Random seed")
    
    # TTS
    p_tts = subparsers.add_parser("tts", help="Text-to-speech")
    p_tts.add_argument("--text", help="Text to synthesize (use --text-file for long texts)")
    p_tts.add_argument("--text-file", help="Path to text file (avoids shell quoting issues)")
    p_tts.add_argument("--voice-id", required=True, help="Voice ID (see references/voice_id_list.md)")
    p_tts.add_argument("--speed", type=float, help="Speech speed (0.5-2.0)")
    p_tts.add_argument("--volume", type=int, help="Volume (0-10)")
    p_tts.add_argument("--pitch", type=int, help="Pitch (-12 to 12)")
    p_tts.add_argument("--emotion", help="Emotion (happy, sad, angry, etc.)")
    p_tts.add_argument("--download", help="Download path")
    
    # Voice clone
    p_vc = subparsers.add_parser("voice-clone", help="Voice cloning")
    p_vc.add_argument("--audio-url", required=True, help="Source audio URL")
    p_vc.add_argument("--voice-id", required=True, help="Custom voice ID")
    p_vc.add_argument("--text", help="Demo text")
    p_vc.add_argument("--text-file", help="Path to text file")
    p_vc.add_argument("--prompt-audio-url", help="Reference audio URL")
    p_vc.add_argument("--prompt-text", help="Reference audio text")
    
    # Status
    p_status = subparsers.add_parser("status", help="Query task status")
    p_status.add_argument("task_id", help="Task ID")
    p_status.add_argument("--wait", action="store_true", help="Wait for completion")
    p_status.add_argument("--download", help="Download directory")
    
    # Cancel
    p_cancel = subparsers.add_parser("cancel", help="Cancel task")
    p_cancel.add_argument("task_id", help="Task ID")
    
    args = parser.parse_args()
    
    if not args.command:
        parser.print_help()
        sys.exit(1)
    
    # Execute command
    commands = {
        "text2video": cmd_text2video,
        "img2video": cmd_img2video,
        "ref2video": cmd_ref2video,
        "start-end2video": cmd_start_end2video,
        "template": cmd_template,
        "nano-image": cmd_nano_image,
        "text2image": cmd_text2image,
        "tts": cmd_tts,
        "voice-clone": cmd_voice_clone,
        "status": cmd_status,
        "cancel": cmd_cancel,
    }
    
    commands[args.command](args)

if __name__ == "__main__":
    main()


path: references/api_reference.md
content: # Vidu Speech Generate API Reference

语音合成与声音复刻 API 参考，包含TTS和声音复刻功能。

## Base URL

```
https://api.vidu.cn/ent/v2
```

**域名选择规则**：
- **简体中文用户**：使用 `api.vidu.cn`
- **非简体中文用户**：使用 `api.vidu.com`

根据用户交流语言自动切换域名。

## Authentication

```
Authorization: Token YOUR_API_KEY
```

---

## 音频生成 API

### 1. TTS 语音合成

**POST** `/audio-tts`

```json
{
  "text": "要配音的文本",
  "voice_setting_voice_id": "female-shaonv",
  "voice_setting_speed": 1.0,
  "voice_setting_volume": 0,
  "voice_setting_pitch": 0,
  "voice_setting_emotion": "calm"
}
```

**参数说明**：
| 参数 | 类型 | 范围 | 说明 |
|------|------|------|------|
| text | string | - | 要合成的文本 |
| voice_setting_voice_id | string | - | 音色ID，见 voice_id_list.md |
| voice_setting_speed | float | 0.5-2.0 | 语速，默认1.0 |
| voice_setting_volume | int | 0-10 | 音量，默认0 |
| voice_setting_pitch | int | -12~12 | 语调，默认0 |
| voice_setting_emotion | string | - | 情绪：happy/sad/angry/fearful/disgusted/surprised/calm |

**返回**：同步返回音频URL

**停顿控制**：
使用 `<#x#>` 标记控制停顿（x为秒数）：

```json
{
  "text": "你好<#2#>我是vidu<#1#>很高兴见到你",
  "voice_setting_voice_id": "female-shaonv"
}
```

**热门音色**：
| 音色类型 | Voice ID | 适用场景 |
|---------|---------|---------|
| 少女音色 | `female-shaonv` | 小红书/短视频 |
| 精英青年 | `male-qn-jingying` | 小红书/短视频（男） |
| 播报男声 | `Chinese (Mandarin)_Male_Announcer` | 教程/科普 |
| 御姐音色 | `female-yujie` | 情感/故事 |
| 温暖少女 | `Chinese (Mandarin)_Warm_Girl` | 温馨/治愈 |

---

### 2. 声音复刻

**POST** `/audio-clone`

```json
{
  "audio_url": "源音频URL",
  "voice_id": "自定义音色ID",
  "text": "试听文本",
  "prompt_audio_url": "参考音频URL（可选）",
  "prompt_text": "参考音频对应文本（可选）"
}
```

**参数说明**：
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| audio_url | string | ✅ | 源音频URL（10秒-5分钟） |
| voice_id | string | ✅ | 自定义音色ID |
| text | string | ✅ | 试听文本 |
| prompt_audio_url | string | ❌ | 参考音频URL |
| prompt_text | string | ❌ | 参考音频对应文本 |

**要求**：
- 源音频时长：10秒-5分钟
- 音频清晰，无背景噪音
- 复刻音色7天内需使用才能保留

**返回**：复刻音色ID

---

## 响应格式

### TTS 响应

```json
{
  "state": "success",
  "file_url": "https://...",
  "duration": 10.5
}
```

### 声音复刻响应

```json
{
  "state": "success",
  "voice_id": "my_voice_001",
  "demo_url": "https://..."
}
```

---

## 错误码

| 错误 | 说明 |
|------|------|
| Invalid API key | API 密钥错误 |
| Voice ID not found | 音色不存在 |
| Audio too long | 音频过长（超过5分钟） |
| Audio too short | 音频过短（少于10秒） |
| Task failed | 生成失败 |

---

## 使用注意事项

### TTS 使用

1. **文本长度**：超过30字符建议使用 `--text-file` 参数
2. **音色选择**：根据场景自动推荐音色
3. **停顿控制**：使用 `<#x#>` 标记
4. **情绪设置**：可选 happy/sad/angry/fearful/disgusted/surprised/calm

### 声音复刻

1. **音频质量**：必须清晰，无背景噪音
2. **音频时长**：10秒-5分钟
3. **音色保留**：7天内需使用，否则删除
4. **音色ID**：自定义，建议使用有意义的前缀

---

## 参数速查

### TTS参数

| 参数 | 可选值 | 默认值 |
|------|--------|--------|
| speed | 0.5-2.0 | 1.0 |
| volume | 0-10 | 0 |
| pitch | -12~12 | 0 |
| emotion | happy/sad/angry/fearful/disgusted/surprised/calm | calm |

### 音色列表

**完整音色列表**: 见 `voice_id_list.md` (303个音色)

包含：
- 中文音色（少女、御姐、精英、播报等）
- 英文音色（男声、女声）
- 日文音色（男声、女声）
- 韩文音色（男声、女声）
- 特色音色（萌系、搞笑、温馨等）


path: references/voice_id_list.md
content: # Vidu TTS 音色列表

共 303 个音色，覆盖 11 种语言。

## 中文（普通话）- 58个

| Voice ID | 音色名称 |
|----------|----------|
| male-qn-qingse | 青涩青年音色 |
| male-qn-jingying | 精英青年音色 |
| male-qn-badao | 霸道青年音色 |
| male-qn-daxuesheng | 青年大学生音色 |
| female-shaonv | 少女音色 |
| female-yujie | 御姐音色 |
| female-chengshu | 成熟女性音色 |
| female-tianmei | 甜美女性音色 |
| male-qn-qingse-jingpin | 青涩青年音色-beta |
| male-qn-jingying-jingpin | 精英青年音色-beta |
| male-qn-badao-jingpin | 霸道青年音色-beta |
| male-qn-daxuesheng-jingpin | 青年大学生音色-beta |
| female-shaonv-jingpin | 少女音色-beta |
| female-yujie-jingpin | 御姐音色-beta |
| female-chengshu-jingpin | 成熟女性音色-beta |
| female-tianmei-jingpin | 甜美女性音色-beta |
| clever_boy | 聪明男童 |
| cute_boy | 可爱男童 |
| lovely_girl | 萌萌女童 |
| cartoon_pig | 卡通猪小琪 |
| bingjiao_didi | 病娇弟弟 |
| junlang_nanyou | 俊朗男友 |
| chunzhen_xuedi | 纯真学弟 |
| lengdan_xiongzhang | 冷淡学长 |
| badao_shaoye | 霸道少爷 |
| tianxin_xiaoling | 甜心小玲 |
| qiaopi_mengmei | 俏皮萌妹 |
| wumei_yujie | 妩媚御姐 |
| diadia_xuemei | 嗲嗲学妹 |
| danya_xuejie | 淡雅学姐 |
| Chinese (Mandarin)_Reliable_Executive | 沉稳高管 |
| Chinese (Mandarin)_News_Anchor | 新闻女声 |
| Chinese (Mandarin)_Mature_Woman | 傲娇御姐 |
| Chinese (Mandarin)_Unrestrained_Young_Man | 不羁青年 |
| Arrogant_Miss | 嚣张小姐 |
| Robot_Armor | 机械战甲 |
| Chinese (Mandarin)_Kind-hearted_Antie | 热心大婶 |
| Chinese (Mandarin)_HK_Flight_Attendant | 港普空姐 |
| Chinese (Mandarin)_Humorous_Elder | 搞笑大爷 |
| Chinese (Mandarin)_Gentleman | 温润男声 |
| Chinese (Mandarin)_Warm_Bestie | 温暖闺蜜 |
| Chinese (Mandarin)_Male_Announcer | 播报男声 |
| Chinese (Mandarin)_Sweet_Lady | 甜美女声 |
| Chinese (Mandarin)_Southern_Young_Man | 南方小哥 |
| Chinese (Mandarin)_Wise_Women | 阅历姐姐 |
| Chinese (Mandarin)_Gentle_Youth | 温润青年 |
| Chinese (Mandarin)_Warm_Girl | 温暖少女 |
| Chinese (Mandarin)_Kind-hearted_Elder | 花甲奶奶 |
| Chinese (Mandarin)_Cute_Spirit | 憨憨萌兽 |
| Chinese (Mandarin)_Radio_Host | 电台男主播 |
| Chinese (Mandarin)_Lyrical_Voice | 抒情男声 |
| Chinese (Mandarin)_Straightforward_Boy | 率真弟弟 |
| Chinese (Mandarin)_Sincere_Adult | 真诚青年 |
| Chinese (Mandarin)_Gentle_Senior | 温柔学姐 |
| Chinese (Mandarin)_Stubborn_Friend | 嘴硬竹马 |
| Chinese (Mandarin)_Crisp_Girl | 清脆少女 |
| Chinese (Mandarin)_Pure-hearted_Boy | 清澈邻家弟弟 |
| Chinese (Mandarin)_Soft_Girl | 软软女孩 |

## 中文（粤语）- 6个

| Voice ID | 音色名称 |
|----------|----------|
| Cantonese_ProfessionalHost（F) | 专业女主持 |
| Cantonese_GentleLady | 温柔女声 |
| Cantonese_ProfessionalHost（M) | 专业男主持 |
| Cantonese_PlayfulMan | 活泼男声 |
| Cantonese_CuteGirl | 可爱女孩 |
| Cantonese_KindWoman | 善良女声 |

## 英文 - 15个

| Voice ID | 音色名称 |
|----------|----------|
| Grinch | Grinch |
| Rudolph | Rudolph |
| Arnold | Arnold |
| Charming_Santa | Charming Santa |
| Charming_Lady | Charming Lady |
| Sweet_Girl | Sweet Girl |
| Cute_Elf | Cute Elf |
| Attractive_Girl | Attractive Girl |
| Serene_Woman | Serene Woman |
| English_Trustworthy_Man | Trustworthy Man |
| English_Graceful_Lady | Graceful Lady |
| English_Aussie_Bloke | Aussie Bloke |
| English_Whispering_girl | Whispering girl |
| English_Diligent_Man | Diligent Man |
| English_Gentle-voiced_man | Gentle-voiced man |

## 日文 - 15个

| Voice ID | 音色名称 |
|----------|----------|
| Japanese_IntellectualSenior | Intellectual Senior |
| Japanese_DecisivePrincess | Decisive Princess |
| Japanese_LoyalKnight | Loyal Knight |
| Japanese_DominantMan | Dominant Man |
| Japanese_SeriousCommander | Serious Commander |
| Japanese_ColdQueen | Cold Queen |
| Japanese_DependableWoman | Dependable Woman |
| Japanese_GentleButler | Gentle Butler |
| Japanese_KindLady | Kind Lady |
| Japanese_CalmLady | Calm Lady |
| Japanese_OptimisticYouth | Optimistic Youth |
| Japanese_GenerousIzakayaOwner | Generous Izakaya Owner |
| Japanese_SportyStudent | Sporty Student |
| Japanese_InnocentBoy | Innocent Boy |
| Japanese_GracefulMaiden | Graceful Maiden |

## 韩文 - 48个

| Voice ID | 音色名称 |
|----------|----------|
| Korean_SweetGirl | Sweet Girl |
| Korean_CheerfulBoyfriend | Cheerful Boyfriend |
| Korean_EnchantingSister | Enchanting Sister |
| Korean_ShyGirl | Shy Girl |
| Korean_ReliableSister | Reliable Sister |
| Korean_StrictBoss | Strict Boss |
| Korean_SassyGirl | Sassy Girl |
| Korean_ChildhoodFriendGirl | Childhood Friend Girl |
| Korean_PlayboyCharmer | Playboy Charmer |
| Korean_ElegantPrincess | Elegant Princess |
| Korean_BraveFemaleWarrior | Brave Female Warrior |
| Korean_BraveYouth | Brave Youth |
| Korean_CalmLady | Calm Lady |
| Korean_EnthusiasticTeen | Enthusiastic Teen |
| Korean_SoothingLady | Soothing Lady |
| Korean_IntellectualSenior | Intellectual Senior |
| Korean_LonelyWarrior | Lonely Warrior |
| Korean_MatureLady | Mature Lady |
| Korean_InnocentBoy | Innocent Boy |
| Korean_CharmingSister | Charming Sister |
| Korean_AthleticStudent | Athletic Student |
| Korean_BraveAdventurer | Brave Adventurer |
| Korean_CalmGentleman | Calm Gentleman |
| Korean_WiseElf | Wise Elf |
| Korean_CheerfulCoolJunior | Cheerful Cool Junior |
| Korean_DecisiveQueen | Decisive Queen |
| Korean_ColdYoungMan | Cold Young Man |
| Korean_MysteriousGirl | Mysterious Girl |
| Korean_QuirkyGirl | Quirky Girl |
| Korean_ConsiderateSenior | Considerate Senior |
| Korean_CheerfulLittleSister | Cheerful Little Sister |
| Korean_DominantMan | Dominant Man |
| Korean_AirheadedGirl | Airheaded Girl |
| Korean_ReliableYouth | Reliable Youth |
| Korean_FriendlyBigSister | Friendly Big Sister |
| Korean_GentleBoss | Gentle Boss |
| Korean_ColdGirl | Cold Girl |
| Korean_HaughtyLady | Haughty Lady |
| Korean_CharmingElderSister | Charming Elder Sister |
| Korean_IntellectualMan | Intellectual Man |
| Korean_CaringWoman | Caring Woman |
| Korean_WiseTeacher | Wise Teacher |
| Korean_ConfidentBoss | Confident Boss |
| Korean_AthleticGirl | Athletic Girl |
| Korean_PossessiveMan | Possessive Man |
| Korean_GentleWoman | Gentle Woman |
| Korean_CockyGuy | Cocky Guy |
| Korean_ThoughtfulWoman | Thoughtful Woman |
| Korean_OptimisticYouth | Optimistic Youth |

## 西班牙文 - 47个

| Voice ID | 音色名称 |
|----------|----------|
| Spanish_SereneWoman | Serene Woman |
| Spanish_MaturePartner | Mature Partner |
| Spanish_CaptivatingStoryteller | Captivating Storyteller |
| Spanish_Narrator | Narrator |
| Spanish_WiseScholar | Wise Scholar |
| Spanish_Kind-heartedGirl | Kind-hearted Girl |
| Spanish_DeterminedManager | Determined Manager |
| Spanish_BossyLeader | Bossy Leader |
| Spanish_ReservedYoungMan | Reserved Young Man |
| Spanish_ConfidentWoman | Confident Woman |
| Spanish_ThoughtfulMan | Thoughtful Man |
| Spanish_Strong-WilledBoy | Strong-willed Boy |
| Spanish_SophisticatedLady | Sophisticated Lady |
| Spanish_RationalMan | Rational Man |
| Spanish_AnimeCharacter | Anime Character |
| Spanish_Deep-tonedMan | Deep-toned Man |
| Spanish_Fussyhostess | Fussy hostess |
| Spanish_SincereTeen | Sincere Teen |
| Spanish_FrankLady | Frank Lady |
| Spanish_Comedian | Comedian |
| Spanish_Debator | Debator |
| Spanish_ToughBoss | Tough Boss |
| Spanish_Wiselady | Wise Lady |
| Spanish_Steadymentor | Steady Mentor |
| Spanish_Jovialman | Jovial Man |
| Spanish_SantaClaus | Santa Claus |
| Spanish_Rudolph | Rudolph |
| Spanish_Intonategirl | Intonate Girl |
| Spanish_Arnold | Arnold |
| Spanish_Ghost | Ghost |
| Spanish_HumorousElder | Humorous Elder |
| Spanish_EnergeticBoy | Energetic Boy |
| Spanish_WhimsicalGirl | Whimsical Girl |
| Spanish_StrictBoss | Strict Boss |
| Spanish_ReliableMan | Reliable Man |
| Spanish_SereneElder | Serene Elder |
| Spanish_AngryMan | Angry Man |
| Spanish_AssertiveQueen | Assertive Queen |
| Spanish_CaringGirlfriend | Caring Girlfriend |
| Spanish_PowerfulSoldier | Powerful Soldier |
| Spanish_PassionateWarrior | Passionate Warrior |
| Spanish_ChattyGirl | Chatty Girl |
| Spanish_RomanticHusband | Romantic Husband |
| Spanish_CompellingGirl | Compelling Girl |
| Spanish_PowerfulVeteran | Powerful Veteran |
| Spanish_SensibleManager | Sensible Manager |
| Spanish_ThoughtfulLady | Thoughtful Lady |

## 葡萄牙文 - 65个

（完整列表见源文件）

## 法文 - 6个

| Voice ID | 音色名称 |
|----------|----------|
| French_Male_Speech_New | Level-Headed Man |
| French_Female_News Anchor | Patient Female Presenter |
| French_CasualMan | Casual Man |
| French_MovieLeadFemale | Movie Lead Female |
| French_FemaleAnchor | Female Anchor |
| French_MaleNarrator | Male Narrator |

## 印尼文 - 9个

| Voice ID | 音色名称 |
|----------|----------|
| Indonesian_SweetGirl | Sweet Girl |
| Indonesian_ReservedYoungMan | Reserved Young Man |
| Indonesian_CharmingGirl | Charming Girl |
| Indonesian_CalmWoman | Calm Woman |
| Indonesian_ConfidentWoman | Confident Woman |
| Indonesian_CaringMan | Caring Man |
| Indonesian_BossyLeader | Bossy Leader |
| Indonesian_DeterminedBoy | Determined Boy |
| Indonesian_GentleGirl | Gentle Girl |

## 德文 - 3个

| Voice ID | 音色名称 |
|----------|----------|
| German_FriendlyMan | Friendly Man |
| German_SweetLady | Sweet Lady |
| German_PlayfulMan | Playful Man |

## 俄文 - 8个

| Voice ID | 音色名称 |
|----------|----------|
| Russian_HandsomeChildhoodFriend | Handsome Childhood Friend |
| Russian_BrightHeroine | Bright Queen |
| Russian_AmbitiousWoman | Ambitious Woman |
| Russian_ReliableMan | Reliable Man |
| Russian_CrazyQueen | Crazy Girl |
| Russian_PessimisticGirl | Pessimistic Girl |
| Russian_AttractiveGuy | Attractive Guy |
| Russian_Bad-temperedBoy | Bad-tempered Boy |

## 意大利文 - 4个

| Voice ID | 音色名称 |
|----------|----------|
| Italian_BraveHeroine | Brave Heroine |
| Italian_Narrator | Narrator |
| Italian_WanderingSorcerer | Wandering Sorcerer |
| Italian_DiligentLeader | Diligent Leader |

## 阿拉伯文 - 2个

| Voice ID | 音色名称 |
|----------|----------|
| Arabic_CalmWoman | Calm Woman |
| Arabic_FriendlyGuy | Friendly Guy |

## 土耳其文 - 2个

| Voice ID | 音色名称 |
|----------|----------|
| Turkish_CalmWoman | Calm Woman |
| Turkish_Trustworthyman | Trustworthy man |

## 乌克兰文 - 2个

| Voice ID | 音色名称 |
|----------|----------|
| Ukrainian_CalmWoman | Calm Woman |
| Ukrainian_WiseScholar | Wise Scholar |

## 荷兰文 - 2个

| Voice ID | 音色名称 |
|----------|----------|
| Dutch_kindhearted_girl | Kind-hearted girl |
| Dutch_bossy_leader | Bossy leader |

## 越南文 - 1个

| Voice ID | 音色名称 |
|----------|----------|
| Vietnamese_kindhearted_girl | Kind-hearted girl |

---

## 使用方式

```bash
python3 vidu_cli.py tts \
  --text "要配音的文本" \
  --voice-id "female-shaonv" \
  --speed 1.0
```

## 常用推荐

| 场景 | 推荐Voice ID | 说明 |
|------|-------------|------|
| 小红书女声 | female-shaonv | 少女音色 |
| 小红书男声 | male-qn-jingying | 精英青年 |
| 教程解说 | Chinese (Mandarin)_Male_Announcer | 播报男声 |
| 情感类 | female-yujie | 御姐音色 |
| 可爱风格 | lovely_girl | 萌萌女童 |
| 专业商务 | Chinese (Mandarin)_Reliable_Executive | 沉稳高管 |


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector:  ()