batch

import ssl
_orig_ctx = ssl.create_default_context
def _patched_ctx(args, **kwargs):
ctx = _orig_ctx(args, **kwargs)
ctx.verify_flags &= ~ssl.VERIFY_X509_STRICT
return ctx
ssl.create_default_context = _patched_ctx

import re
import subprocess
import sys
import os
current_dir = os.path.dirname(os.path.abspath(file))
sys.path.append(current_dir)
sys.path.append(os.path.join(current_dir, "indextts"))

from indextts.infer_v2 import IndexTTS2

====================================================================

配置(根据需要修改)

====================================================================

INPUT_TXT = "/home/wangrui/F5-TTS/data/text/titanic_romantic_narration_pure.txt" # 输入 txt 路径
SPK_AUDIO_PROMPT = "/home/wangrui/F5-TTS/ref_voice/0013_001405.wav" # 音色参考音频
OUTPUT_DIR = "outputs/chapters" # 输出目录

全局情绪描述(整批共用)

GLOBAL_EMO_TEXT = "Rose, I love you, but I'm so sad."
EMO_ALPHA = 0.6 # 文本情绪模式建议 ≤ 0.6
USE_RANDOM = False
VERBOSE = True

时长后处理:True 时用 ffmpeg 把生成的音频拉伸到 txt 里指定的秒数

(需要系统已安装 ffmpeg 和 ffprobe)

注意:当前官方 IndexTTS2 尚未启用 duration control,所以只能后处理变速对齐。

STRETCH_TO_DURATION = True

====================================================================

解析 txt

格式:每章节 3+ 行(章节名 / 时长秒数 / 文本(可跨多行)),章节之间用空行分隔

====================================================================

def parse_chapters(path):
with open(path, "r", encoding="utf-8") as f:
raw = f.read()
blocks = re.split(r"\n\s*\n", raw.strip()) # 按空行切块
chapters = []
for idx, block in enumerate(blocks, 1):
lines = [ln.strip() for ln in block.splitlines() if ln.strip()]
if len(lines) < 3:
print(f"⚠️ 跳过第 {idx} 块(行数 < 3):{lines}")
continue
name = lines[0]
try:
duration = float(lines[1])
except ValueError:
print(f"⚠️ 跳过第 {idx} 块(第二行不是数字):{lines[1]}")
continue
text = " ".join(lines[2:]) # 多行文本拼接成一行
chapters.append({"name": name, "duration": duration, "text": text})
return chapters

====================================================================

可选:用 ffmpeg 把音频时长拉伸到目标秒数

atempo 单次有效范围 [0.5, 2.0],超出范围会自动串联多次

====================================================================

def stretch_audio(in_path, out_path, target_sec):
# 取实际时长
probe = subprocess.run(
["ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", in_path],
capture_output=True, text=True, check=True
)
actual_sec = float(probe.stdout.strip())
# 计算变速因子:atempo 是"加速倍数",>1 加速、<1 减速
tempo = actual_sec / target_sec
# 拆分到 [0.5, 2.0] 区间
filters = []
remaining = tempo
while remaining > 2.0:
filters.append("atempo=2.0")
remaining /= 2.0
while remaining < 0.5:
filters.append("atempo=0.5")
remaining /= 0.5
filters.append(f"atempo={remaining:.4f}")
af = ",".join(filters)
subprocess.run(
["ffmpeg", "-y", "-i", in_path, "-filter:a", af, out_path],
capture_output=True, check=True
)
print(f" stretched: {actual_sec:.2f}s -> {target_sec:.2f}s (tempo={tempo:.3f})")

====================================================================

主流程

====================================================================

def main():
chapters = parse_chapters(INPUT_TXT)
print(f">> Parsed {len(chapters)} chapters from {INPUT_TXT}")

os.makedirs(OUTPUT_DIR, exist_ok=True)
tts = IndexTTS2(
cfg_path="checkpoints/config.yaml",
model_dir="checkpoints",
use_fp16=True,
use_cuda_kernel=False,
use_deepspeed=False,
)

failed = []
for i, ch in enumerate(chapters, 1):
name = ch["name"]
duration = ch["duration"]
text = ch["text"]
out_path = os.path.join(OUTPUT_DIR, f"{name}.wav")

print(f"\n[{i}/{len(chapters)}] {name} (target {duration}s)")
print(f" text: {text[:80]}{'...' if len(text) > 80 else ''}")

try:
# 如果要后处理拉伸,先生成到临时文件
gen_path = out_path + ".raw.wav" if STRETCH_TO_DURATION else out_path

tts.infer(
spk_audio_prompt=SPK_AUDIO_PROMPT,
text=text,
output_path=gen_path,
use_emo_text=True,
emo_text=GLOBAL_EMO_TEXT,
emo_alpha=EMO_ALPHA,
use_random=USE_RANDOM,
verbose=VERBOSE,
)

if STRETCH_TO_DURATION:
stretch_audio(gen_path, out_path, duration)
os.remove(gen_path)

except Exception as e:
print(f"❌ {name} 失败:{e}")
failed.append(name)

print(f"\n✅ Done. {len(chapters) - len(failed)}/{len(chapters)} 成功")
if failed:
print(" 失败章节:", failed)
print(f" 输出目录:{OUTPUT_DIR}/")

if name == "main":
main()

posted @ 2026-05-28 15:28  ruiw123  阅读(6)  评论(0)    收藏  举报