diff --git a/app/config/config.py b/app/config/config.py index 641bac4..908afea 100644 --- a/app/config/config.py +++ b/app/config/config.py @@ -11,11 +11,14 @@ config_file = f"{root_dir}/config.toml" version_file = f"{root_dir}/project_version" INDEXTTS_ENGINE = "indextts" INDEXTTS_DISPLAY_NAME = "IndexTTS-1.5-windows" +INDEXTTS_MACOS_ENGINE = "indextts_macos" +INDEXTTS_MACOS_DISPLAY_NAME = "IndexTTS-1.5-macOS" INDEXTTS2_ENGINE = "indextts2" INDEXTTS2_DISPLAY_NAME = "IndexTTS-2" OMNIVOICE_ENGINE = "omnivoice" OMNIVOICE_DISPLAY_NAME = "OmniVoice" INDEXTTS_VOICE_PREFIX = f"{INDEXTTS_ENGINE}:" +INDEXTTS_MACOS_VOICE_PREFIX = f"{INDEXTTS_MACOS_ENGINE}:" INDEXTTS2_VOICE_PREFIX = f"{INDEXTTS2_ENGINE}:" OMNIVOICE_VOICE_PREFIX = f"{OMNIVOICE_ENGINE}:" INDEXTTS2_EMOTION_VECTOR_FIELDS = ( @@ -169,6 +172,7 @@ def save_config(): _cfg["tts_qwen"] = tts_qwen _cfg["fun_asr"] = fun_asr _cfg["indextts"] = indextts + _cfg["indextts_macos"] = indextts_macos _cfg["indextts2"] = indextts2 _cfg["omnivoice"] = omnivoice _cfg["doubaotts"] = doubaotts @@ -187,6 +191,7 @@ frames = _cfg.get("frames", {}) tts_qwen = _cfg.get("tts_qwen", {}) fun_asr = _cfg.get("fun_asr", {}) indextts = _cfg.get("indextts", {}) +indextts_macos = _cfg.get("indextts_macos", {}) indextts2 = _cfg.get("indextts2", {}) omnivoice = _cfg.get("omnivoice", {}) doubaotts = _cfg.get("doubaotts", {}) diff --git a/app/config/test_config_bootstrap_unittest.py b/app/config/test_config_bootstrap_unittest.py index 8034d02..0931c79 100644 --- a/app/config/test_config_bootstrap_unittest.py +++ b/app/config/test_config_bootstrap_unittest.py @@ -1,6 +1,7 @@ import tempfile import unittest from pathlib import Path +from unittest.mock import patch try: import tomllib @@ -15,6 +16,30 @@ from app.config.defaults import ( class ConfigBootstrapDefaultsTests(unittest.TestCase): + def test_save_config_keeps_macos_tts_settings_independent(self): + macos_settings = { + "api_url": "http://127.0.0.1:7866", + "reference_audio": "/tmp/macos-reference.wav", + "speed": 1.1, + } + windows_settings = { + "api_url": "http://127.0.0.1:8081/tts", + "reference_audio": "/tmp/windows-reference.wav", + } + + with tempfile.TemporaryDirectory() as tmp_dir: + config_path = Path(tmp_dir) / "config.toml" + with ( + patch.object(cfg, "config_file", str(config_path)), + patch.object(cfg, "indextts_macos", dict(macos_settings)), + patch.object(cfg, "indextts", dict(windows_settings)), + ): + cfg.save_config() + saved_config = tomllib.loads(config_path.read_text(encoding="utf-8")) + + self.assertEqual(macos_settings, saved_config["indextts_macos"]) + self.assertEqual(windows_settings, saved_config["indextts"]) + def test_load_config_bootstraps_webui_llm_defaults(self): original_root_dir = cfg.root_dir original_config_file = cfg.config_file diff --git a/app/services/jianying_task.py b/app/services/jianying_task.py index 941a2c8..dd20468 100644 --- a/app/services/jianying_task.py +++ b/app/services/jianying_task.py @@ -114,6 +114,10 @@ def _normalize_indextts_reference_audio(params: VideoClipParams) -> None: tts_config = config.indextts voice_prefix = config.INDEXTTS_VOICE_PREFIX display_name = "IndexTTS-1.5" + elif params.tts_engine == config.INDEXTTS_MACOS_ENGINE: + tts_config = config.indextts_macos + voice_prefix = config.INDEXTTS_MACOS_VOICE_PREFIX + display_name = config.INDEXTTS_MACOS_DISPLAY_NAME elif params.tts_engine == config.INDEXTTS2_ENGINE: tts_config = config.indextts2 voice_prefix = config.INDEXTTS2_VOICE_PREFIX diff --git a/app/services/test_indextts_macos_tts_unittest.py b/app/services/test_indextts_macos_tts_unittest.py new file mode 100644 index 0000000..1dd91e2 --- /dev/null +++ b/app/services/test_indextts_macos_tts_unittest.py @@ -0,0 +1,102 @@ +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +from app.services import voice + + +class FakeResponse: + def __init__(self, *, status_code=200, content=b"", payload=None, content_type="application/json"): + self.status_code = status_code + self.content = content + self._payload = payload or {} + self.headers = {"content-type": content_type} + self.text = "OK" + + def json(self): + return self._payload + + +class IndexTTSMacOSTtsTests(unittest.TestCase): + def setUp(self): + self.original_config = dict(voice.config.indextts_macos) + self.original_proxy = dict(voice.config.proxy) + + def tearDown(self): + voice.config.indextts_macos.clear() + voice.config.indextts_macos.update(self.original_config) + voice.config.proxy.clear() + voice.config.proxy.update(self.original_proxy) + + def test_uploads_reference_audio_and_downloads_pack_output_url(self): + voice.config.indextts_macos.clear() + voice.config.indextts_macos.update( + { + "api_url": "http://127.0.0.1:7866", + "speed": 1.1, + "seed": 42, + "max_mel_tokens": 800, + "max_text_tokens_per_segment": 120, + "interval_silence": 200, + "temperature": 1.0, + "top_p": 0.8, + "top_k": 30, + "repetition_penalty": 10.0, + "segment_overlap_ms": 50, + } + ) + voice.config.proxy.clear() + + generation_response = FakeResponse(payload={"output_url": "/outputs/speech.wav"}) + download_response = FakeResponse(content=b"wav-bytes", content_type="audio/wav") + + with tempfile.TemporaryDirectory() as temp_dir: + reference_audio = Path(temp_dir) / "reference.wav" + output_file = Path(temp_dir) / "output.wav" + reference_audio.write_bytes(b"reference-wav") + + with ( + patch("app.services.voice.requests.post", return_value=generation_response) as post, + patch("app.services.voice.requests.get", return_value=download_response) as get, + patch("app.services.voice.get_audio_duration_from_file", return_value=1.25), + ): + result = voice.indextts_macos_tts( + text=" macOS 接口测试。 ", + voice_name=f"indextts_macos:{reference_audio}", + voice_file=str(output_file), + ) + + output_bytes = output_file.read_bytes() if output_file.exists() else b"" + + self.assertIsNotNone(result) + self.assertEqual(output_bytes, b"wav-bytes") + self.assertEqual( + post.call_args.args[0], + "http://127.0.0.1:7866/v1/audio/speech/upload", + ) + self.assertEqual( + post.call_args.kwargs["data"], + { + "text": "macOS 接口测试。", + "speed": 1.1, + "seed": 42, + "max_mel_tokens": 800, + "max_text_tokens_per_segment": 120, + "interval_silence": 200, + "temperature": 1.0, + "top_p": 0.8, + "top_k": 30, + "repetition_penalty": 10.0, + "segment_overlap_ms": 50, + }, + ) + self.assertIn("reference_audio", post.call_args.kwargs["files"]) + self.assertEqual( + get.call_args.args[0], + "http://127.0.0.1:7866/outputs/speech.wav", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/app/services/voice.py b/app/services/voice.py index 1f8e759..20a6223 100644 --- a/app/services/voice.py +++ b/app/services/voice.py @@ -1302,6 +1302,10 @@ def tts( logger.info("分发到 IndexTTS-1.5") return indextts_tts(text, voice_name, voice_file, speed=voice_rate) + if tts_engine == config.INDEXTTS_MACOS_ENGINE: + logger.info("分发到 IndexTTS-1.5-macOS") + return indextts_macos_tts(text, voice_name, voice_file) + if tts_engine == config.INDEXTTS2_ENGINE: logger.info("分发到 IndexTTS-2") return indextts2_tts(text, voice_name, voice_file) @@ -1796,6 +1800,7 @@ def tts_multiple(task_id: str, list_script: list, voice_name: str, voice_rate: f tts_results = [] audio_extension = ".wav" if tts_engine in ( config.INDEXTTS_ENGINE, + config.INDEXTTS_MACOS_ENGINE, config.INDEXTTS2_ENGINE, config.OMNIVOICE_ENGINE, ) else ".mp3" @@ -1828,7 +1833,12 @@ def tts_multiple(task_id: str, list_script: list, voice_name: str, voice_rate: f if ( is_soulvoice_voice(voice_name) or is_qwen_engine(tts_engine) - or tts_engine in (config.INDEXTTS_ENGINE, config.INDEXTTS2_ENGINE, config.OMNIVOICE_ENGINE) + or tts_engine in ( + config.INDEXTTS_ENGINE, + config.INDEXTTS_MACOS_ENGINE, + config.INDEXTTS2_ENGINE, + config.OMNIVOICE_ENGINE, + ) or tts_engine == "doubaotts" ): # 获取实际音频文件的时长 @@ -2271,6 +2281,13 @@ def parse_indextts2_voice(voice_name: str) -> str: return voice_name +def parse_indextts_macos_voice(voice_name: str) -> str: + """解析 IndexTTS-1.5-macOS 参考音频路径。""" + if isinstance(voice_name, str) and voice_name.startswith(config.INDEXTTS_MACOS_VOICE_PREFIX): + return voice_name[len(config.INDEXTTS_MACOS_VOICE_PREFIX):] + return voice_name + + def parse_omnivoice_voice(voice_name: str) -> str: """ 解析 OmniVoice 语音名称 @@ -2414,6 +2431,149 @@ def _normalize_indextts2_api_url(api_url: str) -> str: return f"{api_url}{upload_path}" +def _normalize_indextts_macos_api_url(api_url: str) -> str: + """Return the IndexTTS 1.5 MLX Pack multipart upload endpoint.""" + api_url = (api_url or "http://127.0.0.1:7866").strip().rstrip("/") + upload_path = "/v1/audio/speech/upload" + speech_path = "/v1/audio/speech" + if api_url.endswith(upload_path): + return api_url + if api_url.endswith(speech_path): + return f"{api_url}/upload" + return f"{api_url}{upload_path}" + + +def _get_indextts_macos_number( + key: str, + default: float | int, + minimum: float | int, + maximum: float | int, + *, + integer: bool = False, +) -> float | int: + try: + raw_value = config.indextts_macos.get(key, default) + value = int(float(raw_value)) if integer else float(raw_value) + except (TypeError, ValueError): + value = default + return max(minimum, min(maximum, value)) + + +def _get_indextts_macos_seed() -> int | None: + seed = config.indextts_macos.get("seed") + if seed in (None, ""): + return None + try: + return int(seed) + except (TypeError, ValueError): + logger.warning("IndexTTS-1.5-macOS 随机种子无效,将使用随机采样: {}", seed) + return None + + +def _download_indextts_macos_audio( + response: requests.Response, + api_url: str, + voice_file: str, + proxies: dict, +) -> bool: + try: + result = response.json() + except ValueError: + logger.error("IndexTTS-1.5-macOS API 返回了无效的 JSON 响应") + return False + + download_url = result.get("output_url") if isinstance(result, dict) else "" + if not download_url: + logger.error(f"IndexTTS-1.5-macOS API 响应中没有音频下载地址: {result}") + return False + + audio_response = requests.get( + urljoin(api_url, download_url), + proxies=proxies, + timeout=120, + ) + if audio_response.status_code != 200: + logger.error( + f"IndexTTS-1.5-macOS 音频下载失败: " + f"{audio_response.status_code} - {audio_response.text}" + ) + return False + + with open(voice_file, "wb") as f: + f.write(audio_response.content) + return os.path.getsize(voice_file) > 0 + + +def indextts_macos_tts(text: str, voice_name: str, voice_file: str) -> Union[SubMaker, None]: + """使用 IndexTTS-MLX-1.5-Pack 的上传接口进行零样本语音克隆。""" + api_url = _normalize_indextts_macos_api_url( + config.indextts_macos.get("api_url", "http://127.0.0.1:7866") + ) + reference_audio_path = parse_indextts_macos_voice(voice_name) + if not reference_audio_path or not os.path.exists(reference_audio_path): + logger.error(f"IndexTTS-1.5-macOS 参考音频文件不存在: {reference_audio_path}") + return None + + data = { + "text": text.strip(), + "speed": _get_indextts_macos_number("speed", 1.0, 0.5, 2.0), + "max_mel_tokens": _get_indextts_macos_number( + "max_mel_tokens", 800, 64, 1600, integer=True + ), + "max_text_tokens_per_segment": _get_indextts_macos_number( + "max_text_tokens_per_segment", 120, 20, 600, integer=True + ), + "interval_silence": _get_indextts_macos_number( + "interval_silence", 200, 0, 2000, integer=True + ), + "temperature": _get_indextts_macos_number("temperature", 1.0, 0.0, 2.0), + "top_p": _get_indextts_macos_number("top_p", 0.8, 0.05, 1.0), + "top_k": _get_indextts_macos_number("top_k", 30, 0, 200, integer=True), + "repetition_penalty": _get_indextts_macos_number( + "repetition_penalty", 10.0, 1.0, 20.0 + ), + "segment_overlap_ms": _get_indextts_macos_number( + "segment_overlap_ms", 50, 0, 500, integer=True + ), + } + seed = _get_indextts_macos_seed() + if seed is not None: + data["seed"] = seed + + proxies = _get_configured_proxies() + for attempt in range(3): + try: + with open(reference_audio_path, "rb") as reference_audio: + logger.info(f"第 {attempt + 1} 次调用 IndexTTS-1.5-macOS API: {api_url}") + response = requests.post( + api_url, + files={"reference_audio": reference_audio}, + data=data, + proxies=proxies, + timeout=600, + ) + if response.status_code == 200 and _download_indextts_macos_audio( + response, api_url, voice_file, proxies + ): + sub_maker = new_sub_maker() + duration = get_audio_duration_from_file(voice_file) + duration_ms = int(duration * 1000) if duration > 0 else max(1000, int(len(text) * 200)) + add_subtitle_event(sub_maker, 0, duration_ms * 10000, text) + return sub_maker + logger.error( + f"IndexTTS-1.5-macOS API 调用失败: {response.status_code} - {response.text}" + ) + except requests.exceptions.Timeout: + logger.error(f"IndexTTS-1.5-macOS API 调用超时 (尝试 {attempt + 1}/3)") + except requests.exceptions.RequestException as e: + logger.error(f"IndexTTS-1.5-macOS API 网络错误: {str(e)} (尝试 {attempt + 1}/3)") + except Exception as e: + logger.error(f"IndexTTS-1.5-macOS TTS 处理错误: {str(e)} (尝试 {attempt + 1}/3)") + if attempt < 2: + time.sleep(2) + return None + + def _get_configured_proxies() -> dict: if not config.proxy.get("http"): return {} diff --git a/config.example.toml b/config.example.toml index f93b11f..b56e5b4 100644 --- a/config.example.toml +++ b/config.example.toml @@ -160,6 +160,24 @@ num_beams = 3 repetition_penalty = 10.0 +[indextts_macos] + # IndexTTS-1.5 macOS MLX Pack 语音克隆配置(仅适用于 Apple Silicon) + # 参考音频会通过 POST /v1/audio/speech/upload 上传 + api_url = "http://127.0.0.1:7866" + reference_audio_source = "resource" + # reference_audio = "/path/to/reference_audio.wav" + + speed = 1.0 + # seed = 42 + max_mel_tokens = 800 + max_text_tokens_per_segment = 120 + interval_silence = 200 + temperature = 1.0 + top_p = 0.8 + top_k = 30 + repetition_penalty = 10.0 + segment_overlap_ms = 50 + [indextts2] # IndexTTS-2 MLX Pack 语音克隆配置 # 参考音频会通过 POST /v1/audio/speech/upload 上传;可填写服务根地址或完整接口地址 diff --git a/webui/components/audio_settings.py b/webui/components/audio_settings.py index 6cb89fe..1fda62d 100644 --- a/webui/components/audio_settings.py +++ b/webui/components/audio_settings.py @@ -42,6 +42,7 @@ BGM_UPLOAD_SUBDIR = "uploaded_bgms" BGM_AUDIO_EXTENSIONS = (".mp3", ".wav", ".flac", ".m4a", ".aac", ".ogg") LOCAL_TTS_ENGINES = { config.INDEXTTS_ENGINE, + config.INDEXTTS_MACOS_ENGINE, config.INDEXTTS2_ENGINE, config.OMNIVOICE_ENGINE, } @@ -74,6 +75,7 @@ def get_tts_engine_options(tr=lambda key: key): """获取TTS引擎选项""" engine_options = { config.INDEXTTS_ENGINE: config.INDEXTTS_DISPLAY_NAME, + config.INDEXTTS_MACOS_ENGINE: config.INDEXTTS_MACOS_DISPLAY_NAME, config.INDEXTTS2_ENGINE: config.INDEXTTS2_DISPLAY_NAME, config.OMNIVOICE_ENGINE: config.OMNIVOICE_DISPLAY_NAME, "edge_tts": "Edge TTS", @@ -136,6 +138,12 @@ def get_tts_engine_descriptions(tr=lambda key: key): "use_case": tr("IndexTTS use case"), "registration": None }, + config.INDEXTTS_MACOS_ENGINE: { + "title": config.INDEXTTS_MACOS_DISPLAY_NAME, + "features": tr("IndexTTS macOS features"), + "use_case": tr("IndexTTS macOS use case"), + "registration": None + }, config.INDEXTTS2_ENGINE: { "title": config.INDEXTTS2_DISPLAY_NAME, "features": tr("IndexTTS2 features"), @@ -588,6 +596,8 @@ def render_tts_settings(tr): render_qwen3_tts_settings(tr) elif selected_engine == config.INDEXTTS_ENGINE: render_indextts_tts_settings(tr) + elif selected_engine == config.INDEXTTS_MACOS_ENGINE: + render_indextts_macos_tts_settings(tr) elif selected_engine == config.INDEXTTS2_ENGINE: render_indextts2_tts_settings(tr) elif selected_engine == config.OMNIVOICE_ENGINE: @@ -1122,6 +1132,102 @@ def render_indextts_tts_settings(tr): config.ui["voice_name"] = f"{config.INDEXTTS_VOICE_PREFIX}{reference_audio}" +def render_indextts_macos_tts_settings(tr): + """渲染 IndexTTS-1.5 macOS MLX Pack 设置。""" + tts_config = config.indextts_macos + + def bounded_value(key, default, min_value, max_value): + try: + value = float(tts_config.get(key, default)) + except (TypeError, ValueError): + value = default + return max(min_value, min(max_value, value)) + + api_url = st.text_input( + tr("API URL"), + value=tts_config.get("api_url", "http://127.0.0.1:7866"), + help=tr("IndexTTS macOS API URL Help"), + ) + reference_audio_source, reference_audio = render_indextts_reference_audio_selector( + tr, + tts_config, + "indextts_macos", + ) + + speed = st.slider( + tr("IndexTTS2 Speed"), + min_value=0.5, + max_value=2.0, + value=bounded_value("speed", 1.0, 0.5, 2.0), + step=0.05, + help=tr("IndexTTS2 Speed Help"), + ) + seed = st.text_input( + tr("IndexTTS2 Seed"), + value=str(tts_config.get("seed", "") or ""), + help=tr("IndexTTS2 Seed Help"), + placeholder=tr("IndexTTS2 Seed Placeholder"), + ) + + with st.expander(tr("Advanced Parameters"), expanded=False): + col1, col2 = st.columns(2) + with col1: + temperature = st.slider( + tr("Sampling Temperature"), 0.0, 2.0, + bounded_value("temperature", 1.0, 0.0, 2.0), 0.05, + ) + top_p = st.slider( + "Top P", 0.05, 1.0, + bounded_value("top_p", 0.8, 0.05, 1.0), 0.05, + ) + top_k = st.slider( + "Top K", 0, 200, + int(bounded_value("top_k", 30, 0, 200)), 1, + ) + max_text_tokens_per_segment = st.slider( + tr("Max Text Tokens Per Segment"), 20, 600, + int(bounded_value("max_text_tokens_per_segment", 120, 20, 600)), 10, + ) + with col2: + repetition_penalty = st.slider( + tr("Repetition Penalty"), 1.0, 20.0, + bounded_value("repetition_penalty", 10.0, 1.0, 20.0), 0.1, + ) + max_mel_tokens = st.slider( + tr("Max Mel Tokens"), 64, 1600, + int(bounded_value("max_mel_tokens", 800, 64, 1600)), 1, + ) + interval_silence = st.slider( + tr("Interval Silence"), 0, 2000, + int(bounded_value("interval_silence", 200, 0, 2000)), 50, + ) + segment_overlap_ms = st.slider( + tr("Segment Overlap"), 0, 500, + int(bounded_value("segment_overlap_ms", 50, 0, 500)), 10, + ) + + with st.expander(tr("IndexTTS macOS Usage Instructions Title"), expanded=False): + st.markdown(tr("IndexTTS macOS Usage Instructions")) + + tts_config["api_url"] = api_url + tts_config["reference_audio_source"] = reference_audio_source + tts_config["reference_audio"] = reference_audio + tts_config["speed"] = speed + tts_config["seed"] = seed.strip() + tts_config["temperature"] = temperature + tts_config["top_p"] = top_p + tts_config["top_k"] = top_k + tts_config["max_text_tokens_per_segment"] = max_text_tokens_per_segment + tts_config["repetition_penalty"] = repetition_penalty + tts_config["max_mel_tokens"] = max_mel_tokens + tts_config["interval_silence"] = interval_silence + tts_config["segment_overlap_ms"] = segment_overlap_ms + if reference_audio: + config.ui["voice_name"] = f"{config.INDEXTTS_MACOS_VOICE_PREFIX}{reference_audio}" + st.session_state["voice_rate"] = 1.0 + st.session_state["voice_pitch"] = 1.0 + + def render_indextts2_tts_settings(tr): """渲染 IndexTTS-2 MLX Pack TTS 设置""" @@ -1748,6 +1854,12 @@ def render_voice_preview_new(tr, selected_engine): voice_name = f"{config.INDEXTTS_VOICE_PREFIX}{reference_audio}" voice_rate = 1.0 # IndexTTS-1.5 不支持速度调节 voice_pitch = 1.0 # IndexTTS-1.5 不支持音调调节 + elif selected_engine == config.INDEXTTS_MACOS_ENGINE: + reference_audio = config.indextts_macos.get("reference_audio", "") + if reference_audio: + voice_name = f"{config.INDEXTTS_MACOS_VOICE_PREFIX}{reference_audio}" + voice_rate = 1.0 # 语速由 macOS Pack 配置传递 + voice_pitch = 1.0 elif selected_engine == config.INDEXTTS2_ENGINE: reference_audio = config.indextts2.get("reference_audio", "") if reference_audio: @@ -1777,6 +1889,7 @@ def render_voice_preview_new(tr, selected_engine): temp_dir = utils.storage_dir("temp", create=True) audio_format = "audio/wav" if selected_engine in ( config.INDEXTTS_ENGINE, + config.INDEXTTS_MACOS_ENGINE, config.INDEXTTS2_ENGINE, config.OMNIVOICE_ENGINE, ) else "audio/mp3" diff --git a/webui/components/test_audio_settings_unittest.py b/webui/components/test_audio_settings_unittest.py index 86990ec..2c7272c 100644 --- a/webui/components/test_audio_settings_unittest.py +++ b/webui/components/test_audio_settings_unittest.py @@ -1,6 +1,9 @@ import unittest -from webui.components.audio_settings import _normalize_source_pills_value +from webui.components.audio_settings import ( + _normalize_source_pills_value, + get_tts_engine_options, +) def zh_tr(key): @@ -12,6 +15,17 @@ def zh_tr(key): class AudioSettingsSourcePillsTests(unittest.TestCase): + def test_tts_engine_options_include_indextts_15_macos_as_local_engine(self): + options = get_tts_engine_options(lambda key: { + "Local Deployment": "本地部署", + "Cloud Service": "云服务", + }.get(key, key)) + + self.assertEqual( + "IndexTTS-1.5-macOS [本地部署]", + options["indextts_macos"], + ) + def test_normalize_source_pills_value_keeps_canonical_value(self): options = { "resource": "Select from Resource Directory", diff --git a/webui/i18n/en.json b/webui/i18n/en.json index 32668f8..a7aede1 100644 --- a/webui/i18n/en.json +++ b/webui/i18n/en.json @@ -373,9 +373,11 @@ "Tencent Cloud TTS use case": "Personal and enterprise users who need stable Chinese speech synthesis", "Tongyi Qwen3 TTS features": "Alibaba Cloud Tongyi Qwen speech synthesis with high-quality voices and multiple voice options.", "High-quality Chinese speech synthesis use case": "Users who need high-quality Chinese speech synthesis", - "IndexTTS features": "A locally or privately deployed IndexTTS-1.5 voice-cloning engine. Choose a resource audio file or upload a reference audio file, then synthesize narration in that voice.", - "IndexTTS use case": "Best for fixed narrator voices, character dubbing, or generating multiple videos with the same voice. Start the IndexTTS-1.5 API service before use. Deployment packages:\n\n- **macOS**: [Download](https://cutagent.online/resources/indextts15-mlx-macos)\n- **Windows**: [Download](https://cutagent.online/resources/indextts15-windows)", + "IndexTTS features": "A locally or privately deployed IndexTTS-1.5 voice-cloning engine for Windows. Choose or upload reference audio to synthesize narration in that voice.", + "IndexTTS use case": "Best for fixed narrator voices, character dubbing, and batch generation on Windows. Start the IndexTTS-1.5 API service before use. [Download the deployment package](https://cutagent.online/resources/indextts15-windows).", "IndexTTS download link": "Download packages:\n\n- **macOS**: [Download](https://cutagent.online/resources/indextts15-mlx-macos)\n- **Windows**: [Download](https://cutagent.online/resources/indextts15-windows)", + "IndexTTS macOS features": "A local IndexTTS-1.5 MLX voice-cloning engine for Apple Silicon that uploads reference audio to synthesize narration.", + "IndexTTS macOS use case": "Best for local fixed narrator voices and character dubbing on Apple Silicon Macs. Start IndexTTS-MLX-1.5-Pack before use. [Download the deployment package](https://cutagent.online/resources/indextts15-mlx-macos).", "IndexTTS2 features": "A locally deployed IndexTTS-2 MLX Pack voice-cloning engine with emotion control and advanced generation parameters.", "IndexTTS2 use case": "Best for fixed voices, emotional narration, and local speech synthesis workflows that need finer sampling controls. Start the IndexTTS-2 MLX Pack service before use. Deployment packages:\n\n- **macOS**: [Download](https://cutagent.online/resources/indextts2-full-macos)\n- **Windows**: Coming soon", "OmniVoice features": "A locally or privately deployed OmniVoice-Pack multilingual TTS engine with automatic voice generation, voice design, and reference-audio cloning.", @@ -564,6 +566,7 @@ "Select Qwen3 TTS Voice": "Select a Qwen3 TTS voice", "API URL": "API URL", "IndexTTS API URL Help": "IndexTTS-1.5 API service URL", + "IndexTTS macOS API URL Help": "IndexTTS-MLX-1.5-Pack service URL. Enter the service root or the full /v1/audio/speech/upload endpoint.", "IndexTTS2 API URL Help": "IndexTTS-2 MLX Pack service URL. Enter the service root or the full /v1/audio/speech/upload endpoint.", "OmniVoice API URL Help": "OmniVoice-Pack API service URL. You can enter the service root or the full /tts endpoint.", "OmniVoice Language Code": "Synthesis Language", @@ -618,6 +621,8 @@ "Enable Sampling Help": "Enable sampling for more natural speech.", "IndexTTS Usage Instructions Title": "IndexTTS-1.5 Usage Instructions", "IndexTTS Usage Instructions": "**Zero-shot voice cloning**\n\n1. **Prepare reference audio**: upload or specify a clear audio file (3-10 seconds recommended)\n2. **Set API URL**: make sure the IndexTTS-1.5 service is running\n3. **Start synthesis**: the system will use the reference voice to synthesize new speech\n\n**Notes**:\n- Reference audio quality directly affects synthesis quality\n- Use clean audio without background noise when possible\n- Keep text length within a reasonable range\n- The first synthesis may take longer", + "IndexTTS macOS Usage Instructions Title": "IndexTTS-1.5-macOS Usage Instructions", + "IndexTTS macOS Usage Instructions": "**Local voice cloning on Apple Silicon**\n\n1. Double-click `start.command` in the Pack to start the service\n2. Keep the default API URL `http://127.0.0.1:7866`, or enter the full upload endpoint\n3. Select clean reference audio and start synthesis\n\nThe first request usually takes longer while the MLX model loads. This engine requires an Apple Silicon Mac.", "IndexTTS2 Emotion Parameters": "Emotion Parameters", "IndexTTS2 Emotion": "Emotion Override", "IndexTTS2 Emotion Help": "Leave blank to preserve the reference audio emotion. Use a label such as happy or a weighted mix such as happy:0.7,calm:0.3.", diff --git a/webui/i18n/zh.json b/webui/i18n/zh.json index a3b90f2..7bb53fb 100644 --- a/webui/i18n/zh.json +++ b/webui/i18n/zh.json @@ -311,9 +311,11 @@ "Tencent Cloud TTS use case": "个人和企业用户,需要稳定的中文语音合成", "Tongyi Qwen3 TTS features": "阿里云通义千问语音合成,音质优秀,支持多种音色", "High-quality Chinese speech synthesis use case": "需要高质量中文语音合成的用户", - "IndexTTS features": "本地/私有部署的 IndexTTS-1.5 语音克隆引擎。选择资源目录音频或上传参考音频后,可按该音色合成旁白。", - "IndexTTS use case": "适合需要固定旁白音色、角色配音或批量生成同一音色视频的场景。使用前请先启动 IndexTTS-1.5 API 服务;部署包下载:\n\n- **macOS**:[下载地址](https://cutagent.online/resources/indextts15-mlx-macos)\n- **Windows**:[下载地址](https://cutagent.online/resources/indextts15-windows)", + "IndexTTS features": "Windows 本地/私有部署的 IndexTTS-1.5 语音克隆引擎。选择资源目录音频或上传参考音频后,可按该音色合成旁白。", + "IndexTTS use case": "适合在 Windows 上生成固定旁白音色、角色配音或批量生成同一音色视频。使用前请先启动 IndexTTS-1.5 API 服务;[下载部署包](https://cutagent.online/resources/indextts15-windows)。", "IndexTTS download link": "下载地址:\n\n- **macOS**:[下载地址](https://cutagent.online/resources/indextts15-mlx-macos)\n- **Windows**:[下载地址](https://cutagent.online/resources/indextts15-windows)", + "IndexTTS macOS features": "面向 Apple Silicon 的 IndexTTS-1.5 MLX 本地语音克隆引擎,通过上传参考音频合成旁白。", + "IndexTTS macOS use case": "适合在 Apple Silicon Mac 上本地生成固定旁白音色和角色配音。使用前请启动 IndexTTS-MLX-1.5-Pack;[下载部署包](https://cutagent.online/resources/indextts15-mlx-macos)。", "IndexTTS2 features": "本地部署的 IndexTTS-2 MLX Pack 语音克隆引擎,支持情感控制和更完整的生成参数。", "IndexTTS2 use case": "适合需要固定音色、情绪化旁白或更细致采样控制的本地语音合成场景。使用前请先启动 IndexTTS-2 MLX Pack 服务;部署包下载:\n\n- **macOS**:[下载地址](https://cutagent.online/resources/indextts2-full-macos)\n- **Windows**:待更新", "OmniVoice features": "本地/私有部署的 OmniVoice-Pack 多语种语音合成引擎,支持自动音色、指令音色和参考音频克隆。", @@ -503,6 +505,7 @@ "Select Qwen3 TTS Voice": "选择 Qwen3 TTS 音色", "API URL": "API 地址", "IndexTTS API URL Help": "IndexTTS-1.5 API 服务地址", + "IndexTTS macOS API URL Help": "IndexTTS-MLX-1.5-Pack 服务地址,可填写服务根地址或完整 /v1/audio/speech/upload 地址", "IndexTTS2 API URL Help": "IndexTTS-2 MLX Pack 服务地址,可填写服务根地址或完整 /v1/audio/speech/upload 地址", "OmniVoice API URL Help": "OmniVoice-Pack API 服务地址,可填写服务根地址或完整 /tts 地址", "OmniVoice Language Code": "合成语言", @@ -557,6 +560,8 @@ "Enable Sampling Help": "启用采样可以获得更自然的语音", "IndexTTS Usage Instructions Title": "IndexTTS-1.5 使用说明", "IndexTTS Usage Instructions": "**零样本语音克隆**\n\n1. **准备参考音频**:上传或指定一段清晰的音频文件(建议 3-10 秒)\n2. **设置 API 地址**:确保 IndexTTS-1.5 服务正常运行\n3. **开始合成**:系统会自动使用参考音频的音色合成新语音\n\n**注意事项**:\n- 参考音频质量直接影响合成效果\n- 建议使用无背景噪音的清晰音频\n- 文本长度建议控制在合理范围内\n- 首次合成可能需要较长时间", + "IndexTTS macOS Usage Instructions Title": "IndexTTS-1.5-macOS 使用说明", + "IndexTTS macOS Usage Instructions": "**Apple Silicon 本地语音克隆**\n\n1. 双击 Pack 中的 `start.command` 启动服务\n2. 保持默认 API 地址 `http://127.0.0.1:7866`,或填写完整上传接口\n3. 选择清晰的参考音频后开始合成\n\n首次请求需要加载 MLX 模型,耗时通常更长。该引擎仅适用于 Apple Silicon Mac。", "IndexTTS2 Emotion Parameters": "情感参数", "IndexTTS2 Emotion": "情感覆盖", "IndexTTS2 Emotion Help": "留空时保留参考音频的情感;可填写单个情感,如 happy,或权重混合,如 happy:0.7,calm:0.3。",