mirror of
https://github.com/linyqh/NarratoAI.git
synced 2026-08-01 02:45:54 +00:00
fix: adapt IndexTTS-2 MLX Pack API
This commit is contained in:
parent
cd20aebacf
commit
39eae0d511
@ -10,7 +10,7 @@ root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__fi
|
|||||||
config_file = f"{root_dir}/config.toml"
|
config_file = f"{root_dir}/config.toml"
|
||||||
version_file = f"{root_dir}/project_version"
|
version_file = f"{root_dir}/project_version"
|
||||||
INDEXTTS_ENGINE = "indextts"
|
INDEXTTS_ENGINE = "indextts"
|
||||||
INDEXTTS_DISPLAY_NAME = "IndexTTS-1.5"
|
INDEXTTS_DISPLAY_NAME = "IndexTTS-1.5-windows"
|
||||||
INDEXTTS2_ENGINE = "indextts2"
|
INDEXTTS2_ENGINE = "indextts2"
|
||||||
INDEXTTS2_DISPLAY_NAME = "IndexTTS-2"
|
INDEXTTS2_DISPLAY_NAME = "IndexTTS-2"
|
||||||
OMNIVOICE_ENGINE = "omnivoice"
|
OMNIVOICE_ENGINE = "omnivoice"
|
||||||
@ -18,6 +18,16 @@ OMNIVOICE_DISPLAY_NAME = "OmniVoice"
|
|||||||
INDEXTTS_VOICE_PREFIX = f"{INDEXTTS_ENGINE}:"
|
INDEXTTS_VOICE_PREFIX = f"{INDEXTTS_ENGINE}:"
|
||||||
INDEXTTS2_VOICE_PREFIX = f"{INDEXTTS2_ENGINE}:"
|
INDEXTTS2_VOICE_PREFIX = f"{INDEXTTS2_ENGINE}:"
|
||||||
OMNIVOICE_VOICE_PREFIX = f"{OMNIVOICE_ENGINE}:"
|
OMNIVOICE_VOICE_PREFIX = f"{OMNIVOICE_ENGINE}:"
|
||||||
|
INDEXTTS2_EMOTION_VECTOR_FIELDS = (
|
||||||
|
("happy", "vec_happy"),
|
||||||
|
("angry", "vec_angry"),
|
||||||
|
("sad", "vec_sad"),
|
||||||
|
("afraid", "vec_afraid"),
|
||||||
|
("disgusted", "vec_disgusted"),
|
||||||
|
("melancholic", "vec_melancholic"),
|
||||||
|
("surprised", "vec_surprised"),
|
||||||
|
("calm", "vec_calm"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def normalize_tts_engine_name(tts_engine: str) -> str:
|
def normalize_tts_engine_name(tts_engine: str) -> str:
|
||||||
@ -28,6 +38,32 @@ def normalize_indextts_voice_prefix(voice_name: str) -> str:
|
|||||||
return voice_name
|
return voice_name
|
||||||
|
|
||||||
|
|
||||||
|
def get_indextts2_pack_emotion(indextts2_config) -> str:
|
||||||
|
"""Return the MLX Pack emotion string for current or legacy settings."""
|
||||||
|
if not isinstance(indextts2_config, dict):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
configured_emotion = str(indextts2_config.get("emotion", "")).strip()
|
||||||
|
if configured_emotion:
|
||||||
|
return configured_emotion
|
||||||
|
|
||||||
|
emotion_mode = indextts2_config.get("emotion_mode", "speaker")
|
||||||
|
if emotion_mode == "text":
|
||||||
|
return str(indextts2_config.get("emotion_text", "")).strip()
|
||||||
|
if emotion_mode != "vector":
|
||||||
|
return ""
|
||||||
|
|
||||||
|
weights = []
|
||||||
|
for emotion, field in INDEXTTS2_EMOTION_VECTOR_FIELDS:
|
||||||
|
try:
|
||||||
|
weight = float(indextts2_config.get(field, 0.0))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
if weight > 0:
|
||||||
|
weights.append(f"{emotion}:{weight:g}")
|
||||||
|
return ",".join(weights)
|
||||||
|
|
||||||
|
|
||||||
def _is_legacy_indextts2_config(indextts2_config) -> bool:
|
def _is_legacy_indextts2_config(indextts2_config) -> bool:
|
||||||
if not isinstance(indextts2_config, dict):
|
if not isinstance(indextts2_config, dict):
|
||||||
return False
|
return False
|
||||||
|
|||||||
211
app/services/test_indextts2_tts_unittest.py
Normal file
211
app/services/test_indextts2_tts_unittest.py
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
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 IndexTTS2TtsTests(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.original_indextts2 = dict(voice.config.indextts2)
|
||||||
|
self.original_proxy = dict(voice.config.proxy)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
voice.config.indextts2.clear()
|
||||||
|
voice.config.indextts2.update(self.original_indextts2)
|
||||||
|
voice.config.proxy.clear()
|
||||||
|
voice.config.proxy.update(self.original_proxy)
|
||||||
|
|
||||||
|
def test_uploads_reference_audio_and_downloads_pack_output_url(self):
|
||||||
|
voice.config.indextts2.clear()
|
||||||
|
voice.config.indextts2.update(
|
||||||
|
{
|
||||||
|
"api_url": "http://127.0.0.1:7860",
|
||||||
|
"emotion": "happy:0.7,calm:0.3",
|
||||||
|
"emo_alpha": 0.6,
|
||||||
|
"speed": 1.15,
|
||||||
|
"seed": 20260713,
|
||||||
|
"max_mel_tokens": 1500,
|
||||||
|
"max_text_tokens_per_segment": 120,
|
||||||
|
"interval_silence": 200,
|
||||||
|
"temperature": 0.8,
|
||||||
|
"top_p": 0.8,
|
||||||
|
"top_k": 30,
|
||||||
|
"repetition_penalty": 10.0,
|
||||||
|
"diffusion_steps": 25,
|
||||||
|
"cfg_rate": 0.7,
|
||||||
|
"segment_overlap_ms": 50,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
voice.config.proxy.clear()
|
||||||
|
|
||||||
|
generation_response = FakeResponse(
|
||||||
|
payload={"output": {"url": "/outputs/audio/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.indextts2_tts(
|
||||||
|
text=" 新版接口测试。 ",
|
||||||
|
voice_name=f"indextts2:{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:7860/v1/audio/speech/upload",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
post.call_args.kwargs["data"],
|
||||||
|
{
|
||||||
|
"text": "新版接口测试。",
|
||||||
|
"emotion": "happy:0.7,calm:0.3",
|
||||||
|
"emo_alpha": 0.6,
|
||||||
|
"speed": 1.15,
|
||||||
|
"seed": 20260713,
|
||||||
|
"max_mel_tokens": 1500,
|
||||||
|
"max_text_tokens_per_segment": 120,
|
||||||
|
"interval_silence": 200,
|
||||||
|
"temperature": 0.8,
|
||||||
|
"top_p": 0.8,
|
||||||
|
"top_k": 30,
|
||||||
|
"repetition_penalty": 10.0,
|
||||||
|
"diffusion_steps": 25,
|
||||||
|
"cfg_rate": 0.7,
|
||||||
|
"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:7860/outputs/audio/speech.wav",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_maps_legacy_vector_settings_to_pack_emotion_and_endpoint(self):
|
||||||
|
voice.config.indextts2.clear()
|
||||||
|
voice.config.indextts2.update(
|
||||||
|
{
|
||||||
|
"api_url": "http://127.0.0.1:7860/tts",
|
||||||
|
"emotion_mode": "vector",
|
||||||
|
"emotion_alpha": 0.65,
|
||||||
|
"vec_happy": 0.3,
|
||||||
|
"vec_calm": 0.7,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
voice.config.proxy.clear()
|
||||||
|
|
||||||
|
generation_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.get_audio_duration_from_file", return_value=1.0),
|
||||||
|
):
|
||||||
|
result = voice.indextts2_tts(
|
||||||
|
text="旧配置兼容测试。",
|
||||||
|
voice_name=f"indextts2:{reference_audio}",
|
||||||
|
voice_file=str(output_file),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsNotNone(result)
|
||||||
|
self.assertEqual(
|
||||||
|
post.call_args.args[0],
|
||||||
|
"http://127.0.0.1:7860/v1/audio/speech/upload",
|
||||||
|
)
|
||||||
|
self.assertEqual(post.call_args.kwargs["data"]["emotion"], "happy:0.3,calm:0.7")
|
||||||
|
self.assertEqual(post.call_args.kwargs["data"]["emo_alpha"], 0.65)
|
||||||
|
self.assertNotIn("emotion_mode", post.call_args.kwargs["data"])
|
||||||
|
self.assertNotIn("vec_happy", post.call_args.kwargs["data"])
|
||||||
|
|
||||||
|
def test_normalizes_saved_values_to_pack_request_ranges(self):
|
||||||
|
voice.config.indextts2.clear()
|
||||||
|
voice.config.indextts2.update(
|
||||||
|
{
|
||||||
|
"api_url": "http://127.0.0.1:7860",
|
||||||
|
"emo_alpha": 2.0,
|
||||||
|
"speed": 0.1,
|
||||||
|
"max_mel_tokens": 50,
|
||||||
|
"max_text_tokens_per_segment": 1,
|
||||||
|
"interval_silence": -10,
|
||||||
|
"temperature": 0.0,
|
||||||
|
"top_p": 0.0,
|
||||||
|
"top_k": 0,
|
||||||
|
"repetition_penalty": 0.1,
|
||||||
|
"diffusion_steps": 0,
|
||||||
|
"cfg_rate": -1.0,
|
||||||
|
"segment_overlap_ms": -5,
|
||||||
|
"seed": "not-an-integer",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
voice.config.proxy.clear()
|
||||||
|
|
||||||
|
generation_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.get_audio_duration_from_file", return_value=1.0),
|
||||||
|
):
|
||||||
|
result = voice.indextts2_tts(
|
||||||
|
text="参数范围测试。",
|
||||||
|
voice_name=f"indextts2:{reference_audio}",
|
||||||
|
voice_file=str(output_file),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsNotNone(result)
|
||||||
|
self.assertEqual(
|
||||||
|
post.call_args.kwargs["data"],
|
||||||
|
{
|
||||||
|
"text": "参数范围测试。",
|
||||||
|
"emo_alpha": 1.0,
|
||||||
|
"speed": 0.5,
|
||||||
|
"max_mel_tokens": 64,
|
||||||
|
"max_text_tokens_per_segment": 20,
|
||||||
|
"interval_silence": 0,
|
||||||
|
"temperature": 0.05,
|
||||||
|
"top_p": 0.05,
|
||||||
|
"top_k": 1,
|
||||||
|
"repetition_penalty": 1.0,
|
||||||
|
"diffusion_steps": 1,
|
||||||
|
"cfg_rate": 0.0,
|
||||||
|
"segment_overlap_ms": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@ -2395,10 +2395,23 @@ def indextts_tts(text: str, voice_name: str, voice_file: str, speed: float = 1.0
|
|||||||
|
|
||||||
|
|
||||||
def _normalize_indextts2_api_url(api_url: str) -> str:
|
def _normalize_indextts2_api_url(api_url: str) -> str:
|
||||||
api_url = (api_url or "http://192.168.3.6:7863/tts").strip()
|
"""Return the IndexTTS-2 MLX Pack upload endpoint for a configured URL.
|
||||||
if api_url.endswith("/tts"):
|
|
||||||
|
The Pack accepts a server root, the JSON speech endpoint, or the multipart
|
||||||
|
upload endpoint. Treat an old ``/tts`` value as a server root so existing
|
||||||
|
saved settings move to the new Pack route instead of continuing to 404.
|
||||||
|
"""
|
||||||
|
api_url = (api_url or "http://127.0.0.1:7860").strip().rstrip("/")
|
||||||
|
upload_path = "/v1/audio/speech/upload"
|
||||||
|
speech_path = "/v1/audio/speech"
|
||||||
|
|
||||||
|
if api_url.endswith(upload_path):
|
||||||
return api_url
|
return api_url
|
||||||
return f"{api_url.rstrip('/')}/tts"
|
if api_url.endswith(speech_path):
|
||||||
|
return f"{api_url}/upload"
|
||||||
|
if api_url.endswith("/tts"):
|
||||||
|
api_url = api_url[: -len("/tts")]
|
||||||
|
return f"{api_url}{upload_path}"
|
||||||
|
|
||||||
|
|
||||||
def _get_configured_proxies() -> dict:
|
def _get_configured_proxies() -> dict:
|
||||||
@ -2410,6 +2423,49 @@ def _get_configured_proxies() -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_indextts2_number(
|
||||||
|
key: str,
|
||||||
|
default: float | int,
|
||||||
|
minimum: float | int,
|
||||||
|
maximum: float | int,
|
||||||
|
*,
|
||||||
|
integer: bool = False,
|
||||||
|
) -> float | int:
|
||||||
|
"""Read an IndexTTS-2 option and constrain it to the MLX Pack schema."""
|
||||||
|
try:
|
||||||
|
raw_value = config.indextts2.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_indextts2_seed() -> int | None:
|
||||||
|
"""Return the optional Pack seed, omitting invalid legacy text values."""
|
||||||
|
seed = config.indextts2.get("seed")
|
||||||
|
if seed in (None, ""):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return int(seed)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
logger.warning("IndexTTS-2 随机种子无效,将使用随机采样: {}", seed)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _get_indextts2_emotion() -> str:
|
||||||
|
"""Map current and legacy IndexTTS-2 emotion settings to the MLX Pack API."""
|
||||||
|
emotion = config.get_indextts2_pack_emotion(config.indextts2)
|
||||||
|
if emotion:
|
||||||
|
return emotion
|
||||||
|
|
||||||
|
emotion_mode = config.indextts2.get("emotion_mode", "speaker")
|
||||||
|
if emotion_mode == "audio" and config.indextts2.get("emotion_audio"):
|
||||||
|
logger.warning(
|
||||||
|
"IndexTTS-2 MLX Pack 不支持单独的情感参考音频,将使用音色参考音频的情感。"
|
||||||
|
)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _download_indextts2_audio(response: requests.Response, api_url: str, voice_file: str, proxies: dict) -> bool:
|
def _download_indextts2_audio(response: requests.Response, api_url: str, voice_file: str, proxies: dict) -> bool:
|
||||||
content_type = response.headers.get("content-type", "").lower()
|
content_type = response.headers.get("content-type", "").lower()
|
||||||
if "application/json" not in content_type:
|
if "application/json" not in content_type:
|
||||||
@ -2417,9 +2473,14 @@ def _download_indextts2_audio(response: requests.Response, api_url: str, voice_f
|
|||||||
f.write(response.content)
|
f.write(response.content)
|
||||||
return os.path.getsize(voice_file) > 0
|
return os.path.getsize(voice_file) > 0
|
||||||
|
|
||||||
result = response.json()
|
try:
|
||||||
downloads = result.get("downloads") if isinstance(result, dict) else {}
|
result = response.json()
|
||||||
download_url = downloads.get("wav") if isinstance(downloads, dict) else ""
|
except ValueError:
|
||||||
|
logger.error("IndexTTS-2 API 返回了无效的 JSON 响应")
|
||||||
|
return False
|
||||||
|
|
||||||
|
output = result.get("output") if isinstance(result, dict) else {}
|
||||||
|
download_url = output.get("url") if isinstance(output, dict) else ""
|
||||||
if not download_url:
|
if not download_url:
|
||||||
logger.error(f"IndexTTS-2 API 响应中没有音频下载地址: {result}")
|
logger.error(f"IndexTTS-2 API 响应中没有音频下载地址: {result}")
|
||||||
return False
|
return False
|
||||||
@ -2437,68 +2498,74 @@ def _download_indextts2_audio(response: requests.Response, api_url: str, voice_f
|
|||||||
|
|
||||||
def indextts2_tts(text: str, voice_name: str, voice_file: str) -> Union[SubMaker, None]:
|
def indextts2_tts(text: str, voice_name: str, voice_file: str) -> Union[SubMaker, None]:
|
||||||
"""
|
"""
|
||||||
使用 IndexTTS-2 API 进行零样本语音克隆。
|
使用 IndexTTS-2 MLX Pack API 进行零样本语音克隆。
|
||||||
接口兼容 IndexTTS2-Pack 的 POST /tts multipart form。
|
|
||||||
|
参考音频通过 ``POST /v1/audio/speech/upload`` 上传,这样 Pack 即使
|
||||||
|
运行在另一台机器上,也不需要访问 NarratoAI 的本地文件路径。
|
||||||
"""
|
"""
|
||||||
api_url = _normalize_indextts2_api_url(config.indextts2.get("api_url", "http://192.168.3.6:7863/tts"))
|
api_url = _normalize_indextts2_api_url(config.indextts2.get("api_url", "http://127.0.0.1:7860"))
|
||||||
reference_audio_path = parse_indextts2_voice(voice_name)
|
reference_audio_path = parse_indextts2_voice(voice_name)
|
||||||
|
|
||||||
if not reference_audio_path or not os.path.exists(reference_audio_path):
|
if not reference_audio_path or not os.path.exists(reference_audio_path):
|
||||||
logger.error(f"IndexTTS-2 参考音频文件不存在: {reference_audio_path}")
|
logger.error(f"IndexTTS-2 参考音频文件不存在: {reference_audio_path}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
emotion_mode = config.indextts2.get("emotion_mode", "speaker")
|
|
||||||
emotion_audio_path = config.indextts2.get("emotion_audio", "")
|
|
||||||
data = {
|
data = {
|
||||||
"text": text.strip(),
|
"text": text.strip(),
|
||||||
"emotion_mode": emotion_mode,
|
"emo_alpha": _get_indextts2_number(
|
||||||
"emotion_alpha": config.indextts2.get("emotion_alpha", 0.65),
|
"emo_alpha", config.indextts2.get("emotion_alpha", 0.6), 0.0, 1.0
|
||||||
"emotion_text": config.indextts2.get("emotion_text", ""),
|
),
|
||||||
"use_random": str(bool(config.indextts2.get("use_random", False))).lower(),
|
"speed": _get_indextts2_number("speed", 1.0, 0.5, 2.0),
|
||||||
"max_text_tokens_per_segment": config.indextts2.get("max_text_tokens_per_segment", 120),
|
"max_mel_tokens": _get_indextts2_number(
|
||||||
"vec_happy": config.indextts2.get("vec_happy", 0.0),
|
"max_mel_tokens", 1500, 64, 1815, integer=True
|
||||||
"vec_angry": config.indextts2.get("vec_angry", 0.0),
|
),
|
||||||
"vec_sad": config.indextts2.get("vec_sad", 0.0),
|
"max_text_tokens_per_segment": _get_indextts2_number(
|
||||||
"vec_afraid": config.indextts2.get("vec_afraid", 0.0),
|
"max_text_tokens_per_segment", 120, 20, 600, integer=True
|
||||||
"vec_disgusted": config.indextts2.get("vec_disgusted", 0.0),
|
),
|
||||||
"vec_melancholic": config.indextts2.get("vec_melancholic", 0.0),
|
"interval_silence": _get_indextts2_number(
|
||||||
"vec_surprised": config.indextts2.get("vec_surprised", 0.0),
|
"interval_silence", 200, 0, 5000, integer=True
|
||||||
"vec_calm": config.indextts2.get("vec_calm", 0.8),
|
),
|
||||||
"temperature": config.indextts2.get("temperature", 0.8),
|
"temperature": _get_indextts2_number("temperature", 0.8, 0.05, 2.0),
|
||||||
"top_p": config.indextts2.get("top_p", 0.8),
|
"top_p": _get_indextts2_number("top_p", 0.8, 0.05, 1.0),
|
||||||
"top_k": config.indextts2.get("top_k", 30),
|
"top_k": _get_indextts2_number("top_k", 30, 1, 200, integer=True),
|
||||||
"num_beams": config.indextts2.get("num_beams", 3),
|
"repetition_penalty": _get_indextts2_number(
|
||||||
"repetition_penalty": config.indextts2.get("repetition_penalty", 10.0),
|
"repetition_penalty", 10.0, 1.0, 30.0
|
||||||
"max_mel_tokens": config.indextts2.get("max_mel_tokens", 1500),
|
),
|
||||||
|
"diffusion_steps": _get_indextts2_number(
|
||||||
|
"diffusion_steps", 25, 1, 100, integer=True
|
||||||
|
),
|
||||||
|
"cfg_rate": _get_indextts2_number("cfg_rate", 0.7, 0.0, 2.0),
|
||||||
|
"segment_overlap_ms": _get_indextts2_number(
|
||||||
|
"segment_overlap_ms", 50, 0, 1000, integer=True
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
emotion = _get_indextts2_emotion()
|
||||||
|
if emotion:
|
||||||
|
data["emotion"] = emotion
|
||||||
|
seed = _get_indextts2_seed()
|
||||||
|
if seed is not None:
|
||||||
|
data["seed"] = seed
|
||||||
|
|
||||||
proxies = _get_configured_proxies()
|
proxies = _get_configured_proxies()
|
||||||
for attempt in range(3):
|
for attempt in range(3):
|
||||||
files = {}
|
|
||||||
try:
|
try:
|
||||||
files["speaker_audio"] = open(reference_audio_path, "rb")
|
with open(reference_audio_path, "rb") as reference_audio:
|
||||||
if emotion_mode == "audio":
|
logger.info(f"第 {attempt + 1} 次调用 IndexTTS-2 API: {api_url}")
|
||||||
if not emotion_audio_path or not os.path.exists(emotion_audio_path):
|
response = requests.post(
|
||||||
logger.error(f"IndexTTS-2 情感参考音频文件不存在: {emotion_audio_path}")
|
api_url,
|
||||||
return None
|
files={"reference_audio": reference_audio},
|
||||||
files["emotion_audio"] = open(emotion_audio_path, "rb")
|
data=data,
|
||||||
|
proxies=proxies,
|
||||||
|
timeout=180,
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"第 {attempt + 1} 次调用 IndexTTS-2 API: {api_url}")
|
if response.status_code == 200 and _download_indextts2_audio(response, api_url, voice_file, proxies):
|
||||||
response = requests.post(
|
logger.info(f"IndexTTS-2 成功生成音频: {voice_file}, 大小: {os.path.getsize(voice_file)} 字节")
|
||||||
api_url,
|
sub_maker = new_sub_maker()
|
||||||
files=files,
|
duration = get_audio_duration_from_file(voice_file)
|
||||||
data=data,
|
duration_ms = int(duration * 1000) if duration > 0 else max(1000, int(len(text) * 200))
|
||||||
proxies=proxies,
|
add_subtitle_event(sub_maker, 0, duration_ms * 10000, text)
|
||||||
timeout=180,
|
return sub_maker
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code == 200 and _download_indextts2_audio(response, api_url, voice_file, proxies):
|
|
||||||
logger.info(f"IndexTTS-2 成功生成音频: {voice_file}, 大小: {os.path.getsize(voice_file)} 字节")
|
|
||||||
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-2 API 调用失败: {response.status_code} - {response.text}")
|
logger.error(f"IndexTTS-2 API 调用失败: {response.status_code} - {response.text}")
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
@ -2507,12 +2574,6 @@ def indextts2_tts(text: str, voice_name: str, voice_file: str) -> Union[SubMaker
|
|||||||
logger.error(f"IndexTTS-2 API 网络错误: {str(e)} (尝试 {attempt + 1}/3)")
|
logger.error(f"IndexTTS-2 API 网络错误: {str(e)} (尝试 {attempt + 1}/3)")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"IndexTTS-2 TTS 处理错误: {str(e)} (尝试 {attempt + 1}/3)")
|
logger.error(f"IndexTTS-2 TTS 处理错误: {str(e)} (尝试 {attempt + 1}/3)")
|
||||||
finally:
|
|
||||||
for file_obj in files.values():
|
|
||||||
try:
|
|
||||||
file_obj.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if attempt < 2:
|
if attempt < 2:
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|||||||
@ -161,39 +161,32 @@
|
|||||||
repetition_penalty = 10.0
|
repetition_penalty = 10.0
|
||||||
|
|
||||||
[indextts2]
|
[indextts2]
|
||||||
# IndexTTS-2 语音克隆配置
|
# IndexTTS-2 MLX Pack 语音克隆配置
|
||||||
# 支持 IndexTTS2-Pack FastAPI 接口:POST /tts
|
# 参考音频会通过 POST /v1/audio/speech/upload 上传;可填写服务根地址或完整接口地址
|
||||||
api_url = "http://192.168.3.6:7863/tts"
|
api_url = "http://127.0.0.1:7860"
|
||||||
|
|
||||||
# 默认参考音频(可选),音色列表复用 IndexTTS-1.5 的资源目录
|
# 默认参考音频(可选),音色列表复用 IndexTTS-1.5 的资源目录
|
||||||
reference_audio_source = "resource"
|
reference_audio_source = "resource"
|
||||||
# reference_audio = "/path/to/reference_audio.wav"
|
# reference_audio = "/path/to/reference_audio.wav"
|
||||||
|
|
||||||
# 情感控制:speaker / audio / vector / text
|
# 留空时保留参考音频情绪;支持单个情绪或混合权重,例如 happy:0.7,calm:0.3
|
||||||
emotion_mode = "speaker"
|
emotion = ""
|
||||||
emotion_audio = ""
|
emo_alpha = 0.6
|
||||||
emotion_alpha = 0.65
|
speed = 1.0
|
||||||
emotion_text = ""
|
# 可选:设置固定随机种子以复现生成结果
|
||||||
use_random = false
|
# seed = 20260713
|
||||||
max_text_tokens_per_segment = 120
|
|
||||||
|
|
||||||
# 8 维情感向量,顺序:happy, angry, sad, afraid, disgusted, melancholic, surprised, calm
|
|
||||||
vec_happy = 0.0
|
|
||||||
vec_angry = 0.0
|
|
||||||
vec_sad = 0.0
|
|
||||||
vec_afraid = 0.0
|
|
||||||
vec_disgusted = 0.0
|
|
||||||
vec_melancholic = 0.0
|
|
||||||
vec_surprised = 0.0
|
|
||||||
vec_calm = 0.8
|
|
||||||
|
|
||||||
# 高级生成参数
|
# 高级生成参数
|
||||||
|
max_text_tokens_per_segment = 120
|
||||||
|
interval_silence = 200
|
||||||
temperature = 0.8
|
temperature = 0.8
|
||||||
top_p = 0.8
|
top_p = 0.8
|
||||||
top_k = 30
|
top_k = 30
|
||||||
num_beams = 3
|
|
||||||
repetition_penalty = 10.0
|
repetition_penalty = 10.0
|
||||||
max_mel_tokens = 1500
|
max_mel_tokens = 1500
|
||||||
|
diffusion_steps = 25
|
||||||
|
cfg_rate = 0.7
|
||||||
|
segment_overlap_ms = 50
|
||||||
|
|
||||||
[omnivoice]
|
[omnivoice]
|
||||||
# OmniVoice-Pack 语音合成配置
|
# OmniVoice-Pack 语音合成配置
|
||||||
|
|||||||
@ -1123,10 +1123,18 @@ def render_indextts_tts_settings(tr):
|
|||||||
|
|
||||||
|
|
||||||
def render_indextts2_tts_settings(tr):
|
def render_indextts2_tts_settings(tr):
|
||||||
"""渲染 IndexTTS-2 TTS 设置"""
|
"""渲染 IndexTTS-2 MLX Pack TTS 设置"""
|
||||||
|
|
||||||
|
def bounded_value(key, default, min_value, max_value):
|
||||||
|
try:
|
||||||
|
value = float(config.indextts2.get(key, default))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
value = default
|
||||||
|
return max(min_value, min(max_value, value))
|
||||||
|
|
||||||
api_url = st.text_input(
|
api_url = st.text_input(
|
||||||
tr("API URL"),
|
tr("API URL"),
|
||||||
value=config.indextts2.get("api_url", "http://192.168.3.6:7863/tts"),
|
value=config.indextts2.get("api_url", "http://127.0.0.1:7860"),
|
||||||
help=tr("IndexTTS2 API URL Help")
|
help=tr("IndexTTS2 API URL Help")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1135,108 +1143,43 @@ def render_indextts2_tts_settings(tr):
|
|||||||
config.indextts2,
|
config.indextts2,
|
||||||
"indextts2",
|
"indextts2",
|
||||||
)
|
)
|
||||||
|
initial_emotion = config.get_indextts2_pack_emotion(config.indextts2)
|
||||||
emotion_mode_options = [
|
legacy_emotion_audio = (
|
||||||
("speaker", tr("Emotion Mode Speaker")),
|
config.indextts2.get("emotion_mode") == "audio"
|
||||||
("audio", tr("Emotion Mode Audio")),
|
and bool(config.indextts2.get("emotion_audio"))
|
||||||
("vector", tr("Emotion Mode Vector")),
|
)
|
||||||
("text", tr("Emotion Mode Text")),
|
|
||||||
]
|
|
||||||
saved_emotion_mode = config.indextts2.get("emotion_mode", "speaker")
|
|
||||||
emotion_mode_values = [item[0] for item in emotion_mode_options]
|
|
||||||
if saved_emotion_mode not in emotion_mode_values:
|
|
||||||
saved_emotion_mode = "speaker"
|
|
||||||
|
|
||||||
with st.expander(tr("IndexTTS2 Emotion Parameters"), expanded=False):
|
with st.expander(tr("IndexTTS2 Emotion Parameters"), expanded=False):
|
||||||
emotion_mode = emotion_mode_options[st.selectbox(
|
emotion = st.text_input(
|
||||||
tr("Emotion Mode"),
|
tr("IndexTTS2 Emotion"),
|
||||||
options=range(len(emotion_mode_options)),
|
value=initial_emotion,
|
||||||
index=emotion_mode_values.index(saved_emotion_mode),
|
help=tr("IndexTTS2 Emotion Help"),
|
||||||
format_func=lambda x: emotion_mode_options[x][1],
|
placeholder=tr("IndexTTS2 Emotion Placeholder"),
|
||||||
help=tr("Emotion Mode Help"),
|
)
|
||||||
)][0]
|
if legacy_emotion_audio and not emotion.strip():
|
||||||
|
st.warning(tr("IndexTTS2 Emotion Audio Unsupported"))
|
||||||
emotion_alpha = st.slider(
|
emo_alpha = st.slider(
|
||||||
tr("Emotion Alpha"),
|
tr("Emotion Alpha"),
|
||||||
min_value=0.0,
|
min_value=0.0,
|
||||||
max_value=1.0,
|
max_value=1.0,
|
||||||
value=float(config.indextts2.get("emotion_alpha", 0.65)),
|
value=bounded_value("emo_alpha", config.indextts2.get("emotion_alpha", 0.6), 0.0, 1.0),
|
||||||
step=0.05,
|
step=0.05,
|
||||||
help=tr("Emotion Alpha Help"),
|
help=tr("Emotion Alpha Help"),
|
||||||
)
|
)
|
||||||
|
speed = st.slider(
|
||||||
emotion_audio = config.indextts2.get("emotion_audio", "")
|
tr("IndexTTS2 Speed"),
|
||||||
emotion_text = config.indextts2.get("emotion_text", "")
|
min_value=0.5,
|
||||||
if emotion_mode == "audio":
|
max_value=2.0,
|
||||||
emotion_audio_col, emotion_preview_col = st.columns([5, 1])
|
value=bounded_value("speed", 1.0, 0.5, 2.0),
|
||||||
with emotion_audio_col:
|
step=0.05,
|
||||||
emotion_audio = st.text_input(
|
help=tr("IndexTTS2 Speed Help"),
|
||||||
tr("Emotion Reference Audio Path"),
|
)
|
||||||
value=emotion_audio,
|
seed = st.text_input(
|
||||||
help=tr("Emotion Reference Audio Path Help"),
|
tr("IndexTTS2 Seed"),
|
||||||
)
|
value=str(config.indextts2.get("seed", "") or ""),
|
||||||
with emotion_preview_col:
|
help=tr("IndexTTS2 Seed Help"),
|
||||||
render_reference_audio_preview_button(
|
placeholder=tr("IndexTTS2 Seed Placeholder"),
|
||||||
emotion_audio,
|
|
||||||
"indextts2_emotion_audio_preview",
|
|
||||||
tr,
|
|
||||||
preview_state_key="indextts2_emotion_audio_preview_path",
|
|
||||||
)
|
|
||||||
preview_audio_path = st.session_state.get("indextts2_emotion_audio_preview_path", "")
|
|
||||||
if preview_audio_path == emotion_audio and os.path.isfile(preview_audio_path):
|
|
||||||
with open(preview_audio_path, "rb") as audio_file:
|
|
||||||
st.audio(audio_file.read(), format=get_audio_mime_type(preview_audio_path))
|
|
||||||
elif emotion_mode == "text":
|
|
||||||
emotion_text = st.text_input(
|
|
||||||
tr("Emotion Text"),
|
|
||||||
value=emotion_text,
|
|
||||||
help=tr("Emotion Text Help"),
|
|
||||||
placeholder=tr("Emotion Text Placeholder"),
|
|
||||||
)
|
|
||||||
|
|
||||||
use_random = st.checkbox(
|
|
||||||
tr("Use Random Emotion"),
|
|
||||||
value=bool(config.indextts2.get("use_random", False)),
|
|
||||||
help=tr("Use Random Emotion Help"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
emotion_vector_defaults = {
|
|
||||||
"vec_happy": 0.0,
|
|
||||||
"vec_angry": 0.0,
|
|
||||||
"vec_sad": 0.0,
|
|
||||||
"vec_afraid": 0.0,
|
|
||||||
"vec_disgusted": 0.0,
|
|
||||||
"vec_melancholic": 0.0,
|
|
||||||
"vec_surprised": 0.0,
|
|
||||||
"vec_calm": 0.8,
|
|
||||||
}
|
|
||||||
emotion_vector_labels = {
|
|
||||||
"vec_happy": tr("Emotion Happy"),
|
|
||||||
"vec_angry": tr("Emotion Angry"),
|
|
||||||
"vec_sad": tr("Emotion Sad"),
|
|
||||||
"vec_afraid": tr("Emotion Afraid"),
|
|
||||||
"vec_disgusted": tr("Emotion Disgusted"),
|
|
||||||
"vec_melancholic": tr("Emotion Melancholic"),
|
|
||||||
"vec_surprised": tr("Emotion Surprised"),
|
|
||||||
"vec_calm": tr("Emotion Calm"),
|
|
||||||
}
|
|
||||||
emotion_vector_values = {}
|
|
||||||
if emotion_mode == "vector":
|
|
||||||
vec_cols = st.columns(2)
|
|
||||||
for index, (field, default_value) in enumerate(emotion_vector_defaults.items()):
|
|
||||||
with vec_cols[index % 2]:
|
|
||||||
emotion_vector_values[field] = st.slider(
|
|
||||||
emotion_vector_labels[field],
|
|
||||||
min_value=0.0,
|
|
||||||
max_value=1.0,
|
|
||||||
value=float(config.indextts2.get(field, default_value)),
|
|
||||||
step=0.05,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
emotion_vector_values = {
|
|
||||||
field: float(config.indextts2.get(field, default_value))
|
|
||||||
for field, default_value in emotion_vector_defaults.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
with st.expander(tr("Advanced Parameters"), expanded=False):
|
with st.expander(tr("Advanced Parameters"), expanded=False):
|
||||||
col1, col2 = st.columns(2)
|
col1, col2 = st.columns(2)
|
||||||
@ -1244,65 +1187,92 @@ def render_indextts2_tts_settings(tr):
|
|||||||
with col1:
|
with col1:
|
||||||
temperature = st.slider(
|
temperature = st.slider(
|
||||||
tr("Sampling Temperature"),
|
tr("Sampling Temperature"),
|
||||||
min_value=0.1,
|
min_value=0.05,
|
||||||
max_value=2.0,
|
max_value=2.0,
|
||||||
value=float(config.indextts2.get("temperature", 0.8)),
|
value=bounded_value("temperature", 0.8, 0.05, 2.0),
|
||||||
step=0.1,
|
step=0.05,
|
||||||
help=tr("Sampling Temperature Help")
|
help=tr("Sampling Temperature Help")
|
||||||
)
|
)
|
||||||
|
|
||||||
top_p = st.slider(
|
top_p = st.slider(
|
||||||
"Top P",
|
"Top P",
|
||||||
min_value=0.0,
|
min_value=0.05,
|
||||||
max_value=1.0,
|
max_value=1.0,
|
||||||
value=float(config.indextts2.get("top_p", 0.8)),
|
value=bounded_value("top_p", 0.8, 0.05, 1.0),
|
||||||
step=0.05,
|
step=0.05,
|
||||||
help=tr("Top P Help")
|
help=tr("Top P Help")
|
||||||
)
|
)
|
||||||
|
|
||||||
top_k = st.slider(
|
top_k = st.slider(
|
||||||
"Top K",
|
"Top K",
|
||||||
min_value=0,
|
min_value=1,
|
||||||
max_value=100,
|
max_value=200,
|
||||||
value=int(config.indextts2.get("top_k", 30)),
|
value=int(bounded_value("top_k", 30, 1, 200)),
|
||||||
step=5,
|
step=1,
|
||||||
help=tr("Top K Help")
|
help=tr("IndexTTS2 Top K Help")
|
||||||
)
|
)
|
||||||
|
|
||||||
max_text_tokens_per_segment = st.slider(
|
max_text_tokens_per_segment = st.slider(
|
||||||
tr("Max Text Tokens Per Segment"),
|
tr("Max Text Tokens Per Segment"),
|
||||||
min_value=20,
|
min_value=20,
|
||||||
max_value=600,
|
max_value=600,
|
||||||
value=int(config.indextts2.get("max_text_tokens_per_segment", 120)),
|
value=int(bounded_value("max_text_tokens_per_segment", 120, 20, 600)),
|
||||||
step=10,
|
step=10,
|
||||||
help=tr("Max Text Tokens Per Segment Help")
|
help=tr("Max Text Tokens Per Segment Help")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
interval_silence = st.slider(
|
||||||
|
tr("Interval Silence"),
|
||||||
|
min_value=0,
|
||||||
|
max_value=5000,
|
||||||
|
value=int(bounded_value("interval_silence", 200, 0, 5000)),
|
||||||
|
step=50,
|
||||||
|
help=tr("Interval Silence Help"),
|
||||||
|
)
|
||||||
|
|
||||||
|
segment_overlap_ms = st.slider(
|
||||||
|
tr("Segment Overlap"),
|
||||||
|
min_value=0,
|
||||||
|
max_value=1000,
|
||||||
|
value=int(bounded_value("segment_overlap_ms", 50, 0, 1000)),
|
||||||
|
step=10,
|
||||||
|
help=tr("Segment Overlap Help"),
|
||||||
|
)
|
||||||
|
|
||||||
with col2:
|
with col2:
|
||||||
num_beams = st.slider(
|
diffusion_steps = st.slider(
|
||||||
tr("Num Beams"),
|
tr("Diffusion Steps"),
|
||||||
min_value=1,
|
min_value=1,
|
||||||
max_value=10,
|
max_value=100,
|
||||||
value=int(config.indextts2.get("num_beams", 3)),
|
value=int(bounded_value("diffusion_steps", 25, 1, 100)),
|
||||||
step=1,
|
step=1,
|
||||||
help=tr("Num Beams Help")
|
help=tr("Diffusion Steps Help")
|
||||||
|
)
|
||||||
|
|
||||||
|
cfg_rate = st.slider(
|
||||||
|
tr("CFG Rate"),
|
||||||
|
min_value=0.0,
|
||||||
|
max_value=2.0,
|
||||||
|
value=bounded_value("cfg_rate", 0.7, 0.0, 2.0),
|
||||||
|
step=0.05,
|
||||||
|
help=tr("CFG Rate Help"),
|
||||||
)
|
)
|
||||||
|
|
||||||
repetition_penalty = st.slider(
|
repetition_penalty = st.slider(
|
||||||
tr("Repetition Penalty"),
|
tr("Repetition Penalty"),
|
||||||
min_value=0.1,
|
min_value=1.0,
|
||||||
max_value=20.0,
|
max_value=30.0,
|
||||||
value=float(config.indextts2.get("repetition_penalty", 10.0)),
|
value=bounded_value("repetition_penalty", 10.0, 1.0, 30.0),
|
||||||
step=0.1,
|
step=0.1,
|
||||||
help=tr("Repetition Penalty Help")
|
help=tr("Repetition Penalty Help")
|
||||||
)
|
)
|
||||||
|
|
||||||
max_mel_tokens = st.slider(
|
max_mel_tokens = st.slider(
|
||||||
tr("Max Mel Tokens"),
|
tr("Max Mel Tokens"),
|
||||||
min_value=50,
|
min_value=64,
|
||||||
max_value=1815,
|
max_value=1815,
|
||||||
value=int(config.indextts2.get("max_mel_tokens", 1500)),
|
value=int(bounded_value("max_mel_tokens", 1500, 64, 1815)),
|
||||||
step=10,
|
step=1,
|
||||||
help=tr("Max Mel Tokens Help")
|
help=tr("Max Mel Tokens Help")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1312,20 +1282,32 @@ def render_indextts2_tts_settings(tr):
|
|||||||
config.indextts2["api_url"] = api_url
|
config.indextts2["api_url"] = api_url
|
||||||
config.indextts2["reference_audio_source"] = reference_audio_source
|
config.indextts2["reference_audio_source"] = reference_audio_source
|
||||||
config.indextts2["reference_audio"] = reference_audio
|
config.indextts2["reference_audio"] = reference_audio
|
||||||
config.indextts2["emotion_mode"] = emotion_mode
|
config.indextts2["emotion"] = emotion
|
||||||
config.indextts2["emotion_audio"] = emotion_audio
|
config.indextts2["emo_alpha"] = emo_alpha
|
||||||
config.indextts2["emotion_alpha"] = emotion_alpha
|
config.indextts2["speed"] = speed
|
||||||
config.indextts2["emotion_text"] = emotion_text
|
config.indextts2["seed"] = seed.strip()
|
||||||
config.indextts2["use_random"] = use_random
|
|
||||||
config.indextts2["max_text_tokens_per_segment"] = max_text_tokens_per_segment
|
config.indextts2["max_text_tokens_per_segment"] = max_text_tokens_per_segment
|
||||||
for field, value in emotion_vector_values.items():
|
config.indextts2["interval_silence"] = interval_silence
|
||||||
config.indextts2[field] = value
|
config.indextts2["segment_overlap_ms"] = segment_overlap_ms
|
||||||
config.indextts2["temperature"] = temperature
|
config.indextts2["temperature"] = temperature
|
||||||
config.indextts2["top_p"] = top_p
|
config.indextts2["top_p"] = top_p
|
||||||
config.indextts2["top_k"] = top_k
|
config.indextts2["top_k"] = top_k
|
||||||
config.indextts2["num_beams"] = num_beams
|
|
||||||
config.indextts2["repetition_penalty"] = repetition_penalty
|
config.indextts2["repetition_penalty"] = repetition_penalty
|
||||||
config.indextts2["max_mel_tokens"] = max_mel_tokens
|
config.indextts2["max_mel_tokens"] = max_mel_tokens
|
||||||
|
config.indextts2["diffusion_steps"] = diffusion_steps
|
||||||
|
config.indextts2["cfg_rate"] = cfg_rate
|
||||||
|
|
||||||
|
legacy_fields = (
|
||||||
|
"emotion_mode", "emotion_audio", "emotion_alpha", "emotion_text", "use_random",
|
||||||
|
"num_beams", "vec_happy", "vec_angry", "vec_sad", "vec_afraid",
|
||||||
|
"vec_disgusted", "vec_melancholic", "vec_surprised", "vec_calm",
|
||||||
|
)
|
||||||
|
if legacy_emotion_audio and not emotion.strip():
|
||||||
|
legacy_fields = tuple(
|
||||||
|
field for field in legacy_fields if field not in {"emotion_mode", "emotion_audio"}
|
||||||
|
)
|
||||||
|
for field in legacy_fields:
|
||||||
|
config.indextts2.pop(field, None)
|
||||||
|
|
||||||
if reference_audio:
|
if reference_audio:
|
||||||
config.ui["voice_name"] = f"{config.INDEXTTS2_VOICE_PREFIX}{reference_audio}"
|
config.ui["voice_name"] = f"{config.INDEXTTS2_VOICE_PREFIX}{reference_audio}"
|
||||||
|
|||||||
@ -376,8 +376,8 @@
|
|||||||
"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 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 package: https://pan.quark.cn/s/0767c9bcefd5",
|
"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 package: https://pan.quark.cn/s/0767c9bcefd5",
|
||||||
"IndexTTS download link": "Download link: https://pan.quark.cn/s/0767c9bcefd5",
|
"IndexTTS download link": "Download link: https://pan.quark.cn/s/0767c9bcefd5",
|
||||||
"IndexTTS2 features": "A locally or privately deployed IndexTTS-2 voice-cloning engine with emotion control and fuller generation parameters.",
|
"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 API service before use.",
|
"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.",
|
||||||
"OmniVoice features": "A locally or privately deployed OmniVoice-Pack multilingual TTS engine with automatic voice generation, voice design, and reference-audio cloning.",
|
"OmniVoice features": "A locally or privately deployed OmniVoice-Pack multilingual TTS engine with automatic voice generation, voice design, and reference-audio cloning.",
|
||||||
"OmniVoice use case": "Best for local controllable multilingual narration, voice design, or reference-audio cloning. Start the OmniVoice-Pack API service before use.",
|
"OmniVoice use case": "Best for local controllable multilingual narration, voice design, or reference-audio cloning. Start the OmniVoice-Pack API service before use.",
|
||||||
"Doubao TTS features": "Volcengine Doubao speech synthesis with multiple voices and emotions, plus fast access in mainland China.",
|
"Doubao TTS features": "Volcengine Doubao speech synthesis with multiple voices and emotions, plus fast access in mainland China.",
|
||||||
@ -564,7 +564,7 @@
|
|||||||
"Select Qwen3 TTS Voice": "Select a Qwen3 TTS voice",
|
"Select Qwen3 TTS Voice": "Select a Qwen3 TTS voice",
|
||||||
"API URL": "API URL",
|
"API URL": "API URL",
|
||||||
"IndexTTS API URL Help": "IndexTTS-1.5 API service URL",
|
"IndexTTS API URL Help": "IndexTTS-1.5 API service URL",
|
||||||
"IndexTTS2 API URL Help": "IndexTTS-2 API service URL. You can enter the service root or the full /tts 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 API URL Help": "OmniVoice-Pack API service URL. You can enter the service root or the full /tts endpoint.",
|
||||||
"OmniVoice Language Code": "Synthesis Language",
|
"OmniVoice Language Code": "Synthesis Language",
|
||||||
"OmniVoice Language Code Help": "The language parameter sent to OmniVoice-Pack, such as zh or en.",
|
"OmniVoice Language Code Help": "The language parameter sent to OmniVoice-Pack, such as zh or en.",
|
||||||
@ -619,6 +619,15 @@
|
|||||||
"IndexTTS Usage Instructions Title": "IndexTTS-1.5 Usage Instructions",
|
"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 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",
|
||||||
"IndexTTS2 Emotion Parameters": "Emotion Parameters",
|
"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.",
|
||||||
|
"IndexTTS2 Emotion Placeholder": "e.g. happy:0.7,calm:0.3",
|
||||||
|
"IndexTTS2 Emotion Audio Unsupported": "The IndexTTS-2 MLX Pack does not support a separate emotion reference audio. The speaker reference audio emotion will be used.",
|
||||||
|
"IndexTTS2 Speed": "Speech Speed",
|
||||||
|
"IndexTTS2 Speed Help": "Speech speed sent to the IndexTTS-2 MLX Pack (0.5x–2.0x).",
|
||||||
|
"IndexTTS2 Seed": "Seed",
|
||||||
|
"IndexTTS2 Seed Help": "Optional integer seed for reproducible generation. Leave blank for random sampling.",
|
||||||
|
"IndexTTS2 Seed Placeholder": "Random",
|
||||||
"Emotion Mode": "Emotion Mode",
|
"Emotion Mode": "Emotion Mode",
|
||||||
"Emotion Mode Help": "Choose the emotion control source for IndexTTS-2.",
|
"Emotion Mode Help": "Choose the emotion control source for IndexTTS-2.",
|
||||||
"Emotion Mode Speaker": "Same as speaker reference",
|
"Emotion Mode Speaker": "Same as speaker reference",
|
||||||
@ -642,12 +651,21 @@
|
|||||||
"Emotion Melancholic": "Melancholic",
|
"Emotion Melancholic": "Melancholic",
|
||||||
"Emotion Surprised": "Surprised",
|
"Emotion Surprised": "Surprised",
|
||||||
"Emotion Calm": "Calm",
|
"Emotion Calm": "Calm",
|
||||||
|
"Diffusion Steps": "Diffusion Steps",
|
||||||
|
"Diffusion Steps Help": "Number of diffusion steps. Higher values can improve quality but take longer.",
|
||||||
|
"CFG Rate": "CFG Rate",
|
||||||
|
"CFG Rate Help": "Classifier-free guidance strength for the diffusion stage.",
|
||||||
|
"Interval Silence": "Inter-segment Silence (ms)",
|
||||||
|
"Interval Silence Help": "Silence inserted between text segments by the Pack.",
|
||||||
|
"Segment Overlap": "Segment Overlap (ms)",
|
||||||
|
"Segment Overlap Help": "Crossfade overlap between generated text segments.",
|
||||||
|
"IndexTTS2 Top K Help": "Top-k sampling value for IndexTTS-2 MLX Pack. It must be at least 1.",
|
||||||
"Max Text Tokens Per Segment": "Max Text Tokens Per Segment",
|
"Max Text Tokens Per Segment": "Max Text Tokens Per Segment",
|
||||||
"Max Text Tokens Per Segment Help": "Maximum text tokens per segment for IndexTTS-2 inference.",
|
"Max Text Tokens Per Segment Help": "Maximum text tokens per segment for IndexTTS-2 inference.",
|
||||||
"Max Mel Tokens": "Max Mel Tokens",
|
"Max Mel Tokens": "Max Mel Tokens",
|
||||||
"Max Mel Tokens Help": "Controls the maximum mel tokens generated in one request. Higher values can produce longer audio.",
|
"Max Mel Tokens Help": "Controls the maximum mel tokens generated in one request. Higher values can produce longer audio.",
|
||||||
"IndexTTS2 Usage Instructions Title": "IndexTTS-2 Usage Instructions",
|
"IndexTTS2 Usage Instructions Title": "IndexTTS-2 Usage Instructions",
|
||||||
"IndexTTS2 Usage Instructions": "**IndexTTS-2 voice cloning**\n\n1. **Choose a voice**: reuse IndexTTS-1.5 resource audio or upload a reference audio file\n2. **Set API URL**: for example http://192.168.3.6:7863/tts, or enter the service root\n3. **Tune emotion**: speaker is the default; switch to audio, vector, or text when needed\n4. **Tune generation**: temperature, top_p, top_k, num_beams, repetition_penalty, and max_mel_tokens are sent directly to the IndexTTS-2 API\n\n**Notes**:\n- Reference audio quality directly affects cloning quality\n- The first request may load the model and take longer\n- CPU deployments are much slower than GPU deployments",
|
"IndexTTS2 Usage Instructions": "**IndexTTS-2 MLX Pack voice cloning**\n\n1. **Choose a voice**: reuse IndexTTS-1.5 resource audio or upload a reference audio file\n2. **Set API URL**: the default Pack service is http://127.0.0.1:7860; NarratoAI calls /v1/audio/speech/upload and uploads the reference audio\n3. **Tune emotion**: leave it blank to preserve the reference audio emotion, or enter a label/mix such as happy:0.7,calm:0.3\n4. **Tune generation**: speed, temperature, top_p, top_k, repetition_penalty, diffusion_steps, cfg_rate, and max_mel_tokens are sent to the Pack\n\n**Notes**:\n- Reference audio quality directly affects cloning quality\n- The first request may load the model and take longer\n- The Pack binds to localhost by default",
|
||||||
"OmniVoice Usage Instructions Title": "OmniVoice Usage Instructions",
|
"OmniVoice Usage Instructions Title": "OmniVoice Usage Instructions",
|
||||||
"OmniVoice Usage Instructions": "**OmniVoice-Pack speech synthesis**\n\n1. **Automatic voice**: set the API URL and language, then synthesize directly.\n2. **Voice design**: fill instruct with the desired gender, pitch, accent, or style.\n3. **Reference-audio clone**: upload or choose reference audio and fill its matching transcript.\n\n**Notes**:\n- The default service URL is http://127.0.0.1:7866/tts\n- Reference-audio cloning requires reference text when the service has no ASR model loaded\n- OmniVoice returns WAV audio, and NarratoAI estimates subtitle segment timing from the audio duration",
|
"OmniVoice Usage Instructions": "**OmniVoice-Pack speech synthesis**\n\n1. **Automatic voice**: set the API URL and language, then synthesize directly.\n2. **Voice design**: fill instruct with the desired gender, pitch, accent, or style.\n3. **Reference-audio clone**: upload or choose reference audio and fill its matching transcript.\n\n**Notes**:\n- The default service URL is http://127.0.0.1:7866/tts\n- Reference-audio cloning requires reference text when the service has no ASR model loaded\n- OmniVoice returns WAV audio, and NarratoAI estimates subtitle segment timing from the audio duration",
|
||||||
"Volcengine Access Key Help": "Volcengine Access Key",
|
"Volcengine Access Key Help": "Volcengine Access Key",
|
||||||
|
|||||||
@ -314,8 +314,8 @@
|
|||||||
"IndexTTS features": "本地/私有部署的 IndexTTS-1.5 语音克隆引擎。选择资源目录音频或上传参考音频后,可按该音色合成旁白。",
|
"IndexTTS features": "本地/私有部署的 IndexTTS-1.5 语音克隆引擎。选择资源目录音频或上传参考音频后,可按该音色合成旁白。",
|
||||||
"IndexTTS use case": "适合需要固定旁白音色、角色配音或批量生成同一音色视频的场景。使用前请先启动 IndexTTS-1.5 API 服务;部署包下载:https://pan.quark.cn/s/0767c9bcefd5",
|
"IndexTTS use case": "适合需要固定旁白音色、角色配音或批量生成同一音色视频的场景。使用前请先启动 IndexTTS-1.5 API 服务;部署包下载:https://pan.quark.cn/s/0767c9bcefd5",
|
||||||
"IndexTTS download link": "下载地址:https://pan.quark.cn/s/0767c9bcefd5",
|
"IndexTTS download link": "下载地址:https://pan.quark.cn/s/0767c9bcefd5",
|
||||||
"IndexTTS2 features": "本地/私有部署的 IndexTTS-2 语音克隆引擎,支持情感控制和更完整的生成参数。",
|
"IndexTTS2 features": "本地部署的 IndexTTS-2 MLX Pack 语音克隆引擎,支持情感控制和更完整的生成参数。",
|
||||||
"IndexTTS2 use case": "适合需要固定音色、情绪化旁白或更细致采样控制的本地语音合成场景。使用前请先启动 IndexTTS-2 API 服务。",
|
"IndexTTS2 use case": "适合需要固定音色、情绪化旁白或更细致采样控制的本地语音合成场景。使用前请先启动 IndexTTS-2 MLX Pack 服务。",
|
||||||
"OmniVoice features": "本地/私有部署的 OmniVoice-Pack 多语种语音合成引擎,支持自动音色、指令音色和参考音频克隆。",
|
"OmniVoice features": "本地/私有部署的 OmniVoice-Pack 多语种语音合成引擎,支持自动音色、指令音色和参考音频克隆。",
|
||||||
"OmniVoice use case": "适合需要本地可控、多语言旁白、音色设计或参考音频克隆的场景。使用前请先启动 OmniVoice-Pack API 服务。",
|
"OmniVoice use case": "适合需要本地可控、多语言旁白、音色设计或参考音频克隆的场景。使用前请先启动 OmniVoice-Pack API 服务。",
|
||||||
"Doubao TTS features": "火山引擎豆包语音合成,支持多种音色和情感,国内访问速度快",
|
"Doubao TTS features": "火山引擎豆包语音合成,支持多种音色和情感,国内访问速度快",
|
||||||
@ -503,7 +503,7 @@
|
|||||||
"Select Qwen3 TTS Voice": "选择 Qwen3 TTS 音色",
|
"Select Qwen3 TTS Voice": "选择 Qwen3 TTS 音色",
|
||||||
"API URL": "API 地址",
|
"API URL": "API 地址",
|
||||||
"IndexTTS API URL Help": "IndexTTS-1.5 API 服务地址",
|
"IndexTTS API URL Help": "IndexTTS-1.5 API 服务地址",
|
||||||
"IndexTTS2 API URL Help": "IndexTTS-2 API 服务地址,可填写服务根地址或完整 /tts 地址",
|
"IndexTTS2 API URL Help": "IndexTTS-2 MLX Pack 服务地址,可填写服务根地址或完整 /v1/audio/speech/upload 地址",
|
||||||
"OmniVoice API URL Help": "OmniVoice-Pack API 服务地址,可填写服务根地址或完整 /tts 地址",
|
"OmniVoice API URL Help": "OmniVoice-Pack API 服务地址,可填写服务根地址或完整 /tts 地址",
|
||||||
"OmniVoice Language Code": "合成语言",
|
"OmniVoice Language Code": "合成语言",
|
||||||
"OmniVoice Language Code Help": "传给 OmniVoice-Pack 的 language 参数,例如 zh、en。",
|
"OmniVoice Language Code Help": "传给 OmniVoice-Pack 的 language 参数,例如 zh、en。",
|
||||||
@ -558,6 +558,15 @@
|
|||||||
"IndexTTS Usage Instructions Title": "IndexTTS-1.5 使用说明",
|
"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 Usage Instructions": "**零样本语音克隆**\n\n1. **准备参考音频**:上传或指定一段清晰的音频文件(建议 3-10 秒)\n2. **设置 API 地址**:确保 IndexTTS-1.5 服务正常运行\n3. **开始合成**:系统会自动使用参考音频的音色合成新语音\n\n**注意事项**:\n- 参考音频质量直接影响合成效果\n- 建议使用无背景噪音的清晰音频\n- 文本长度建议控制在合理范围内\n- 首次合成可能需要较长时间",
|
||||||
"IndexTTS2 Emotion Parameters": "情感参数",
|
"IndexTTS2 Emotion Parameters": "情感参数",
|
||||||
|
"IndexTTS2 Emotion": "情感覆盖",
|
||||||
|
"IndexTTS2 Emotion Help": "留空时保留参考音频的情感;可填写单个情感,如 happy,或权重混合,如 happy:0.7,calm:0.3。",
|
||||||
|
"IndexTTS2 Emotion Placeholder": "例如:happy:0.7,calm:0.3",
|
||||||
|
"IndexTTS2 Emotion Audio Unsupported": "IndexTTS-2 MLX Pack 不支持单独的情感参考音频,将使用音色参考音频的情感。",
|
||||||
|
"IndexTTS2 Speed": "语速",
|
||||||
|
"IndexTTS2 Speed Help": "传给 IndexTTS-2 MLX Pack 的语速(0.5x–2.0x)。",
|
||||||
|
"IndexTTS2 Seed": "随机种子",
|
||||||
|
"IndexTTS2 Seed Help": "可选整数,用于复现生成结果;留空则随机采样。",
|
||||||
|
"IndexTTS2 Seed Placeholder": "随机",
|
||||||
"Emotion Mode": "情感控制方式",
|
"Emotion Mode": "情感控制方式",
|
||||||
"Emotion Mode Help": "选择 IndexTTS-2 的情感控制来源",
|
"Emotion Mode Help": "选择 IndexTTS-2 的情感控制来源",
|
||||||
"Emotion Mode Speaker": "与音色参考相同",
|
"Emotion Mode Speaker": "与音色参考相同",
|
||||||
@ -581,12 +590,21 @@
|
|||||||
"Emotion Melancholic": "忧郁",
|
"Emotion Melancholic": "忧郁",
|
||||||
"Emotion Surprised": "惊讶",
|
"Emotion Surprised": "惊讶",
|
||||||
"Emotion Calm": "平静",
|
"Emotion Calm": "平静",
|
||||||
|
"Diffusion Steps": "扩散步数",
|
||||||
|
"Diffusion Steps Help": "扩散生成的步数;值越大可能提升质量,但生成更慢。",
|
||||||
|
"CFG Rate": "CFG 引导强度",
|
||||||
|
"CFG Rate Help": "扩散阶段的 classifier-free guidance 强度。",
|
||||||
|
"Interval Silence": "分段静音(毫秒)",
|
||||||
|
"Interval Silence Help": "Pack 在文本分段之间插入的静音时长。",
|
||||||
|
"Segment Overlap": "分段重叠(毫秒)",
|
||||||
|
"Segment Overlap Help": "生成的文本片段之间用于交叉淡化的重叠时长。",
|
||||||
|
"IndexTTS2 Top K Help": "IndexTTS-2 MLX Pack 的 top-k 采样值,必须不小于 1。",
|
||||||
"Max Text Tokens Per Segment": "单段最大文本 Token",
|
"Max Text Tokens Per Segment": "单段最大文本 Token",
|
||||||
"Max Text Tokens Per Segment Help": "IndexTTS-2 分段推理的最大文本 token 数",
|
"Max Text Tokens Per Segment Help": "IndexTTS-2 分段推理的最大文本 token 数",
|
||||||
"Max Mel Tokens": "最大 Mel Tokens",
|
"Max Mel Tokens": "最大 Mel Tokens",
|
||||||
"Max Mel Tokens Help": "控制单次生成的最大 mel token 数,值越大可生成更长音频",
|
"Max Mel Tokens Help": "控制单次生成的最大 mel token 数,值越大可生成更长音频",
|
||||||
"IndexTTS2 Usage Instructions Title": "IndexTTS-2 使用说明",
|
"IndexTTS2 Usage Instructions Title": "IndexTTS-2 使用说明",
|
||||||
"IndexTTS2 Usage Instructions": "**IndexTTS-2 语音克隆**\n\n1. **选择音色**:复用 IndexTTS-1.5 的资源音频或上传参考音频\n2. **设置 API 地址**:例如 http://192.168.3.6:7863/tts,也可以填写服务根地址\n3. **调整情感参数**:默认使用 speaker,可按需切换到 audio、vector 或 text\n4. **调整生成参数**:temperature、top_p、top_k、num_beams、repetition_penalty 和 max_mel_tokens 会直接传给 IndexTTS-2 接口\n\n**注意事项**:\n- 参考音频质量会直接影响克隆效果\n- 首次请求可能需要加载模型,耗时更长\n- CPU 部署生成速度会明显慢于 GPU",
|
"IndexTTS2 Usage Instructions": "**IndexTTS-2 MLX Pack 语音克隆**\n\n1. **选择音色**:复用 IndexTTS-1.5 的资源音频或上传参考音频\n2. **设置 API 地址**:默认 Pack 服务为 http://127.0.0.1:7860;NarratoAI 会调用 /v1/audio/speech/upload 并上传参考音频\n3. **调整情感参数**:留空时保留参考音频情感;也可输入 happy:0.7,calm:0.3 这类情感或权重混合\n4. **调整生成参数**:speed、temperature、top_p、top_k、repetition_penalty、diffusion_steps、cfg_rate 和 max_mel_tokens 会传给 Pack\n\n**注意事项**:\n- 参考音频质量会直接影响克隆效果\n- 首次请求可能需要加载模型,耗时更长\n- Pack 默认仅监听本机地址",
|
||||||
"OmniVoice Usage Instructions Title": "OmniVoice 使用说明",
|
"OmniVoice Usage Instructions Title": "OmniVoice 使用说明",
|
||||||
"OmniVoice Usage Instructions": "**OmniVoice-Pack 语音合成**\n\n1. **自动音色**:只需要设置 API 地址和语言,可直接合成。\n2. **指令音色**:填写 instruct 描述想要的性别、音高、口音或风格。\n3. **参考音频克隆**:上传或选择参考音频,并填写该音频对应文本。\n\n**注意事项**:\n- 当前默认服务地址为 http://127.0.0.1:7866/tts\n- 参考音频克隆在服务未加载 ASR 模型时必须填写参考文本\n- OmniVoice 返回 WAV 音频,系统会按音频时长估算字幕段落",
|
"OmniVoice Usage Instructions": "**OmniVoice-Pack 语音合成**\n\n1. **自动音色**:只需要设置 API 地址和语言,可直接合成。\n2. **指令音色**:填写 instruct 描述想要的性别、音高、口音或风格。\n3. **参考音频克隆**:上传或选择参考音频,并填写该音频对应文本。\n\n**注意事项**:\n- 当前默认服务地址为 http://127.0.0.1:7866/tts\n- 参考音频克隆在服务未加载 ASR 模型时必须填写参考文本\n- OmniVoice 返回 WAV 音频,系统会按音频时长估算字幕段落",
|
||||||
"Volcengine Access Key Help": "火山引擎 Access Key",
|
"Volcengine Access Key Help": "火山引擎 Access Key",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user