CosyVoice命令行工具开发教程:快速语音合成脚本编写

你是不是也遇到过这样的场景?手头有一堆文本需要转成语音,比如给视频配音、制作有声书,或者批量生成客服语音。每次都打开网页、复制粘贴、点击生成,不仅效率低下,还容易出错。

今天,我们就来动手解决这个问题。我将带你一步步编写一个轻量级的命令行工具,把CosyVoice的语音合成能力“装进”你的终端。这个工具能让你像执行 lscp 命令一样,轻松完成批量语音合成。整个过程不需要复杂的界面,只需要几行命令,非常适合集成到自动化流程中,或者作为你日常工具箱里的一件趁手兵器。

1. 准备工作:环境和思路

在开始敲代码之前,我们先花几分钟把环境和思路理清楚。这能让你后面的开发过程更顺畅。

1.1 你需要准备什么

首先,确保你的电脑上已经安装了Python。打开终端(Windows上是命令提示符或PowerShell),输入 python --versionpython3 --version,能看到版本号就行,建议是Python 3.7或更高版本。

接下来,我们需要安装几个必要的Python库。核心是 requests,用来和CosyVoice的API“对话”。另外,我们还会用到 argparse(Python自带的,不用额外装)来处理命令行参数,以及 tqdm 来显示一个漂亮的进度条。

打开终端,运行下面这行命令来安装它们:

pip install requests tqdm

如果安装速度慢,可以试试加上国内的镜像源,比如 -i https://pypi.tuna.tsinghua.edu.cn/simple

最后,也是最重要的一步:你需要有一个可用的CosyVoice API访问密钥(通常叫API Key或Token)。这个密钥就像是你的身份证,告诉API服务器“我是谁,我有权限使用服务”。请确保你已经从服务提供商那里获取了它,并知道API的调用地址(Endpoint)。

1.2 工具设计思路

我们的工具要做什么?很简单:接收用户输入的文字,调用CosyVoice API,把返回的音频数据保存成文件。

具体来说,我们希望工具能这样用:

  • 基本用法python cosyvoice_cli.py --text "你好,世界" --output hello.wav
  • 从文件读取python cosyvoice_cli.py --input text.txt --output speech.wav
  • 指定音色:加上 --voice 参数来选择不同的发音人。
  • 批量处理:如果输入文件里有多行文字,就自动生成多个音频文件。

基于这个目标,我们的脚本大概会包含这几个部分:

  1. 参数解析:用 argparse 来读懂用户在命令行里输入的各种指令。
  2. 文本处理:无论是直接输入的文字还是从文件读取的文字,都统一处理好。
  3. API调用:构造请求,发送给CosyVoice服务器,并安全地接收返回的音频数据。
  4. 文件保存:把收到的音频数据写入到用户指定的文件里。
  5. 错误处理与反馈:万一网络不好或者API出错,要给用户一个明白的提示,而不是一堆看不懂的代码报错。

思路清晰了,我们就可以开始动手了。

2. 搭建骨架:解析命令行参数

任何命令行工具的第一步,都是让程序能“听懂”用户的指令。在Python里,argparse 库就是干这个的,它功能强大又简单易用。

我们来创建一个新的Python文件,比如叫 cosyvoice_cli.py,然后开始写代码。

import argparse

def main():
    # 1. 创建参数解析器,并给它一个简单的描述
    parser = argparse.ArgumentParser(
        description='一个轻量级的CosyVoice语音合成命令行工具。'
    )

    # 2. 定义工具需要接受的参数
    # 文本输入参数:用户可以直接输入一段文字
    parser.add_argument(
        '--text',
        type=str,
        help='直接提供要合成的文本内容。例如:--text "欢迎使用CosyVoice"'
    )

    # 文件输入参数:用户也可以提供一个文本文件的路径
    parser.add_argument(
        '--input',
        type=str,
        help='包含待合成文本的文件的路径。文件内容可以是一行或多行文本。'
    )

    # 输出参数:用户指定合成后的音频文件保存到哪里
    parser.add_argument(
        '--output',
        type=str,
        required=True, # 这个参数是必须提供的
        help='输出音频文件的路径。例如:--output output.wav。如果输入是多行文本,此参数被视为前缀。'
    )

    # 音色参数:让用户选择喜欢的声音
    parser.add_argument(
        '--voice',
        type=str,
        default='default', # 如果不指定,就使用默认音色
        help='指定合成音色。具体可选值请参考CosyVoice API文档。'
    )

    # 3. 解析用户实际输入的命令行参数
    args = parser.parse_args()

    # 4. 简单的参数逻辑检查
    # 用户必须至少提供 --text 或 --input 中的一个,不能两个都没有
    if not args.text and not args.input:
        parser.error('必须提供 --text 或 --input 参数中的一个以指定合成文本。')

    # 打印解析到的参数,方便调试(正式版可以去掉)
    print(f"文本输入: {args.text}")
    print(f"文件输入: {args.input}")
    print(f"输出路径: {args.output}")
    print(f"选择音色: {args.voice}")

if __name__ == '__main__':
    main()

把上面这段代码保存,然后在终端里试试它的效果:

# 测试直接输入文本
python cosyvoice_cli.py --text "测试一下" --output test.wav --voice female_01

# 测试从文件输入(假设你有个叫 script.txt 的文件)
python cosyvoice_cli.py --input script.txt --output chapter.wav

# 测试缺少必要参数(应该会报错)
python cosyvoice_cli.py --output test.wav

你会看到程序已经能正确识别你输入的参数了。这个“骨架”虽然现在什么实际功能都没有,但它已经具备了和用户交互的能力。接下来,我们就要为它填充“血肉”。

3. 核心功能:调用API与保存音频

骨架搭好了,现在来实现最核心的部分:与CosyVoice API通信。这里会涉及到网络请求和文件操作,我会把每一步都讲清楚。

3.1 编写API调用函数

我们写一个专门的函数来处理合成请求。这个函数需要知道:用什么文本合成、用什么音色、以及你的API密钥和地址。

import requests
import json

def synthesize_speech(text, voice, api_key, api_url):
    """
    调用CosyVoice API进行语音合成。

    参数:
        text (str): 要合成的文本。
        voice (str): 音色名称。
        api_key (str): API认证密钥。
        api_url (str): API端点地址。

    返回:
        bytes: 成功时返回音频的二进制数据。
        None: 失败时返回None。
    """
    # 1. 准备请求头,通常需要包含认证信息和内容类型
    headers = {
        'Authorization': f'Bearer {api_key}', # 常见的认证方式,具体请参照你的API文档
        'Content-Type': 'application/json',
    }

    # 2. 准备请求体(Payload),也就是我们要发送给API的数据
    payload = {
        'text': text,
        'voice': voice,
        # 这里可能还有其他参数,如语速、音调等,请根据实际API文档添加
        # 'speed': 1.0,
        # 'pitch': 0,
    }

    try:
        # 3. 发送POST请求
        print(f"正在合成: {text[:50]}...") # 只打印前50个字符,避免刷屏
        response = requests.post(api_url, headers=headers, json=payload, timeout=30)

        # 4. 检查响应状态
        response.raise_for_status() # 如果状态码不是200,会抛出异常

        # 5. 处理响应
        # 假设API成功时直接返回音频二进制流(Content-Type是audio/wav等)
        if 'audio' in response.headers.get('Content-Type', ''):
            return response.content
        else:
            # 有些API可能返回一个包含音频数据的JSON,需要解析
            # 这里假设直接返回二进制数据,实际情况请调整
            print("警告:响应内容类型不是音频。尝试直接读取内容。")
            return response.content

    except requests.exceptions.RequestException as e:
        # 处理网络请求相关的错误(超时、连接错误等)
        print(f"网络请求失败: {e}")
        return None
    except Exception as e:
        # 处理其他意外错误
        print(f"合成过程中发生错误: {e}")
        return None

关键点说明:

  • headersAuthorization 字段是携带API密钥最常见的方式,格式可能是 Bearer {你的API_KEY} 或简单的 {你的API_KEY},务必查看你的CosyVoice服务商提供的文档。
  • payload:这个字典里的内容必须完全按照API文档的要求来填写。textvoice 是必须的,其他如 speed(语速)、pitch(音调)等是可选的增强参数。
  • 错误处理:我们用 try...except 包裹了请求过程。网络不稳定、API服务异常、密钥错误等情况都可能发生,良好的错误处理能让工具更健壮,给用户明确的反馈,而不是直接崩溃。

3.2 保存音频文件

拿到API返回的音频数据(二进制字节流)后,保存到本地就很简单了。

def save_audio(audio_data, filepath):
    """
    将音频二进制数据保存到文件。

    参数:
        audio_data (bytes): 音频数据。
        filepath (str): 要保存的文件路径。
    """
    if audio_data:
        try:
            with open(filepath, 'wb') as f: # 'wb' 表示以二进制写入模式打开文件
                f.write(audio_data)
            print(f"音频已成功保存至: {filepath}")
        except IOError as e:
            print(f"文件保存失败: {e}")
    else:
        print("无音频数据,保存失败。")

3.3 将它们组装到主函数里

现在,我们需要修改之前的 main() 函数,把参数解析、API调用和文件保存串联起来。同时,我们需要处理从文件读取多行文本的情况。

def main():
    parser = argparse.ArgumentParser(description='一个轻量级的CosyVoice语音合成命令行工具。')
    parser.add_argument('--text', type=str, help='直接提供要合成的文本内容。')
    parser.add_argument('--input', type=str, help='包含待合成文本的文件的路径。')
    parser.add_argument('--output', type=str, required=True, help='输出音频文件的路径。')
    parser.add_argument('--voice', type=str, default='default', help='指定合成音色。')

    args = parser.parse_args()

    if not args.text and not args.input:
        parser.error('必须提供 --text 或 --input 参数中的一个以指定合成文本。')

    # --- 在这里添加你的实际API配置 ---
    API_KEY = "YOUR_API_KEY_HERE"  # 替换成你的真实API密钥
    API_URL = "https://api.example.com/v1/synthesize"  # 替换成真实的API地址
    # ---------------------------------

    # 准备要合成的文本列表
    texts_to_synthesize = []

    if args.text:
        # 如果直接提供了--text,就合成这一句
        texts_to_synthesize.append(args.text)
        output_paths = [args.output] # 输出文件就是用户指定的那个
    elif args.input:
        # 如果提供了--input,就从文件读取
        try:
            with open(args.input, 'r', encoding='utf-8') as f:
                # 读取所有行,过滤掉空行
                texts_to_synthesize = [line.strip() for line in f if line.strip()]
        except IOError as e:
            print(f"无法读取输入文件: {e}")
            return

        # 生成多个输出文件名
        # 例如:用户指定 --output result.wav,我们生成 result_1.wav, result_2.wav...
        base_name = args.output.rsplit('.', 1)[0] # 去掉扩展名
        extension = args.output.rsplit('.', 1)[1] if '.' in args.output else 'wav'
        output_paths = [f"{base_name}_{i+1}.{extension}" for i in range(len(texts_to_synthesize))]

    # 核心循环:逐句合成并保存
    for i, (text, output_path) in enumerate(zip(texts_to_synthesize, output_paths)):
        print(f"\n处理第 {i+1}/{len(texts_to_synthesize)} 句...")
        audio_data = synthesize_speech(text, args.voice, API_KEY, API_URL)
        if audio_data:
            save_audio(audio_data, output_path)
        else:
            print(f"第 {i+1} 句合成失败,跳过。")

到这里,一个基础可用的命令行工具就完成了!你可以填充 API_KEYAPI_URL 后运行它。但我们可以让它更好用、更专业。

4. 锦上添花:错误处理与进度显示

一个健壮的工具应该能优雅地处理各种意外,并且让用户清楚地知道当前进度。我们来完善这两个方面。

4.1 更完善的错误处理

目前的错误处理主要在 synthesize_speech 函数里。我们还可以在主循环里增加更细致的控制,比如遇到网络错误时是否重试。

def synthesize_speech_with_retry(text, voice, api_key, api_url, max_retries=2):
    """带重试机制的合成函数。"""
    for attempt in range(max_retries + 1): # 尝试 max_retries + 1 次
        audio_data = synthesize_speech(text, voice, api_key, api_url)
        if audio_data is not None:
            return audio_data
        elif attempt < max_retries: # 如果还没达到最大重试次数
            wait_time = 2 ** attempt # 指数退避:等1秒,2秒,4秒...
            print(f"合成失败,{wait_time}秒后重试... (尝试 {attempt + 1}/{max_retries})")
            time.sleep(wait_time)
        else:
            print(f"已达到最大重试次数({max_retries}),放弃该句。")
            return None

记得在文件开头 import time。然后在主循环里,把调用 synthesize_speech 的地方换成 synthesize_speech_with_retry

4.2 添加进度条

当处理大量文本时,一个进度条能极大提升体验。我们之前安装的 tqdm 库就派上用场了。

修改主循环部分:

from tqdm import tqdm # 在文件开头导入

# ... 省略前面的代码 ...

    # 使用tqdm包装循环,显示进度条
    for i, (text, output_path) in enumerate(tqdm(
        list(zip(texts_to_synthesize, output_paths)),
        desc="合成进度",
        unit="句"
    )):
        # 为了在进度条下也能看到每句的日志,可以用tqdm.write
        tqdm.write(f"正在合成: {text[:30]}...")
        audio_data = synthesize_speech_with_retry(text, args.voice, API_KEY, API_URL)
        if audio_data:
            save_audio(audio_data, output_path)
        else:
            tqdm.write(f"警告: 第 {i+1} 句合成失败。")

现在运行工具,你会看到一个动态的进度条,清晰地显示着完成百分比、预计剩余时间等信息,非常直观。

5. 完整代码与使用示例

让我们把所有的代码片段整合起来,形成一个完整的、可直接运行的脚本。我也会提供几个典型的使用例子。

5.1 完整脚本代码

将以下代码保存为 cosyvoice_cli.py,并记得替换 API_KEYAPI_URL 为你自己的值。

#!/usr/bin/env python3
"""
CosyVoice 命令行语音合成工具
一个轻量级封装,支持从文件或命令行文本输入,批量合成语音。
"""

import argparse
import time
import requests
from tqdm import tqdm

# === 配置区 (请根据你的服务修改) ===
API_KEY = "YOUR_ACTUAL_API_KEY_HERE"
API_URL = "https://your-cosyvoice-api-endpoint.com/v1/synthesize"
# ==================================

def synthesize_speech(text, voice, api_key, api_url):
    """调用CosyVoice API进行单句语音合成。"""
    headers = {
        'Authorization': f'Bearer {api_key}',
        'Content-Type': 'application/json',
    }
    payload = {
        'text': text,
        'voice': voice,
        # 可根据API文档添加额外参数,例如:
        # 'speed': 1.0,
        # 'volume': 1.0,
    }

    try:
        response = requests.post(api_url, headers=headers, json=payload, timeout=45)
        response.raise_for_status()

        # 假设API直接返回音频二进制流
        if 'audio' in response.headers.get('Content-Type', ''):
            return response.content
        else:
            # 如果API返回的是包含音频数据的JSON,需要额外解析
            # 例如:result = response.json(); audio_data = base64.b64decode(result['audio_data'])
            print("  注意:响应内容类型非标准音频,尝试直接读取。")
            return response.content

    except requests.exceptions.Timeout:
        print("  错误:请求超时。")
        return None
    except requests.exceptions.RequestException as e:
        print(f"  网络请求错误: {e}")
        return None
    except Exception as e:
        print(f"  处理响应时发生未知错误: {e}")
        return None

def synthesize_speech_with_retry(text, voice, api_key, api_url, max_retries=2):
    """带重试机制的合成函数。"""
    for attempt in range(max_retries + 1):
        audio_data = synthesize_speech(text, voice, api_key, api_url)
        if audio_data is not None:
            return audio_data
        elif attempt < max_retries:
            wait_time = 2 ** attempt
            print(f"  合成失败,{wait_time}秒后重试... (第{attempt + 1}次重试)")
            time.sleep(wait_time)
        else:
            print(f"  已达到最大重试次数({max_retries}),放弃该句。")
            return None
    return None

def save_audio(audio_data, filepath):
    """保存音频数据到文件。"""
    if not audio_data:
        return False
    try:
        with open(filepath, 'wb') as f:
            f.write(audio_data)
        return True
    except IOError as e:
        print(f"  文件保存失败: {e}")
        return False

def main():
    parser = argparse.ArgumentParser(
        description='CosyVoice语音合成命令行工具。支持单句、批量文本合成。'
    )
    parser.add_argument(
        '--text', '-t',
        type=str,
        help='直接输入要合成的文本。与 --input 参数二选一。'
    )
    parser.add_argument(
        '--input', '-i',
        type=str,
        help='输入文本文件的路径。文件每行将作为一条待合成语句。'
    )
    parser.add_argument(
        '--output', '-o',
        type=str,
        required=True,
        help='输出音频文件路径。若输入多行文本,此路径将作为前缀,生成 output_1.wav, output_2.wav...'
    )
    parser.add_argument(
        '--voice', '-v',
        type=str,
        default='zh-CN-XiaoxiaoNeural', # 示例音色,请替换为你的服务支持的音色
        help='指定合成音色。默认值:zh-CN-XiaoxiaoNeural'
    )

    args = parser.parse_args()

    # 参数校验
    if not args.text and not args.input:
        parser.error('错误:必须提供 --text 或 --input 参数以指定待合成文本。')

    # 准备待合成的文本列表和对应的输出路径列表
    task_list = []  # 每个元素是 (文本, 输出文件路径)

    if args.text:
        # 单句模式
        task_list.append((args.text, args.output))
    else:
        # 文件批量模式
        try:
            with open(args.input, 'r', encoding='utf-8') as f:
                raw_lines = [line.rstrip('\n') for line in f] # 保留行尾空格,只去掉换行符
                sentences = [line for line in raw_lines if line.strip()] # 过滤掉纯空行
        except FileNotFoundError:
            print(f"错误:找不到输入文件 '{args.input}'")
            return
        except IOError as e:
            print(f"错误:读取文件 '{args.input}' 失败: {e}")
            return

        if not sentences:
            print("错误:输入文件为空或仅包含空行。")
            return

        # 生成输出文件名
        import os
        base, ext = os.path.splitext(args.output)
        if not ext:
            ext = '.wav' # 默认使用.wav扩展名
        output_paths = [f"{base}_{i+1}{ext}" for i in range(len(sentences))]
        task_list = list(zip(sentences, output_paths))

    print(f"开始处理,共 {len(task_list)} 个合成任务。")
    print(f"使用音色: {args.voice}")

    success_count = 0
    # 使用tqdm创建进度条
    for text, out_path in tqdm(task_list, desc="合成进度", unit="句"):
        tqdm.write(f"处理: {text[:40]}...") # 在进度条外打印当前处理内容
        audio_data = synthesize_speech_with_retry(text, args.voice, API_KEY, API_URL)

        if audio_data and save_audio(audio_data, out_path):
            success_count += 1
        else:
            tqdm.write(f"  失败: 无法合成或保存 '{out_path}'")

    print(f"\n处理完成!成功: {success_count}/{len(task_list)}")

if __name__ == '__main__':
    main()

5.2 使用示例

假设你的脚本、API配置都正确,下面是一些使用示例:

1. 合成单句语音: 这是最直接的用法,适合快速测试或生成单个音频。

python cosyvoice_cli.py --text "欢迎收听今天的天气播报。" --output weather.wav --voice male_calm

2. 从文件批量合成: 假设你有一个 script.txt 文件,里面每行是一句台词。

python cosyvoice_cli.py --input script.txt --output dialogue.wav

运行后,你会得到 dialogue_1.wav, dialogue_2.wav 等文件。

3. 使用短参数名: 工具也支持短参数,输入更快捷。

python cosyvoice_cli.py -t "这是一个测试。" -o test.mp3 -v female_energetic

4. 处理长文本或网络不佳时: 脚本内置了重试机制和超时设置,能应对偶尔的网络波动。如果某一句合成失败,你会看到明确的提示,并且工具会继续处理下一句,不会整体崩溃。

6. 总结与后续扩展建议

跟着教程走下来,你已经拥有了一个功能完整、健壮性不错的CosyVoice命令行合成工具。它从简单的参数解析开始,逐步加入了核心的API调用、文件操作、错误重试和美观的进度显示。现在你可以用它来高效地处理日常的语音合成任务了。

实际用起来,你会发现这个基础版本已经能解决大部分问题。当然,根据你自己的需求,还有很多可以增强的地方。比如,你可以增加一个 --speed 参数来控制语速,或者加一个 --list-voices 参数来动态查询服务支持的所有音色。如果合成的文本特别长,可能还需要考虑支持SSML(语音合成标记语言)来获得更精细的控制,比如停顿、强调、读数字的方式等。

另一个实用的扩展是支持配置文件。把 API_KEYAPI_URL 从代码里挪到一个单独的配置文件(比如 config.ini.env 文件)中,这样既安全又方便在不同环境或项目间切换。你也可以考虑用 logging 模块替代 print,实现更规范的日志记录,方便排查问题。

工具的价值在于解决实际问题。希望这个教程不仅给了你一个可用的脚本,更重要的是展示了如何将一个常见的、重复性的手动操作,通过一些简单的编程技巧,封装成一个自动化、高效率的命令行工具。这种思路可以应用到很多地方,试着用它去优化你工作流中的其他环节吧。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐