import json import logging import os import sys import time import argparse import requests from urllib.parse import urlparse import warnings # --- 抑制警告信息 --- warnings.filterwarnings("ignore", category=FutureWarning) warnings.filterwarnings("ignore", category=UserWarning) # --- 设置系统路径 --- 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 from tools.download_files import download_model_from_huggingface def download_file(url, save_dir="temp_audio"): """ 从 URL 下载文件,或者如果路径是本地文件则直接返回路径。 """ os.makedirs(save_dir, exist_ok=True) try: result = urlparse(url) is_url = all([result.scheme, result.netloc]) except ValueError: is_url = False if not is_url: if os.path.exists(url): print(f"使用本地文件: {url}") return url else: raise FileNotFoundError(f"本地文件未找到: {url}") filename = os.path.basename(result.path) if not filename: filename = f"audio_{int(time.time())}.wav" save_path = os.path.join(save_dir, filename) print(f"正在从 {url} 下载音频到 {save_path}...") try: response = requests.get(url, stream=True, timeout=30) response.raise_for_status() with open(save_path, "wb") as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print("下载完成。") return save_path except requests.exceptions.RequestException as e: print(f"下载文件时出错: {e}") raise def main(): """ 运行命令行文本转语音应用的主函数。 """ parser = argparse.ArgumentParser( description="IndexTTS: 命令行文本转语音应用", formatter_class=argparse.RawTextHelpFormatter ) parser.add_argument("--prompt", type=str, required=True, help="需要合成的文本。") parser.add_argument("--input_audio", type=str, required=True, help="音色参考音频的 URL 或本地路径 (.wav)。") parser.add_argument("--setting", type=int, choices=[1, 2, 3, 4], required=True, help="情感控制方法:\n" "1: 与音色参考音频相同。\n" "2: 使用单独的情感参考音频。\n" "3: 使用情感向量。\n" "4: 使用文本描述来控制情感。") parser.add_argument("--emo_audio", type=str, help="情感参考音频的 URL 或本地路径 (setting 2 必需)。") parser.add_argument("--emo_weight", type=float, default=0.8, help="情感权重 (setting 2, 默认: 0.8)。") parser.add_argument("--emo_vectors", type=float, nargs=8, metavar=('喜', '怒', '哀', '惧', '厌恶', '低落', '惊喜', '平静'), help="八个情感向量值,用空格分隔 (setting 3 必需)。") parser.add_argument("--emo_text", type=str, help="情感描述文本,例如 '高兴', '伤心' (setting 4 必需)。") parser.add_argument("--output_path", type=str, default=None, help="保存输出音频的路径。如果未提供,将使用默认名称。") parser.add_argument("--model_dir", type=str, default="checkpoints", help="模型检查点目录。") parser.add_argument("--is_fp16", action="store_true", default=False, help="启用 fp16 推理。") parser.add_argument("--verbose", action="store_true", default=False, help="启用详细日志记录。") args = parser.parse_args() print("正在检查模型文件...") download_model_from_huggingface( os.path.join(current_dir, "checkpoints"), os.path.join(current_dir, "checkpoints", "hf_cache") ) print("正在加载 IndexTTS 模型...") tts = IndexTTS2( model_dir=args.model_dir, cfg_path=os.path.join(args.model_dir, "config.yaml"), is_fp16=args.is_fp16, use_cuda_kernel=False ) print("模型加载成功。") # --- 主要修改 1 --- # 确保名为 "output" 的文件夹存在 os.makedirs("output", exist_ok=True) # --- 主要修改 2 --- # 如果用户没有通过 --output_path 指定路径,则默认使用 'output/output.wav' output_path = args.output_path or os.path.join("output", "output.wav") prompt_audio_path = download_file(args.input_audio) emo_audio_prompt = None emo_alpha = 1.0 emo_vector = None use_emo_text = False emo_text_val = "" emo_control_method = args.setting - 1 if emo_control_method == 0: print("使用音色参考音频中的情感。") pass elif emo_control_method == 1: print("使用独立的情感参考音频。") if not args.emo_audio: parser.error("--emo_audio 参数在 setting 2 中是必需的。") emo_audio_prompt = download_file(args.emo_audio) emo_alpha = args.emo_weight print(f"情感参考: {emo_audio_prompt}, 权重: {emo_alpha}") elif emo_control_method == 2: print("使用情感向量进行控制。") if not args.emo_vectors: parser.error("--emo_vectors 参数在 setting 3 中是必需的。") vec_sum = sum(args.emo_vectors) if vec_sum > 1.5: raise ValueError(f"情感向量的总和不能超过1.5。当前总和: {vec_sum}") emo_vector = args.emo_vectors print(f"情感向量: {emo_vector}") elif emo_control_method == 3: print("使用文本描述来控制情感。") if not args.emo_text: parser.error("--emo_text 参数在 setting 4 中是必需的。") use_emo_text = True emo_text_val = args.emo_text print(f"情感文本: '{emo_text_val}'") print("\n开始 TTS 推理...") tts.infer( spk_audio_prompt=prompt_audio_path, text=args.prompt, output_path=output_path, emo_audio_prompt=emo_audio_prompt, emo_alpha=emo_alpha, emo_vector=emo_vector, use_emo_text=use_emo_text, emo_text=emo_text_val, verbose=args.verbose, ) print(f"\n✨ 推理完成!音频已保存至: {output_path}") if __name__ == "__main__": main()