|
|
""" |
|
|
Genie TTS 核心引擎模块 |
|
|
包含Genie TTS的主要功能和接口 |
|
|
""" |
|
|
|
|
|
import os |
|
|
import tempfile |
|
|
import logging |
|
|
import shutil |
|
|
from installer import setup_genie_import |
|
|
from config import ( |
|
|
AVAILABLE_CHARACTERS, MODEL_FILES, MODEL_SIZES, |
|
|
get_cache_dir, get_character_cache_dir, setup_environment |
|
|
) |
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
genie, install_error = setup_genie_import() |
|
|
|
|
|
|
|
|
class GenieTTSInterface: |
|
|
"""Genie TTS 接口类""" |
|
|
|
|
|
def __init__(self): |
|
|
self.available_characters = AVAILABLE_CHARACTERS |
|
|
self.current_character = None |
|
|
self.model_cache_dir = get_cache_dir() |
|
|
self.is_initialized = False |
|
|
self.install_error = install_error |
|
|
|
|
|
def check_model_availability(self, character_name): |
|
|
"""检查模型是否已缓存""" |
|
|
character_cache_dir = get_character_cache_dir(self.model_cache_dir, character_name) |
|
|
|
|
|
if not os.path.exists(character_cache_dir): |
|
|
return False |
|
|
|
|
|
for file_name in MODEL_FILES: |
|
|
if not os.path.exists(os.path.join(character_cache_dir, file_name)): |
|
|
return False |
|
|
return True |
|
|
|
|
|
def initialize_genie(self): |
|
|
"""初始化Genie TTS环境""" |
|
|
if self.is_initialized: |
|
|
return True |
|
|
|
|
|
try: |
|
|
setup_environment() |
|
|
|
|
|
|
|
|
if hasattr(genie, '_internal'): |
|
|
logger.info("Genie TTS环境初始化成功") |
|
|
|
|
|
self.is_initialized = True |
|
|
return True |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"初始化Genie TTS失败: {e}") |
|
|
return False |
|
|
|
|
|
def load_character(self, character_name): |
|
|
"""加载角色模型""" |
|
|
if not genie: |
|
|
return None, "Genie TTS未正确安装" |
|
|
|
|
|
if not self.initialize_genie(): |
|
|
return None, "Genie TTS初始化失败" |
|
|
|
|
|
try: |
|
|
logger.info(f"正在加载角色: {character_name}") |
|
|
|
|
|
|
|
|
if self.check_model_availability(character_name): |
|
|
logger.info(f"使用缓存的模型: {character_name}") |
|
|
else: |
|
|
logger.info(f"首次下载模型: {character_name},请稍候...") |
|
|
|
|
|
|
|
|
genie.load_predefined_character(character_name) |
|
|
self.current_character = character_name |
|
|
|
|
|
return f"角色 {character_name} 加载成功!", "" |
|
|
|
|
|
except Exception as e: |
|
|
error_msg = str(e) |
|
|
logger.error(f"加载角色失败: {error_msg}") |
|
|
|
|
|
|
|
|
if "network" in error_msg.lower() or "connection" in error_msg.lower(): |
|
|
return None, "网络连接错误,请检查网络连接后重试" |
|
|
elif "disk space" in error_msg.lower(): |
|
|
return None, "磁盘空间不足,请清理空间后重试" |
|
|
elif "timeout" in error_msg.lower(): |
|
|
return None, "下载超时,请重试" |
|
|
else: |
|
|
return None, f"加载角色失败: {error_msg}" |
|
|
|
|
|
def estimate_download_size(self, character_name): |
|
|
"""估算下载大小""" |
|
|
return MODEL_SIZES.get(character_name, 200) |
|
|
|
|
|
def cleanup_cache(self): |
|
|
"""清理缓存""" |
|
|
try: |
|
|
if os.path.exists(self.model_cache_dir): |
|
|
shutil.rmtree(self.model_cache_dir) |
|
|
self.model_cache_dir = get_cache_dir() |
|
|
logger.info("缓存清理完成") |
|
|
return True |
|
|
except Exception as e: |
|
|
logger.error(f"清理缓存失败: {e}") |
|
|
return False |
|
|
|
|
|
def synthesize_speech(self, text, character_name, play_audio=False): |
|
|
"""文本转语音 - 增强版""" |
|
|
if not genie: |
|
|
if self.install_error: |
|
|
error_msg = f"Genie TTS 安装失败: {self.install_error}" |
|
|
if "portaudio" in self.install_error.lower(): |
|
|
error_msg += "\n\n💡 解决方案:\n" |
|
|
error_msg += "1. 在本地环境运行此应用(支持完整依赖)\n" |
|
|
error_msg += "2. 或等待我们提供不依赖PyAudio的替代方案\n" |
|
|
error_msg += "3. 查看项目README了解更多信息" |
|
|
return None, error_msg |
|
|
else: |
|
|
return None, "Genie TTS未正确安装,原因未知" |
|
|
|
|
|
if not text.strip(): |
|
|
return None, "请输入要合成的文本" |
|
|
|
|
|
|
|
|
if len(text) > 500: |
|
|
return None, "文本过长(超过500字符),请缩短文本长度" |
|
|
|
|
|
if character_name != self.current_character: |
|
|
status, error = self.load_character(character_name) |
|
|
if error: |
|
|
return None, error |
|
|
|
|
|
try: |
|
|
|
|
|
processed_text = self.preprocess_text(text) |
|
|
|
|
|
|
|
|
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_file: |
|
|
output_path = tmp_file.name |
|
|
|
|
|
logger.info(f"正在合成语音: {processed_text[:50]}...") |
|
|
|
|
|
|
|
|
original_env = os.environ.get('PYTORCH_JIT_USE_NNC_NOT_NVFUSER', None) |
|
|
os.environ['PYTORCH_JIT_USE_NNC_NOT_NVFUSER'] = '1' |
|
|
|
|
|
try: |
|
|
|
|
|
genie.tts( |
|
|
character_name=character_name, |
|
|
text=processed_text, |
|
|
play=False, |
|
|
split_sentence=True, |
|
|
save_path=output_path |
|
|
) |
|
|
finally: |
|
|
|
|
|
if original_env is None and 'PYTORCH_JIT_USE_NNC_NOT_NVFUSER' in os.environ: |
|
|
del os.environ['PYTORCH_JIT_USE_NNC_NOT_NVFUSER'] |
|
|
elif original_env is not None: |
|
|
os.environ['PYTORCH_JIT_USE_NNC_NOT_NVFUSER'] = original_env |
|
|
|
|
|
|
|
|
if not os.path.exists(output_path): |
|
|
return None, "语音合成失败:输出文件未生成" |
|
|
|
|
|
file_size = os.path.getsize(output_path) |
|
|
if file_size == 0: |
|
|
return None, "语音合成失败:输出文件为空" |
|
|
elif file_size < 1000: |
|
|
return None, "语音合成失败:输出文件异常小" |
|
|
|
|
|
logger.info(f"语音合成成功,文件大小: {file_size/1024:.1f}KB") |
|
|
return output_path, "" |
|
|
|
|
|
except Exception as e: |
|
|
error_msg = str(e) |
|
|
logger.error(f"语音合成失败: {error_msg}") |
|
|
|
|
|
|
|
|
if "out of memory" in error_msg.lower() or "memory" in error_msg.lower(): |
|
|
return None, "内存不足,请尝试缩短文本或重启应用" |
|
|
elif "cuda" in error_msg.lower(): |
|
|
return None, "GPU相关错误,正在使用CPU模式重试" |
|
|
elif "model" in error_msg.lower(): |
|
|
return None, "模型加载错误,请重新选择角色" |
|
|
elif "timeout" in error_msg.lower(): |
|
|
return None, "处理超时,请尝试缩短文本" |
|
|
else: |
|
|
return None, f"语音合成失败: {error_msg}" |
|
|
|
|
|
def preprocess_text(self, text): |
|
|
"""文本预处理""" |
|
|
|
|
|
text = text.strip() |
|
|
|
|
|
|
|
|
replacements = { |
|
|
'"': '"', |
|
|
'"': '"', |
|
|
''': "'", |
|
|
''': "'", |
|
|
'—': '一', |
|
|
'–': '-', |
|
|
} |
|
|
|
|
|
for old, new in replacements.items(): |
|
|
text = text.replace(old, new) |
|
|
|
|
|
|
|
|
if text and not text.endswith(('。', '!', '?', '.', '!', '?')): |
|
|
text += '。' |
|
|
|
|
|
return text |
|
|
|
|
|
def get_system_info(self): |
|
|
"""获取系统信息用于调试""" |
|
|
try: |
|
|
|
|
|
try: |
|
|
import psutil |
|
|
memory = psutil.virtual_memory() |
|
|
disk = psutil.disk_usage('/') |
|
|
|
|
|
return { |
|
|
'memory_total': f"{memory.total / (1024**3):.1f}GB", |
|
|
'memory_available': f"{memory.available / (1024**3):.1f}GB", |
|
|
'memory_percent': f"{memory.percent}%", |
|
|
'disk_free': f"{disk.free / (1024**3):.1f}GB" |
|
|
} |
|
|
except ImportError: |
|
|
|
|
|
total, used, free = shutil.disk_usage('/') |
|
|
return { |
|
|
'disk_free': f"{free / (1024**3):.1f}GB", |
|
|
'disk_total': f"{total / (1024**3):.1f}GB", |
|
|
'status': "基础系统信息 (psutil 未安装)" |
|
|
} |
|
|
except Exception as e: |
|
|
return {"status": f"无法获取系统信息: {str(e)}"} |
|
|
|
|
|
|
|
|
|
|
|
tts_interface = GenieTTSInterface() |