Skip to content

MinMax旗下的TTS模型 speech系列

TTS,全称为Text-to-Speech,即文本转语音。TTS模型是一种将书面文本自动转换为人类可听的语音的人工智能模型。它的最终目标是生成听起来尽可能自然、清晰、富有表现力的语音,如同真人说话一般。

模型名称

  • speech-2.6-hd
  • speech-2.6-turbo

调用地址

https://www.dmxapi.cn/v1/audio/speech

Python 示例

python
# 文本转语音(TTS)调用示例(DMXAPI)
# 功能:将指定文本合成为 MP3 并保存到本地。
# 快速使用:
# 1) 安装依赖:pip install requests
# 2) 设置密钥:把下方 api_key 替换为您的DMXAPI密钥(生产建议用环境变量,如 os.getenv("DMXAPI_KEY"))
# 3) 运行脚本:python tts-openai.py
# 4) 输出位置:脚本同级的 outpu/ 目录,文件名包含时间戳。
# 注意:
# - 不要在生产环境硬编码密钥;避免泄露与误提交。

import requests  # 第三方 HTTP 库,用于发起网络请求
import os
from datetime import datetime

# DMXAPI 的 url
url = "https://www.dmxapi.cn/v1/audio/speech"

api_key = "sk-*******************************************"  # 替换为您的dmxapi的API密钥

# 请求负载(JSON 体)
# - model:使用的语音合成模型名称
# - input:要合成的文本内容
# - voice:音色/发音人,具体可查服务文档支持的取值
# 参数提示:
# - 长文本建议分段合成以提升稳定性
# - voice 可替换为平台支持的其他音色
payload = {
    "model": "speech-2.6-hd",
    "input": "DMXAPI,一个key使用全球大模型!",
    "voice": "male-qn-qingse"
}

try:
    # 发起 POST 请求到 TTS 服务
    # - headers:携带认证信息,使用 Bearer Token 方式
    # - json:以 JSON 格式提交负载

    response = requests.post(
        url,
        headers={"Authorization": f"Bearer {api_key}"},
        json=payload
    )
    
    # 如果服务返回 4xx/5xx 状态码,会抛出 HTTPError 异常,便于统一异常处理
    response.raise_for_status()
    
    # 处理音频响应:
    # 一般音频的 Content-Type 为 "audio/mpeg" 或 "audio/mp3"
    # 注意:此处直接访问 headers["Content-Type"],如果服务未返回该头会触发 KeyError。
    # 在生产代码中可考虑使用 headers.get("Content-Type", "") 来更稳健地取值。
    if response.headers["Content-Type"] in ("audio/mpeg", "audio/mp3"):
        # 以二进制写入的方式保存音频文件到本地
        # 输出目录(默认 'outpu';如需更改可修改此处)
        output_dir = os.path.join(os.path.dirname(__file__), "output")
        os.makedirs(output_dir, exist_ok=True)
        # 使用时间戳保证文件名唯一,避免被覆盖
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        output_path = os.path.join(output_dir, f"output_{timestamp}.mp3")
        # 将音频二进制内容写入目标文件
        with open(output_path, "wb") as f:
            _ = f.write(response.content)
        print(f"语音合成成功,已保存为{output_path}")
    else:
        # 如果不是音频类型,打印文本内容以便排查问题
        # 常见原因:参数错误、鉴权失败、服务端异常等
        print("错误响应:", response.text)

except Exception as e:
    # 捕获并输出所有异常,便于快速定位问题
    # 可能出现的异常包括:
    # - requests.exceptions.Timeout:请求超时
    # - requests.exceptions.ConnectionError:网络连接失败
    # - requests.exceptions.HTTPError:HTTP 状态码错误(由 raise_for_status 触发)
    print(f"请求出错: {e}")

运行输出示例:

text
语音合成成功,已保存为c:\Users\98317\Desktop\xiangmu\kefang\outpu\output_20251029_155307.mp3

Python 流式输出示例

python
# 文本转语音(TTS)调用示例(DMXAPI)
# 功能:将指定文本合成为 MP3,并以「纯流式」方式播放,不保存到磁盘。
# 快速使用:
# - 安装依赖:pip install requests
# - 设置密钥:将下方 api_key 替换为您的 DMXAPI 密钥(生产建议使用环境变量,例如 os.getenv("DMXAPI_KEY"))
# - 运行脚本:python tts_stream.py
# 注意:
# - 为实现纯流式播放,本脚本不进行文件落盘。如需播放请确保系统已安装 ffplay(FFmpeg)。

# 说明:
# - 本脚本优先尝试使用 ffplay 实现「边下边播」,仅当 ffplay 不可用或启动失败时,才回退为将音频保存到本地文件并在 Windows 上自动打开。
# - 可通过环境变量 `FFPLAY_PATH` 指定 ffplay 的绝对路径(例如 C:\FFmpeg\bin\ffplay.exe),设置 `FFPLAY_DEBUG=1` 可查看 ffplay 的详细日志。
# - 关键步骤(请求参数、流式下载、播放/保存分支、进度打印与资源清理)均已补充详细中文注释,便于理解与维护。

import requests
import os
import shutil
import subprocess
import sys
from datetime import datetime
from typing import BinaryIO

# 模块用途速览:
# - requests:发送 HTTP 请求并按流方式接收响应内容
# - os/shutil/subprocess:环境变量读取、外部播放器检查、调用系统程序
# - datetime:生成时间戳用于本地文件命名

# DMXAPI 的 URL
url = "https://www.dmxapi.cn/v1/audio/speech"

# 密钥:生产环境建议通过环境变量注入
api_key = os.getenv("DMXAPI_KEY", "sk-*******************************************")

# 安全提示:请避免在生产代码中硬编码密钥,推荐通过环境变量或密钥管控服务注入。

# 请求负载(JSON 体)
payload = {
    "model": "speech-2.6-hd",
    "input": "DMXAPI,一个key使用全球大模型!",
    "voice": "male-qn-qingse",
}

# 字段说明:
# - model:TTS 模型名称
# - input:待合成的文本内容
# - voice:发音人音色或角色(不同平台支持的枚举可能不同)

try:
    # 发起 POST 请求到 TTS 服务(开启流式传输)
    # 说明:Accept 指定期望的音频类型,`stream=True` 允许我们一边接收一边播放或写入文件。
    response = requests.post(
        url,
        headers={
            "Authorization": f"Bearer {api_key}",
            "Accept": "audio/mpeg",
        },
        json=payload,
        stream=True,
        timeout=(10, 120),  # 连接/读取超时(可根据网络情况调整)
    )

    # 若返回 4xx/5xx,抛出异常
    response.raise_for_status()

    # 处理音频响应:常见类型为 audio/mpeg、audio/mp3;也可能返回 application/octet-stream
    content_type = response.headers.get("Content-Type", "")
    print(f"Content-Type: {content_type}")
    if content_type.startswith("audio/") or content_type in ("audio/mpeg", "audio/mp3", "application/octet-stream", "binary/octet-stream"):
        total = int(response.headers.get("Content-Length", 0))
        written = 0
        chunk_size = 1024 * 64  # 64KB 每块
        last_percent = -1
        # 文件保存相关变量(当未使用 ffplay 时启用)
        f_out: BinaryIO | None = None
        output_path: str | None = None

        # 优先使用 ffplay 进行边下边播(不落盘)
        # 允许通过环境变量 FFPLAY_PATH 指定 ffplay 完整路径;否则回退 PATH 检测

        def _is_executable(path: str) -> bool:
            if not path:
                return False
            try:
                return os.path.isfile(path) and os.access(path, os.X_OK)
            except Exception:
                return False

        def find_ffplay_path() -> str | None:
            # 1) 显式环境变量
            env_path = os.getenv("FFPLAY_PATH")
            if env_path and _is_executable(env_path):
                return env_path

            # 2) PATH 中的 ffplay
            which_path = shutil.which("ffplay")
            if which_path and _is_executable(which_path):
                return which_path

            # 3) 常见的 Conda/Windows 安装位置(尤其是 conda-forge 的 Library\bin)
            candidates: list[str] = []
            if os.name == "nt":
                bases: list[str] = []
                # CONDA_PREFIX 优先,其次使用当前解释器前缀
                conda_prefix = os.getenv("CONDA_PREFIX")
                if conda_prefix:
                    bases.append(conda_prefix)
                if sys.prefix and (not bases or sys.prefix not in bases):
                    bases.append(sys.prefix)
                for base in bases:
                    candidates.extend([
                        os.path.join(base, "Library", "bin", "ffplay.exe"),
                        os.path.join(base, "Library", "mingw-w64", "bin", "ffplay.exe"),
                        os.path.join(base, "bin", "ffplay.exe"),
                        os.path.join(base, "Scripts", "ffplay.exe"),
                    ])
                # 常见系统安装位置
                candidates.extend([
                    r"C:\\ffmpeg\\bin\\ffplay.exe",
                    r"C:\\Program Files\\ffmpeg\\bin\\ffplay.exe",
                    r"C:\\Program Files (x86)\\ffmpeg\\bin\\ffplay.exe",
                    r"C:\\ProgramData\\chocolatey\\bin\\ffplay.exe",
                ])

            # 在调试模式下打印候选路径,便于排查
            if os.getenv("FFPLAY_DEBUG") == "1":
                try:
                    print("FFPLAY 检测候选:")
                    for p in candidates:
                        print(" -", p, "[存在]" if _is_executable(p) else "[缺失]")
                except Exception:
                    pass

            for p in candidates:
                if _is_executable(p):
                    return p
            return None

        ffplay_path = find_ffplay_path()
        ffplay_proc: subprocess.Popen[bytes] | None = None
        use_ffplay = bool(ffplay_path)

        # 说明:如果系统存在 ffplay,将尝试实时播放;否则进入保存到文件的回退分支。

        # 先读取首块数据用于格式嗅探与快速启动 ffplay
        it = response.iter_content(chunk_size=chunk_size)
        try:
            first_chunk_raw = next(it)  # pyright: ignore[reportAny]
            first_chunk: bytes = first_chunk_raw if isinstance(first_chunk_raw, bytes) else b""
        except StopIteration:
            print("音频流为空,未收到数据")
            first_chunk = b""

        def looks_like_mp3(buf: bytes) -> bool:
            # MP3 开头可能是 ID3 标签或帧同步字节 0xFFExx
            return buf.startswith(b"ID3") or (len(buf) >= 2 and buf[0] == 0xFF and (buf[1] & 0xE0) == 0xE0)

        if use_ffplay and ffplay_path:
            try:
                ffplay_args: list[str] = [ffplay_path, "-hide_banner", "-nodisp", "-autoexit", "-loglevel", "error"]
                # ffplay 参数说明:
                # - -nodisp:不显示视频/波形窗口;
                # - -autoexit:播放结束后自动退出;
                # - -loglevel error:仅输出错误级别日志,减少控制台噪音。
                if content_type in ("audio/mpeg", "audio/mp3") or looks_like_mp3(first_chunk):
                    ffplay_args += ["-f", "mp3", "-i", "pipe:0"]
                else:
                    ffplay_args += ["-i", "pipe:0"]
                ffplay_proc = subprocess.Popen(
                    ffplay_args,
                    stdin=subprocess.PIPE,
                    stdout=subprocess.DEVNULL,
                    stderr=None if os.getenv("FFPLAY_DEBUG") == "1" else subprocess.DEVNULL,
                )
                print(f"已启动 ffplay 进行实时播放(不保存文件),路径:{ffplay_path}")
            except Exception as _e:
                print(f"启动 ffplay 失败:{_e},将启用回退逻辑:保存到本地并在结束后自动播放。")
                use_ffplay = False

        # 若 ffplay 不可用,准备保存到文件
        if not use_ffplay:
            try:
                # 注意:为与现有项目结构保持一致,音频文件存放在 `outpu` 子目录下。
                output_dir = os.path.join(os.path.dirname(__file__), "output")
                os.makedirs(output_dir, exist_ok=True)
                ts = datetime.now().strftime("%Y%m%d_%H%M%S")
                ext = ".mp3" if (content_type in ("audio/mpeg", "audio/mp3") or looks_like_mp3(first_chunk)) else ".bin"
                output_path = os.path.join(output_dir, f"tts_{ts}{ext}")
                f_out = open(output_path, "wb")
                hint = "(可设置环境变量 FFPLAY_PATH 指向 ffplay.exe 或将其加入 PATH)"
                print(f"未检测到 ffplay,将保存到文件:{output_path} {hint}")
            except Exception as _e:
                print(f"打开输出文件失败:{_e},将仅显示下载进度(不保存、不播放)。")
                f_out = None

        # 将首块数据写入 ffplay 并更新进度
        if first_chunk:
            written += len(first_chunk)
            # 写入 ffplay
            if use_ffplay and ffplay_proc and ffplay_proc.poll() is None and ffplay_proc.stdin:
                try:
                    _ = ffplay_proc.stdin.write(first_chunk)
                    ffplay_proc.stdin.flush()
                except Exception:
                    ffplay_proc = None
            # 写入文件
            if f_out is not None:
                try:
                    _ = f_out.write(first_chunk)
                    f_out.flush()
                except Exception:
                    pass
            # 打印首块后的进度
            if total > 0:
                percent = int(written * 100 / total)
                if percent % 5 == 0:
                    print(f"下载进度 {percent}% ({written/1024:.0f} KB)", end="\r")
            else:
                print(f"已下载 {written/(1024*1024):.2f} MB...", end="\r")

        # 继续写入剩余数据
        for chunk in it:  # pyright: ignore[reportAny]
            chunk_bytes: bytes = chunk if isinstance(chunk, bytes) else b""
            if not chunk_bytes:
                continue
            written += len(chunk_bytes)
            if use_ffplay and ffplay_proc and ffplay_proc.poll() is None and ffplay_proc.stdin:
                try:
                    _ = ffplay_proc.stdin.write(chunk_bytes)
                    ffplay_proc.stdin.flush()
                except BrokenPipeError:
                    ffplay_proc = None
                except Exception:
                    ffplay_proc = None
            # 同时写入文件(当启用回退保存时)
            if f_out is not None:
                try:
                    _ = f_out.write(chunk_bytes)
                except Exception:
                    pass

            # 简单进度输出:有 Content-Length 时按百分比,否则按 MB 计数
            if total > 0:
                percent = int(written * 100 / total)
                if percent != last_percent and percent % 5 == 0:  # 每 5% 打印一次
                    print(f"下载进度 {percent}% ({written/1024:.0f} KB)", end="\r")
                    last_percent = percent
            else:
                if written % (1024 * 1024) < chunk_size:  # 约每 1MB 打印
                    print(f"已下载 {written/(1024*1024):.2f} MB...", end="\r")

        # 播放结束时清理 ffplay 的输入并等待退出
        if use_ffplay and ffplay_proc and ffplay_proc.stdin:
            try:
                ffplay_proc.stdin.close()
            except Exception:
                pass
            try:
                ffplay_proc.wait(timeout=5)  # pyright: ignore[reportUnusedCallResult]
            except Exception:
                pass

        # 若保存到文件,关闭并尝试自动打开
        if f_out is not None:
            try:
                f_out.close()
            except Exception:
                pass
            # 尝试用系统默认播放器打开(Windows)
            try:
                if output_path and os.name == "nt":
                    # Windows 环境:使用系统默认播放器打开生成的音频文件。
                    os.startfile(output_path)
                    print(f"\n语音合成成功,已保存并自动打开:{output_path}")
                else:
                    print(f"\n语音合成成功,已保存:{output_path}")
            except Exception as _e:
                print(f"\n语音合成成功,已保存:{output_path}(自动打开失败:{_e})")
        else:
            if use_ffplay:
                print("\n语音合成成功(纯流式播放已完成,未保存文件)")
            else:
                print("\n语音合成功能已以流式方式完成下载与消费,但未播放且不保存文件。建议安装 FFmpeg 的 ffplay 以实现边下边播。")
    else:
        # 若非音频类型,流式读取文本内容以便排查问题
        # 说明:部分错误情况下服务可能返回 JSON 或纯文本,打印出来有助于定位问题。
        err_lines: list[str] = []
        for line in response.iter_lines(decode_unicode=True):  # pyright: ignore[reportAny]
            line_str: str = line if isinstance(line, str) else str(line)  # pyright: ignore[reportAny]
            if line_str:
                err_lines.append(line_str)
                if sum(len(x) for x in err_lines) > 4000:
                    break
        err_text = "\n".join(err_lines) if err_lines else response.text
        print("错误响应:", err_text)

except Exception as e:
    # 统一异常捕获与输出
    # 顶层兜底:避免未处理异常导致脚本直接中断,便于在控制台查看问题原因。
    print(f"请求出错: {e}")

运行输出示例:

text
Content-Type: audio/mpeg
已启动 ffplay 进行实时播放(不保存文件),路径:C:\ffmpeg\ffmpeg-master-latest-win64-gpl\bin\ffplay.exe
已下载 0.06 MB...
语音合成成功(纯流式播放已完成,未保存文件)

可选择音色列表

序号语言Voice ID音色名称
1中文 (普通话)male-qn-qingse青涩青年音色
2中文 (普通话)male-qn-jingying精英青年音色
3中文 (普通话)male-qn-badao霸道青年音色
4中文 (普通话)male-qn-daxuesheng青年大学生音色
5中文 (普通话)female-shaonv少女音色
6中文 (普通话)female-yujie御姐音色
7中文 (普通话)female-chengshu成熟女性音色
8中文 (普通话)female-tianmei甜美女性音色
9中文 (普通话)male-qn-qingse-jingpin青涩青年音色-beta
10中文 (普通话)male-qn-jingying-jingpin精英青年音色-beta
11中文 (普通话)male-qn-badao-jingpin霸道青年音色-beta
12中文 (普通话)male-qn-daxuesheng-jingpin青年大学生音色-beta
13中文 (普通话)female-shaonv-jingpin少女音色-beta
14中文 (普通话)female-yujie-jingpin御姐音色-beta
15中文 (普通话)female-chengshu-jingpin成熟女性音色-beta
16中文 (普通话)female-tianmei-jingpin甜美女性音色-beta
17中文 (普通话)clever_boy聪明男童
18中文 (普通话)cute_boy可爱男童
19中文 (普通话)lovely_girl萌萌女童
20中文 (普通话)cartoon_pig卡通猪小琪
21中文 (普通话)bingjiao_didi病娇弟弟
22中文 (普通话)junlang_nanyou俊朗男友
23中文 (普通话)chunzhen_xuedi纯真学弟
24中文 (普通话)lengdan_xiongzhang冷淡学长
25中文 (普通话)badao_shaoye霸道少爷
26中文 (普通话)tianxin_xiaoling甜心小玲
27中文 (普通话)qiaopi_mengmei俏皮萌妹
28中文 (普通话)wumei_yujie妩媚御姐
29中文 (普通话)diadia_xuemei嗲嗲学妹
30中文 (普通话)danya_xuejie淡雅学姐
31中文 (普通话)Chinese (Mandarin)_Reliable_Executive沉稳高管
32中文 (普通话)Chinese (Mandarin)_News_Anchor新闻女声
33中文 (普通话)Chinese (Mandarin)_Mature_Woman傲娇御姐
34中文 (普通话)Chinese (Mandarin)_Unrestrained_Young_Man不羁青年
35中文 (普通话)Arrogant_Miss嚣张小姐
36中文 (普通话)Robot_Armor机械战甲
37中文 (普通话)Chinese (Mandarin)_Kind-hearted_Antie热心大婶
38中文 (普通话)Chinese (Mandarin)_HK_Flight_Attendant港普空姐
39中文 (普通话)Chinese (Mandarin)_Humorous_Elder搞笑大爷
40中文 (普通话)Chinese (Mandarin)_Gentleman温润男声
41中文 (普通话)Chinese (Mandarin)_Warm_Bestie温暖闺蜜
42中文 (普通话)Chinese (Mandarin)_Male_Announcer播报男声
43中文 (普通话)Chinese (Mandarin)_Sweet_Lady甜美女声
44中文 (普通话)Chinese (Mandarin)_Southern_Young_Man南方小哥
45中文 (普通话)Chinese (Mandarin)_Wise_Women阅历姐姐
46中文 (普通话)Chinese (Mandarin)_Gentle_Youth温润青年
47中文 (普通话)Chinese (Mandarin)_Warm_Girl温暖少女
48中文 (普通话)Chinese (Mandarin)_Kind-hearted_Elder花甲奶奶
49中文 (普通话)Chinese (Mandarin)_Cute_Spirit憨憨萌兽
50中文 (普通话)Chinese (Mandarin)_Radio_Host电台男主播
51中文 (普通话)Chinese (Mandarin)_Lyrical_Voice抒情男声
52中文 (普通话)Chinese (Mandarin)_Straightforward_Boy率真弟弟
53中文 (普通话)Chinese (Mandarin)_Sincere_Adult真诚青年
54中文 (普通话)Chinese (Mandarin)_Gentle_Senior温柔学姐
55中文 (普通话)Chinese (Mandarin)_Stubborn_Friend嘴硬竹马
56中文 (普通话)Chinese (Mandarin)_Crisp_Girl清脆少女
57中文 (普通话)Chinese (Mandarin)_Pure-hearted_Boy清澈邻家弟弟
58中文 (普通话)Chinese (Mandarin)_Soft_Girl软软女孩
59中文 (粤语)Cantonese_ProfessionalHost(F)专业女主持
60中文 (粤语)Cantonese_GentleLady温柔女声
61中文 (粤语)Cantonese_ProfessionalHost(M)专业男主持
62中文 (粤语)Cantonese_PlayfulMan活泼男声
63中文 (粤语)Cantonese_CuteGirl可爱女孩
64中文 (粤语)Cantonese_KindWoman善良女声
65英文Santa_ClausSanta Claus
66英文GrinchGrinch
67英文RudolphRudolph
68英文ArnoldArnold
69英文Charming_SantaCharming Santa
70英文Charming_LadyCharming Lady
71英文Sweet_GirlSweet Girl
72英文Cute_ElfCute Elf
73英文Attractive_GirlAttractive Girl
74英文Serene_WomanSerene Woman
75英文English_Trustworthy_ManTrustworthy Man
76英文English_Graceful_LadyGraceful Lady
77英文English_Aussie_BlokeAussie Bloke
78英文English_Whispering_girlWhispering girl
79英文English_Diligent_ManDiligent Man
80英文English_Gentle-voiced_manGentle-voiced man

© 2025 DMXAPI MinMax旗下...

一个 Key 用全球大模型