音视频转换成文字faster-whisper
Ubuntu 20下用Python将视频/音频转文字的脚本,主要使用 whisper(OpenAI开源)或 faster-whisper(更快更省资源)。
纯 CPU 方案(无显卡)
使用 int8 量化,CPU也能流畅运行
python3 transcribe.py audio.mp3 --model base --device cpu --compute int8
模型目录结构要求
./faster-whisper-base/ 目录下需要包含这些文件:
./faster-whisper-base/
├── model.bin (模型权重,约145MB for base)
├── config.json (模型配置)
├── tokenizer.json (分词器)
└── vocabulary.txt (词表)
faster-whisper 支持的模型及其特点:
| 模型 | 参数量 | 磁盘大小 | 速度 | 准确率 | 适用场景 | 显存需求 |
|---|---|---|---|---|---|---|
| tiny | 39M | ~75MB | 最快 | 一般 | 实时字幕、快速草稿、资源极有限 | ~1GB |
| tiny.en | 39M | ~75MB | 最快 | 一般(英文优化) | 仅英文实时场景 | ~1GB |
| base | 74M | ~150MB | 很快 | 够用 | 日常使用、播客、会议记录 | ~1GB |
| base.en | 74M | ~150MB | 很快 | 够用(英文优化) | 仅英文日常场景 | ~1GB |
| small | 244M | ~480MB | 快 | 较好 | 平衡选择,推荐大多数场景 | ~2GB |
| small.en | 244M | ~480MB | 快 | 较好(英文优化) | 仅英文高质量场景 | ~2GB |
| medium | 769M | ~1.5GB | 中等 | 好 | 对准确率要求高 | ~5GB |
| medium.en | 769M | ~1.5GB | 中等 | 好(英文优化) | 仅英文高精度场景 | ~5GB |
| large-v1 | 1.5B | ~3GB | 慢 | 很好 | 专业转录、多语言 | ~10GB |
| large-v2 | 1.5B | ~3GB | 较慢 | 很好 | 推荐的大模型版本 | ~10GB |
| large-v3 | 1.5B | ~3GB | 较慢 | 最好 | 最新版,多语言最强 | ~10GB |
| large-v3-turbo | 1.5B | ~3GB | 快 | 接近large-v3 | 速度与准确率的最佳平衡 | ~6GB |
关键说明
.en 后缀模型
- 仅针对英语优化,其他语言效果差
- 如果你只处理中文内容,不要用
.en模型
速度对比(参考值,CPU int8)
| 模型 | 1小时音频处理时间 |
|---|---|
| tiny | ~5分钟 |
| base | ~10分钟 |
| small | ~20分钟 |
| medium | ~1小时 |
| large-v3 | ~2-3小时 |
选择建议
| 你的场景 | 推荐模型 |
|---|---|
| 快速测试/草稿 | tiny |
| 日常中文视频/播客 | base 或 small |
| 会议记录(要准确) | small 或 medium |
| 专业字幕/翻译 | medium 或 large-v3 |
| 多语言混合内容 | large-v3 |
| 追求速度+准确率平衡 | large-v3-turbo |
你的 CPU 场景建议
# 日常够用,速度快
python3 transcribe.py audio.mp3 --model base --device cpu --compute int8
# 想要更好效果(推荐)
python3 transcribe.py audio.mp3 --model small --device cpu --compute int8
# 如果内存够(8G+),追求准确
python3 transcribe.py audio.mp3 --model medium --device cpu --compute int8
large-v3 在纯 CPU 上会很慢,除非内容特别重要,否则不建议。
如何使用HF-Mirror🌟
本站域名 hf-mirror.com,用于镜像 huggingface.co 域名。作为一个公益项目,致力于帮助国内AI开发者快速、稳定的下载模型、数据集。捐赠支持请看网页左下角,感谢支持!
更多详细用法请看《这篇教程》。
方法一:网页下载
在本站搜索,并在模型主页的Files and Version中下载文件。
方法二:huggingface-cli
huggingface-cli 是 Hugging Face 官方提供的命令行工具,自带完善的下载功能。使用方法如下:
1. 安装依赖
pip install -U huggingface_hubCopy
2. 设置环境变量
Linux
export HF_ENDPOINT=https://hf-mirror.comCopy
Windows Powershell
$env:HF_ENDPOINT = "https://hf-mirror.com"Copy
建议将上面这一行写入 ~/.bashrc。
3.1 下载模型
huggingface-cli download --resume-download gpt2 --local-dir gpt2Copy
3.2 下载数据集
huggingface-cli download --repo-type dataset --resume-download wikitext --local-dir wikitextCopy
可以添加 --local-dir-use-symlinks False 参数禁用文件软链接,这样下载路径下所见即所得,详细解释请见上面提到的教程。
方法三:使用 hfd
hfd 是本站开发的 huggingface 专用下载工具,基于成熟工具 aria2,可以做到稳定高速下载不断线。
1. 下载hfd
wget https://hf-mirror.com/hfd/hfd.sh
chmod a+x hfd.shCopy
2. 设置环境变量
Linux
export HF_ENDPOINT=https://hf-mirror.comCopy
Windows Powershell
$env:HF_ENDPOINT = "https://hf-mirror.com"Copy
3.1 下载模型
./hfd.sh gpt2Copy
3.2 下载数据集
./hfd.sh wikitext --datasetCopy
方法四:使用环境变量(非侵入式)
非侵入式,能解决大部分情况。huggingface 工具链会获取HF_ENDPOINT环境变量来确定下载文件所用的网址,所以可以使用通过设置变量来解决。
HF_ENDPOINT=https://hf-mirror.com python your_script.pyCopy
不过有些数据集有内置的下载脚本,那就需要手动改一下脚本内的地址来实现了。
常见问题
Q: 有些项目需要登录,如何下载?
A:部分 Gated Repo 需登录申请许可。为保障账号安全,本站不支持登录,需先前往 Hugging Face 官网登录、申请许可,在官网这里获取 Access Token 后回镜像站用命令行下载。
部分工具下载 Gated Repo 的方法:
huggingface-cli: 添加--token参数
huggingface-cli download --token hf_*** --resume-download meta-llama/Llama-2-7b-hf --local-dir Llama-2-7b-hfCopy
hfd: 添加--hf_username``--hf_token参数
hfd meta-llama/Llama-2-7b --hf_username YOUR_HF_USERNAME --hf_token hf_***Copy
其余如from_pretrained、wget、curl如何设置认证 token,详见上面第一段提到的教程。
transcribe.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
视频/音频转文字脚本 (Ubuntu 20)
使用 faster-whisper,支持 GPU/CPU
"""
import os
import sys
import argparse
import subprocess
import json
from pathlib import Path
from datetime import timedelta
# 尝试导入 faster-whisper,失败则提示安装
try:
from faster_whisper import WhisperModel
except ImportError:
print("错误:未安装 faster-whisper")
print("请运行: pip install faster-whisper")
sys.exit(1)
def format_timestamp(seconds: float) -> str:
"""将秒数转换为 SRT 时间格式 HH:MM:SS,mmm"""
td = timedelta(seconds=seconds)
total_seconds = int(td.total_seconds())
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
secs = total_seconds % 60
millis = int((seconds - int(seconds)) * 1000)
return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
def extract_audio(video_path: str, output_audio: str = None) -> str:
"""
从视频文件中提取音频(使用 ffmpeg)
"""
if output_audio is None:
output_audio = video_path.rsplit('.', 1)[0] + '.wav'
# 检查是否已有音频文件
if os.path.exists(output_audio):
print(f"音频文件已存在: {output_audio}")
return output_audio
print(f"正在从视频提取音频: {video_path}")
cmd = [
'ffmpeg', '-y', '-i', video_path,
'-vn', # 不处理视频
'-acodec', 'pcm_s16le', # 16位PCM编码
'-ar', '16000', # 采样率16kHz(whisper推荐)
'-ac', '1', # 单声道
output_audio
]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=True
)
print(f"音频提取完成: {output_audio}")
return output_audio
except subprocess.CalledProcessError as e:
print(f"ffmpeg 提取音频失败: {e.stderr}")
sys.exit(1)
except FileNotFoundError:
print("错误:未找到 ffmpeg,请先安装: sudo apt install ffmpeg")
sys.exit(1)
def transcribe(
audio_path: str,
model_size: str = "base",
device: str = "auto",
compute_type: str = "default",
language: str = "zh",
output_format: str = "txt",
output_path: str = None,
vad_filter: bool = True
) -> list:
"""
使用 faster-whisper 进行语音识别
参数:
audio_path: 音频文件路径
model_size: 模型大小 (tiny/base/small/medium/large/large-v2/large-v3)
device: 计算设备 (cuda/cpu/auto)
compute_type: 计算精度 (float16/int8/int8_float16/float32)
language: 语言代码 (zh/en/ja/...)
output_format: 输出格式 (txt/srt/json)
output_path: 输出文件路径
vad_filter: 是否启用语音活动检测(过滤静音)
"""
# 自动选择设备
if device == "auto":
import torch
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"自动选择设备: {device}")
# 根据设备调整计算类型
if compute_type == "default":
compute_type = "float16" if device == "cuda" else "int8"
print(f"加载模型: {model_size} (设备: {device}, 精度: {compute_type})")
# 加载模型(优先使用 ./faster-whisper-base 目录下的本地模型)
model_dir = os.environ.get("WHISPER_MODEL_DIR", "./faster-whisper-base")
print(f"模型目录: {os.path.abspath(model_dir)}")
# 加载模型(优先使用 ./faster-whisper-base 目录下的本地模型)
if os.path.exists(model_dir):
print("使用本地模型...")
# 直接传模型目录路径给第一个参数
model = WhisperModel(model_dir, device=device, compute_type=compute_type)
else:
print("本地模型不存在,尝试下载...")
model = WhisperModel(model_size, device=device, compute_type=compute_type)
print(f"开始识别: {audio_path}")
print(f"语言: {language}, VAD过滤: {vad_filter}")
# 执行识别
segments, info = model.transcribe(
audio_path,
language=language,
vad_filter=vad_filter,
vad_parameters=dict(min_silence_duration_ms=500),
condition_on_previous_text=True
)
print(f"检测到语言: {info.language} (概率: {info.language_probability:.2f})")
print(f"预计时长: {info.duration:.2f} 秒")
# 收集结果
results = []
for i, segment in enumerate(segments, 1):
result = {
"id": i,
"start": segment.start,
"end": segment.end,
"text": segment.text.strip(),
"start_formatted": format_timestamp(segment.start),
"end_formatted": format_timestamp(segment.end),
}
results.append(result)
# 实时打印
print(f"[{result['start_formatted']} --> {result['end_formatted']}] {result['text']}")
# 保存结果
if output_path is None:
output_path = audio_path.rsplit('.', 1)[0] + f'.{output_format}'
save_results(results, output_path, output_format)
print(f"\n结果已保存: {output_path}")
return results
def save_results(results: list, output_path: str, format_type: str):
"""保存识别结果到文件"""
if format_type == "txt":
# 纯文本格式
with open(output_path, 'w', encoding='utf-8') as f:
for r in results:
f.write(r["text"] + "\n")
elif format_type == "srt":
# SRT 字幕格式
with open(output_path, 'w', encoding='utf-8') as f:
for r in results:
f.write(f"{r['id']}\n")
f.write(f"{r['start_formatted']} --> {r['end_formatted']}\n")
f.write(f"{r['text']}\n\n")
elif format_type == "json":
# JSON 格式
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(results, f, ensure_ascii=False, indent=2)
elif format_type == "vtt":
# WebVTT 格式
with open(output_path, 'w', encoding='utf-8') as f:
f.write("WEBVTT\n\n")
for r in results:
# VTT 使用点号分隔毫秒
start = r['start_formatted'].replace(',', '.')
end = r['end_formatted'].replace(',', '.')
f.write(f"{start} --> {end}\n")
f.write(f"{r['text']}\n\n")
def batch_process(
input_paths: list,
model_size: str = "base",
**kwargs
):
"""批量处理多个文件"""
for path in input_paths:
print(f"\n{'='*50}")
print(f"处理文件: {path}")
print(f"{'='*50}")
ext = Path(path).suffix.lower()
# 如果是视频,先提取音频
if ext in ['.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv', '.webm']:
audio_path = extract_audio(path)
else:
audio_path = path
# 转文字
transcribe(audio_path, model_size=model_size, **kwargs)
def main():
parser = argparse.ArgumentParser(
description='视频/音频转文字工具 (基于 faster-whisper)',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
# 识别单个音频文件
python3 transcribe.py audio.mp3
# 识别视频并生成字幕
python3 transcribe.py video.mp4 -f srt -l zh
# 使用大模型提高准确率(需要更多显存)
python3 transcribe.py audio.mp3 --model large-v3 --device cuda
# 批量处理
python3 transcribe.py *.mp3 --model small
# 输出JSON(带时间戳)
python3 transcribe.py audio.mp3 -f json
模型大小说明:
tiny - 39M 参数, 最快, 准确率一般
base - 74M 参数, 较快, 日常够用
small - 244M 参数, 平衡选择
medium - 769M 参数, 较准, 较慢
large-v3 - 1.5B 参数, 最准, 最慢, 需8G+显存
"""
)
parser.add_argument('input', nargs='+', help='输入文件路径(支持视频/音频)')
parser.add_argument('-m', '--model', default='base',
choices=['tiny', 'base', 'small', 'medium', 'large', 'large-v2', 'large-v3'],
help='模型大小 (默认: base)')
parser.add_argument('-d', '--device', default='auto',
choices=['cuda', 'cpu', 'auto'],
help='计算设备 (默认: auto)')
parser.add_argument('-c', '--compute', default='default',
choices=['float16', 'float32', 'int8', 'int8_float16', 'default'],
help='计算精度 (默认: 自动)')
parser.add_argument('-l', '--language', default='zh',
help='语言代码,如 zh, en, ja (默认: zh)')
parser.add_argument('-f', '--format', default='txt',
choices=['txt', 'srt', 'json', 'vtt'],
help='输出格式 (默认: txt)')
parser.add_argument('-o', '--output', default=None,
help='输出文件路径 (默认: 同输入文件名)')
parser.add_argument('--no-vad', action='store_true',
help='禁用语音活动检测')
args = parser.parse_args()
# 批量处理
batch_process(
args.input,
model_size=args.model,
device=args.device,
compute_type=args.compute,
language=args.language,
output_format=args.format,
output_path=args.output,
vad_filter=not args.no_vad
)
if __name__ == "__main__":
main()
文档转声音
推荐
| 场景 | 推荐方案 |
|---|---|
| 快速测试、简单需求 | edge-tts |
| 要离线、不联网 | pyttsx3 |
| 高质量、自然语音 | ChatTTS(有GPU)或 edge-tts |
最推荐 edge-tts,免费、中文效果好、直接出 MP3:
pip install edge-tts
edge-tts --voice zh-CN-XiaoxiaoNeural --text "你好世界" --write-media output.mp3
方案1:pyttsx3(离线,最简单)
pip install pyttsx3
import pyttsx3
engine = pyttsx3.init()
engine.setProperty('rate', 150) # 语速
engine.setProperty('volume', 0.9) # 音量
# 保存为 MP3(实际输出的是 wav,需要 ffmpeg 转换)
engine.save_to_file("你好,这是测试语音", "output.wav")
engine.runAndWait()
# 用 ffmpeg 转 MP3
import subprocess
subprocess.run(['ffmpeg', '-y', '-i', 'output.wav', '-b:a', '192k', 'output.mp3'])
缺点:声音机械,中文支持一般。
方案2:edge-tts(免费在线,微软 Edge 语音,推荐)
pip install edge-tts
import asyncio
import edge_tts
async def text_to_speech(text, output_file, voice="zh-CN-XiaoxiaoNeural"):
communicate = edge_tts.Communicate(text, voice)
await communicate.save(output_file)
# 使用
asyncio.run(text_to_speech("你好,这是微软 Edge 语音合成的测试", "output.mp3"))
优点:
- 免费,调用微软 Edge 在线 TTS
- 中文语音自然(晓晓、云希等)
- 直接输出 MP3
可用语音列表:
edge-tts --list-voices | grep zh-CN
常用中文语音:
| 语音名称 | 特点 |
|---|---|
zh-CN-XiaoxiaoNeural |
晓晓,女声,温柔 |
zh-CN-YunxiNeural |
云希,男声,年轻 |
zh-CN-YunjianNeural |
云健,男声,新闻播报 |
zh-CN-XiaoyiNeural |
小艺,女声,活泼 |
方案3:ChatTTS(开源,最自然,需 GPU)
bash复制
pip install ChatTTS
import ChatTTS
import torch
import torchaudio
chat = ChatTTS.Chat()
chat.load(compile=False) # CPU 用 False
texts = ["你好,这是 ChatTTS 生成的自然语音。"]
wavs = chat.infer(texts)
torchaudio.save("output.mp3", torch.from_numpy(wavs[0]), 24000)
优点:中文语音非常自然,带情感。 缺点:需要 4G+ 显存,第一次下载模型较大。
方案4:gTTS(Google TTS,简单)
pip install gtts
from gtts import gTTS
tts = gTTS("你好,这是 Google 语音合成", lang='zh')
tts.save("output.mp3")
缺点:需要翻墙,中文语音一般。
txt2mp3.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
txt 文档转 MP3 语音脚本
使用 edge-tts(微软 Edge 在线语音合成)
依赖安装:
pip install edge-tts
用法:
python3 txt2mp3.py input.txt
python3 txt2mp3.py input.txt -o output.mp3
python3 txt2mp3.py input.txt -v zh-CN-YunxiNeural -r 1.2
# 基本用法
python3 txt2mp3.py article.txt
# 指定输出文件名
python3 txt2mp3.py article.txt -o book.mp3
# 换男声(云希)
python3 txt2mp3.py article.txt -v yunxi
# 加快语速
python3 txt2mp3.py article.txt -v xiaoxiao -r +20%
# 减慢语速 + 增大音量
python3 txt2mp3.py article.txt -r -10% -vol +30%
# 查看可用语音列表
python3 txt2mp3.py --list-voices
"""
import os
import sys
import argparse
import asyncio
import edge_tts
# 常用语音列表
VOICES = {
# === 中文 ===
"xiaoxiao": "zh-CN-XiaoxiaoNeural", # 女声,温柔
"xiaoyi": "zh-CN-XiaoyiNeural", # 女声,活泼
"yunxi": "zh-CN-YunxiNeural", # 男声,年轻
"yunjian": "zh-CN-YunjianNeural", # 男声,新闻播报
"xiaochen": "zh-CN-XiaochenNeural", # 女声,成熟
"xiaohan": "zh-CN-XiaohanNeural", # 女声,柔和
"xiaomeng": "zh-CN-XiaomengNeural", # 女声,甜美
"xiaomo": "zh-CN-XiaomoNeural", # 男声,磁性
"xiaorui": "zh-CN-XiaoruiNeural", # 男声,稳重
"xiaoxuan": "zh-CN-XiaoxuanNeural", # 女声,知性
"yunyang": "zh-CN-YunyangNeural", # 男声,新闻
"yunxia": "zh-CN-YunxiaNeural", # 男声,可爱
# === 英语-美国 ===
"jenny": "en-US-JennyNeural", # 女声,标准美音
"guy": "en-US-GuyNeural", # 男声,标准美音
"aria": "en-US-AriaNeural", # 女声,自然
"ana": "en-US-AnaNeural", # 女声,温柔
"christopher": "en-US-ChristopherNeural", # 男声,稳重
"eric": "en-US-EricNeural", # 男声,年轻
"michelle": "en-US-MichelleNeural", # 女声,知性
"roger": "en-US-RogerNeural", # 男声,成熟
"steffan": "en-US-SteffanNeural", # 男声,阳光
# === 英语-英国 ===
"sonia": "en-GB-SoniaNeural", # 女声,英音
"ryan": "en-GB-RyanNeural", # 男声,英音
"libby": "en-GB-LibbyNeural", # 女声,英音
"thomas": "en-GB-ThomasNeural", # 男声,英音
"maisie": "en-GB-MaisieNeural", # 女声,年轻英音
# === 英语-其他 ===
"natasha": "en-AU-NatashaNeural", # 女声,澳音
"william": "en-AU-WilliamNeural", # 男声,澳音
"clara": "en-CA-ClaraNeural", # 女声,加音
"liam": "en-CA-LiamNeural", # 男声,加音
}
def get_voice(voice_key: str) -> str:
"""获取完整 voice ID"""
voice_key = voice_key.lower()
if voice_key in VOICES:
return VOICES[voice_key]
# 如果用户直接传了完整 ID,直接返回
if "Neural" in voice_key:
return voice_key
# 默认
return VOICES["xiaoxiao"]
def split_text(text: str, max_chars: int = 3000) -> list:
"""
将长文本按段落分割,每段不超过 max_chars 字符
edge-tts 单次有长度限制,建议分段处理
"""
paragraphs = text.split("\n")
chunks = []
current_chunk = ""
for para in paragraphs:
para = para.strip()
if not para:
continue
# 如果当前段落加上已有内容超过限制,先保存当前块
if len(current_chunk) + len(para) > max_chars and current_chunk:
chunks.append(current_chunk.strip())
current_chunk = para
else:
current_chunk += "\n" + para if current_chunk else para
if current_chunk:
chunks.append(current_chunk.strip())
return chunks if chunks else [text]
async def text_to_speech(text: str, output_file: str, voice: str, rate: str = "+0%", volume: str = "+0%"):
"""将文字转换为语音并保存"""
communicate = edge_tts.Communicate(text, voice, rate=rate, volume=volume)
await communicate.save(output_file)
async def convert_file(input_path: str, output_path: str, voice: str, rate: str, volume: str):
"""转换单个文件"""
# 读取 txt 文件
print(f"读取文件: {input_path}")
with open(input_path, "r", encoding="utf-8") as f:
text = f.read()
if not text.strip():
print("错误: 文件内容为空")
return False
total_chars = len(text)
print(f"总字数: {total_chars}")
print(f"语音: {voice}")
print(f"语速: {rate}")
# 分段处理
chunks = split_text(text)
print(f"分 {len(chunks)} 段处理...")
if len(chunks) == 1:
# 单段直接转换
print("转换中...")
await text_to_speech(chunks[0], output_path, voice, rate, volume)
else:
# 多段分别转换后合并
temp_files = []
for i, chunk in enumerate(chunks, 1):
temp_file = f"{output_path}.part{i}.mp3"
temp_files.append(temp_file)
print(f" 处理第 {i}/{len(chunks)} 段 ({len(chunk)} 字)...")
await text_to_speech(chunk, temp_file, voice, rate, volume)
# 合并音频文件(使用 ffmpeg)
print("合并音频片段...")
merge_audio_files(temp_files, output_path)
# 清理临时文件
for f in temp_files:
os.remove(f)
print(f"\n✅ 完成: {output_path}")
file_size = os.path.getsize(output_path) / 1024
print(f"文件大小: {file_size:.1f} KB")
return True
def merge_audio_files(input_files: list, output_file: str):
"""使用 ffmpeg 合并多个 MP3 文件"""
# 创建 concat list 文件
list_file = output_file + ".list.txt"
with open(list_file, "w", encoding="utf-8") as f:
for path in input_files:
# ffmpeg concat 需要转义单引号
escaped = path.replace("\'", "\'\\\'\'")
f.write(f"file \'{escaped}\'\n")
cmd = [
"ffmpeg", "-y", "-f", "concat", "-safe", "0",
"-i", list_file,
"-acodec", "copy",
output_file
]
import subprocess
result = subprocess.run(cmd, capture_output=True, text=True)
os.remove(list_file)
if result.returncode != 0:
print(f"ffmpeg 合并警告: {result.stderr}")
# 备用方案:重新编码合并
filter_str = ""
inputs = []
for i, path in enumerate(input_files):
inputs.extend(["-i", path])
filter_str += f"[{i}:a:0]"
filter_str += f"concat=n={len(input_files)}:v=0:a=1[outa]"
cmd2 = ["ffmpeg", "-y"] + inputs + ["-filter_complex", filter_str, "-map", "[outa]", "-b:a", "192k", output_file]
subprocess.run(cmd2, check=True)
def list_voices():
"""列出可用语音"""
print("常用语音列表:")
print("=" * 60)
print("\n【中文】")
print("-" * 60)
for key, voice_id in VOICES.items():
if voice_id.startswith("zh-CN"):
gender = "男" if "Yun" in voice_id else "女"
print(f" {key:12s} -> {voice_id} ({gender})")
print("\n【英语-美国】")
print("-" * 60)
for key, voice_id in VOICES.items():
if voice_id.startswith("en-US"):
gender = "男" if "Guy" in voice_id or "Christopher" in voice_id or "Eric" in voice_id or "Roger" in voice_id or "Steffan" in voice_id else "女"
print(f" {key:12s} -> {voice_id} ({gender})")
print("\n【英语-英国】")
print("-" * 60)
for key, voice_id in VOICES.items():
if voice_id.startswith("en-GB"):
gender = "男" if "Ryan" in voice_id or "Thomas" in voice_id else "女"
print(f" {key:12s} -> {voice_id} ({gender})")
print("\n【英语-其他】")
print("-" * 60)
for key, voice_id in VOICES.items():
if voice_id.startswith("en-") and not voice_id.startswith("en-US") and not voice_id.startswith("en-GB"):
gender = "男" if "William" in voice_id or "Liam" in voice_id else "女"
print(f" {key:12s} -> {voice_id} ({gender})")
print("=" * 60)
print("\n查看全部语音:")
print(" edge-tts --list-voices | grep en-US # 美式英语")
print(" edge-tts --list-voices | grep en-GB # 英式英语")
print(" edge-tts --list-voices | grep zh-CN # 中文")
def main():
parser = argparse.ArgumentParser(
description="txt 文档转 MP3 语音",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
python3 txt2mp3.py article.txt
python3 txt2mp3.py article.txt -o book.mp3
python3 txt2mp3.py article.txt -v yunxi -r +10%
python3 txt2mp3.py article.txt -v xiaoxiao -r -10% -vol +20%
语速 rate:
+0% 默认速度
+50% 快50%
-20% 慢20%
音量 volume:
+0% 默认音量
+50% 大50%
-20% 小20%
"""
)
parser.add_argument("input", nargs="?", help="输入 txt 文件路径")
parser.add_argument("-o", "--output", default=None, help="输出 MP3 文件路径 (默认: 同输入文件名)")
parser.add_argument("-v", "--voice", default="xiaoxiao", help="语音名称 (默认: xiaoxiao)")
parser.add_argument("-r", "--rate", default="+0%", help="语速调整,如 +10%, -20% (默认: +0%)")
parser.add_argument("-vol", "--volume", default="+0%", help="音量调整,如 +20%, -10% (默认: +0%)")
parser.add_argument("--list-voices", action="store_true", help="列出可用语音")
args = parser.parse_args()
if args.list_voices:
list_voices()
return
if not args.input:
parser.print_help()
return
input_path = args.input
if not os.path.exists(input_path):
print(f"错误: 文件不存在: {input_path}")
sys.exit(1)
# 确定输出路径
if args.output:
output_path = args.output
else:
output_path = os.path.splitext(input_path)[0] + ".mp3"
# 获取语音 ID
voice = get_voice(args.voice)
# 执行转换
try:
asyncio.run(convert_file(input_path, output_path, voice, args.rate, args.volume))
except KeyboardInterrupt:
print("\n用户取消")
except Exception as e:
print(f"\n错误: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
微信
支付宝