tktts_irodori.py ダウンロード/コピー

tktts_irodori.py をダウンロード

tktts_irodori.py
tktts_irodori.py
  1"""tktts_* ライブラリファミリーのためのIrodori-TTSバックエンド。
  2
  3このモジュールは ``tktts_voicevox.py`` や ``tktts_qwen3.py`` で使用されるインターフェースに準拠しています。
  4Irodori-TTSの公式 ``InferenceRuntime`` API を介して直接呼び出しを行います。
  5Gradio UI や HTTPサーバーを実行する必要はありません。
  6ロードされたランタイムはIrodori-TTSによってキャッシュされ、以降のすべての発話で再利用されます。
  7
  8テキストの分割、読み上げ/発音変換は意図的に呼び出し元に任されています。
  9``speak_dialogue`` は、他のバックエンドとまったく同様に、tkttsによって渡された置換辞書のみを適用します。
 10
 11.. seealso::
 12   :doc:`tktts_irodori_usage`
 13
 14"""
 15
 16from __future__ import annotations
 17
 18import os
 19import re
 20from pathlib import Path
 21from typing import Any, Iterable
 22
 23
 24missing = []
 25for lib, import_name in [
 26    ("torch", "torch"),
 27    ("Irodori-TTS", "irodori_tts"),
 28]:
 29    try:
 30        __import__(import_name)
 31    except ImportError:
 32        missing.append(lib)
 33
 34if missing:
 35    raise ImportError(
 36        "Error: Missing libraries: "
 37        + ", ".join(missing)
 38        + "\n  Run this module in the Irodori-TTS uv environment."
 39        + "\n  install: uv sync --extra cu128"
 40    )
 41
 42import torch
 43from irodori_tts.inference_runtime import (
 44    RuntimeKey,
 45    SamplingRequest,
 46    clear_cached_runtime,
 47    default_runtime_device,
 48    download_hf_checkpoint,
 49    get_cached_runtime,
 50    save_wav,
 51)
 52
 53try:
 54    from tktts_base import apply_replacements, normalize_speaker, split_dialogue
 55except ImportError as exc:
 56    raise ImportError(
 57        "tktts_irodori requires tktts_base.py to be importable."
 58    ) from exc
 59
 60
 61TTS_ENGINE_NAME = "irodori"
 62DEFAULT_IRODORI_MODEL = "Aratako/Irodori-TTS-v4.1-Small"
 63DEFAULT_IRODORI_VOICE = "default"
 64DEFAULT_CAPTION = "落ち着いた自然な声で、明瞭に読み上げる。"
 65DEFAULT_DEVICE = "auto"
 66DEFAULT_PRECISION = "auto"
 67DEFAULT_NUM_STEPS = 40
 68DEFAULT_CFG_SCALE_TEXT = 3.5
 69DEFAULT_CFG_SCALE_CAPTION = 3.0
 70DEFAULT_CFG_SCALE_SPEAKER = 5.0
 71DEFAULT_SEED = 0
 72
 73
 74_AVAILABLE_VOICES = [
 75    {
 76        "name": DEFAULT_IRODORI_VOICE,
 77        "description": (
 78            "Irodori-TTS voice. Use irodori_caption and/or reference WAV "
 79            "files to define voice identity and speaking style."
 80        ),
 81    }
 82]
 83
 84
 85def _resolve_device(device: str | None = DEFAULT_DEVICE) -> str:
 86    """Irodori-TTSが推奨する利用可能なデバイスに ``auto`` を解決します。
 87
 88    :param device: デバイス名 (例: "cpu", "cuda", "cuda:0", "auto")。デフォルトは "auto"。
 89    :type device: str | None
 90    :returns: 解決されたデバイス名。 "cuda:0" は "cuda" に正規化されます。
 91    :rtype: str
 92    """
 93    if device is None or str(device).strip().lower() == "auto":
 94        return default_runtime_device()
 95    value = str(device).strip().lower()
 96    if value == "cuda:0":
 97        return "cuda"
 98    return value
 99
100
101def _resolve_precision(device: str, precision: str | None) -> str:
102    """``auto`` をCUDA/XPU上のbf16、それ以外ではfp32に解決します。
103
104    :param device: 解決されたデバイス名 (例: "cpu", "cuda")。
105    :type device: str
106    :param precision: 精度名 (例: "fp32", "bf16", "auto")。
107    :type precision: str | None
108    :returns: 解決された精度名。
109    :rtype: str
110    :raises ValueError: サポートされていない精度が指定された場合、またはbf16が対応していないデバイスで指定された場合。
111    """
112    value = "auto" if precision is None else str(precision).strip().lower()
113    if value == "auto":
114        return "bf16" if device in {"cuda", "xpu"} else "fp32"
115    aliases = {
116        "bfloat16": "bf16",
117        "bf16": "bf16",
118        "float32": "fp32",
119        "fp32": "fp32",
120    }
121    if value not in aliases:
122        raise ValueError(f"Unsupported Irodori-TTS precision: {precision}")
123    resolved = aliases[value]
124    if resolved == "bf16" and device not in {"cuda", "xpu"}:
125        raise ValueError(f"bf16 is not supported for Irodori-TTS device={device}")
126    return resolved
127
128
129def _resolve_checkpoint(model_id: str) -> str:
130    """ローカルチェックポイントを解決するか、Hugging Faceチェックポイントをダウンロードします。
131
132    指定された ``model_id`` がファイルパスであればその絶対パスを返し、
133    そうでなければHugging Faceからダウンロードしてそのパスを返します。
134
135    :param model_id: モデルID (Hugging Faceのリポジトリ名またはローカルファイルパス)。
136    :type model_id: str
137    :returns: 解決されたチェックポイントファイルの絶対パス。
138    :rtype: str
139    :raises FileNotFoundError: ローカルファイルとして指定されたチェックポイントが見つからない場合。
140    """
141    candidate = Path(str(model_id)).expanduser()
142    if candidate.suffix.lower() in {".pt", ".safetensors"}:
143        if not candidate.is_file():
144            raise FileNotFoundError(f"Irodori-TTS checkpoint not found: {candidate}")
145        return str(candidate.resolve())
146    return str(download_hf_checkpoint(str(model_id)))
147
148
149def load_model(
150    model_id: str = DEFAULT_IRODORI_MODEL,
151    device: str = DEFAULT_DEVICE,
152    precision: str = DEFAULT_PRECISION,
153    *,
154    codec_device: str | None = None,
155    codec_precision: str | None = None,
156    force_reload: bool = False,
157):
158    """Irodori-TTS推論ランタイムをロードし、キャッシュします。
159
160    この関数は、指定されたモデル、デバイス、精度に基づいてIrodori-TTSのランタイムを初期化します。
161    ランタイムはIrodori-TTSの内部キャッシュメカニズムによって管理され、
162    同じ設定が再度要求された場合は既存のインスタンスが再利用されます。
163
164    :param model_id: ロードするIrodori-TTSモデルのHugging FaceリポジトリIDまたはローカルパス。デフォルトは ``DEFAULT_IRODORI_MODEL``。
165    :type model_id: str
166    :param device: モデルの推論に使用するデバイス ("cpu", "cuda", "auto" など)。デフォルトは ``DEFAULT_DEVICE``。
167    :type device: str
168    :param precision: モデルの推論に使用する精度 ("fp32", "bf16", "auto" など)。デフォルトは ``DEFAULT_PRECISION``。
169    :type precision: str
170    :param codec_device: コーデックの推論に使用するデバイス。指定されない場合は ``device`` が使用されます。
171    :type codec_device: str | None
172    :param codec_precision: コーデックの推論に使用する精度。指定されない場合は ``precision`` が使用されます。
173    :type codec_precision: str | None
174    :param force_reload: Trueの場合、既存のキャッシュされたランタイムをクリアして強制的に再ロードします。
175    :type force_reload: bool
176    :returns: ロードされたIrodori-TTSの ``InferenceRuntime`` インスタンス。
177    :rtype: irodori_tts.inference_runtime.InferenceRuntime
178    """
179    resolved_device = _resolve_device(device)
180    resolved_codec_device = _resolve_device(codec_device or resolved_device)
181    resolved_precision = _resolve_precision(resolved_device, precision)
182    resolved_codec_precision = _resolve_precision(
183        resolved_codec_device,
184        precision if codec_precision is None else codec_precision,
185    )
186    checkpoint = _resolve_checkpoint(model_id)
187
188    if force_reload:
189        clear_cached_runtime()
190
191    key = RuntimeKey(
192        checkpoint=checkpoint,
193        model_device=resolved_device,
194        codec_repo="Aratako/Semantic-DACVAE-Japanese-32dim",
195        model_precision=resolved_precision,
196        codec_device=resolved_codec_device,
197        codec_precision=resolved_codec_precision,
198        compile_model=False,
199        compile_dynamic=False,
200    )
201    runtime, reloaded = get_cached_runtime(key)
202    status = "loaded" if reloaded else "reused"
203    print(
204        "tktts_irodori.load_model(): "
205        f"{status}, model={model_id}, device={resolved_device}, "
206        f"precision={resolved_precision}"
207    )
208    return runtime
209
210
211def unload_models() -> None:
212    """Irodori-TTSによってキャッシュされたランタイムを解放します。
213
214    これにより、Irodori-TTSの推論ランタイムがキャッシュからクリアされ、
215    GPUメモリを使用している場合はGPUキャッシュも空にされます。
216    """
217    clear_cached_runtime()
218    if torch.cuda.is_available():
219        torch.cuda.empty_cache()
220
221
222def get_available_voices_info(model=None):
223    """Irodori-TTSが使用する仮想的なデフォルト音声の情報を返します。
224
225    Irodori-TTSは固定された話者リストを持っていません。
226    声の同一性は参照オーディオによって制御され、スタイルはキャプションによっても制御できます。
227    ``model`` 引数はtkttsインターフェースの互換性のために受け入れられますが、内部では使用されません。
228
229    :param model: tkttsインターフェース互換性のため。実際には使用されません。
230    :type model: Any
231    :returns: 仮想的なデフォルト音声の詳細情報を含む辞書のリスト。
232    :rtype: list[dict[str, str]]
233    """
234    del model
235    return [voice.copy() for voice in _AVAILABLE_VOICES]
236
237
238def get_available_voices(model=None):
239    """tkttsの音声セレクタと互換性のある音声名を返します。
240
241    :param model: tkttsインターフェース互換性のため。実際には使用されません。
242    :type model: Any
243    :returns: 利用可能な仮想音声名のリスト。
244    :rtype: list[str]
245    """
246    return [voice["name"] for voice in get_available_voices_info(model)]
247
248
249def list_available_voices(model=None) -> bool:
250    """Irodori-TTSの仮想的な音声エントリを表示します。
251
252    :param model: tkttsインターフェース互換性のため。実際には使用されません。
253    :type model: Any
254    :returns: 利用可能な音声がある場合はTrue、ない場合はFalse。
255    :rtype: bool
256    """
257    print(f"=== 利用可能な {TTS_ENGINE_NAME} voices ===")
258    voices = get_available_voices_info(model)
259    for voice in voices:
260        print(f"  Name: {voice['name']}, Description: {voice['description']}")
261    return bool(voices)
262
263
264def _speaker_key(name: str) -> str:
265    """話者名を正規化し、比較用のキーを生成します。
266
267    スペース、アンダースコア、ハイフン、括弧を除去し、小文字に変換します。
268
269    :param name: 話者名。
270    :type name: str
271    :returns: 正規化された話者キー。
272    :rtype: str
273    """
274    normalized = normalize_speaker(str(name))
275    return re.sub(r"[\s_\-()()]+", "", normalized).lower()
276
277
278def resolve_speaker_id(speaker_name, voices_info=None, model=None):
279    """``default``/``irodori`` を仮想的なIrodori音声に解決します。
280
281    Irodori-TTSは固定の話者IDを持たないため、指定された話者名が
282    ``default`` またはそのエイリアスであれば、仮想的なIrodori-TTS音声として扱われます。
283
284    :param speaker_name: 解決する話者名。
285    :type speaker_name: str
286    :param voices_info: 利用可能な音声情報のリスト。Noneの場合は ``get_available_voices_info()`` から取得されます。
287    :type voices_info: list[dict[str, str]] | None
288    :param model: tkttsインターフェース互換性のため。実際には使用されません。
289    :type model: Any
290    :returns: (voices_info, 解決された話者名) のタプル。
291    :rtype: tuple[list[dict[str, str]], str]
292    :raises ValueError: 話者名が見つからない場合。
293    """
294    del model
295    if voices_info is None:
296        voices_info = get_available_voices_info()
297
298    query = _speaker_key(speaker_name)
299    aliases = {
300        "default",
301        "irodori",
302        "irodoritts",
303        "標準",
304        "デフォルト",
305    }
306    if query in aliases:
307        return voices_info, DEFAULT_IRODORI_VOICE
308
309    for voice in voices_info:
310        if query == _speaker_key(voice["name"]):
311            return voices_info, voice["name"]
312
313    raise ValueError(
314        "❌ Error in tktts_irodori.resolve_speaker_id(): "
315        f"話者 [{speaker_name}] が見つかりませんでした。"
316        " Irodori-TTSでは voice=default を使用し、"
317        "irodori_captionまたは参照WAVで声を指定してください。"
318    )
319
320
321def _as_optional_int(value: Any, default: int | None) -> int | None:
322    """値を整数に変換し、Noneや特定の文字列をNoneとして扱います。
323
324    :param value: 変換する値。
325    :type value: Any
326    :param default: 値がNoneまたは特定の文字列の場合に返すデフォルト値。
327    :type default: int | None
328    :returns: 整数またはNone。
329    :rtype: int | None
330    """
331    if value is None:
332        return default
333    if isinstance(value, str):
334        text = value.strip().lower()
335        if text in {"", "none", "random"}:
336            return None
337    return int(value)
338
339
340def _as_ref_wavs(value: Any) -> list[str]:
341    """パス、イテラブル、またはセミコロン区切りのパスを正規化します。
342
343    :param value: 参照WAVファイルのパス、またはパスのイテラブル、またはセミコロン区切りのパス文字列。
344    :type value: Any
345    :returns: 解決された参照WAVファイルの絶対パスのリスト。
346    :rtype: list[str]
347    :raises FileNotFoundError: 指定された参照WAVファイルが見つからない場合。
348    """
349    if value is None:
350        return []
351    if isinstance(value, (str, os.PathLike)):
352        text = str(value).strip()
353        if not text:
354            return []
355        values = [part.strip() for part in text.split(";") if part.strip()]
356    elif isinstance(value, Iterable):
357        values = [str(part).strip() for part in value if str(part).strip()]
358    else:
359        values = [str(value).strip()]
360
361    paths = []
362    for item in values:
363        path = Path(item).expanduser()
364        if not path.is_file():
365            raise FileNotFoundError(f"Irodori-TTS reference WAV not found: {path}")
366        paths.append(str(path.resolve()))
367    return paths
368
369
370def speak(
371    outfile,
372    text,
373    voice=DEFAULT_IRODORI_VOICE,
374    speak_rate=None,
375    speak_pitch=None,
376    *,
377    caption: str | None = DEFAULT_CAPTION,
378    ref_wav: str | os.PathLike | None = None,
379    ref_wavs: Iterable[str | os.PathLike] | str | None = None,
380    model=None,
381    model_id: str = DEFAULT_IRODORI_MODEL,
382    device: str = DEFAULT_DEVICE,
383    precision: str = DEFAULT_PRECISION,
384    codec_device: str | None = None,
385    codec_precision: str | None = None,
386    num_steps: int = DEFAULT_NUM_STEPS,
387    cfg_scale_text: float = DEFAULT_CFG_SCALE_TEXT,
388    cfg_scale_caption: float = DEFAULT_CFG_SCALE_CAPTION,
389    cfg_scale_speaker: float = DEFAULT_CFG_SCALE_SPEAKER,
390    duration_scale: float = 1.0,
391    seed: int | str | None = DEFAULT_SEED,
392    lora_adapter: str | None = None,
393):
394    """Irodori-TTS v4.1-Smallを使用して1つのWAVファイルを生成します。
395
396    テキストを音声に変換し、指定された出力ファイルに保存します。
397    Irodori-TTSは固定話者を持たないため、``voice`` 引数は主に情報として扱われます。
398    音声のスタイルや同一性は、``caption`` および/または ``ref_wav``/``ref_wavs`` を使用して制御されます。
399
400    :param outfile: 生成されたWAVファイルを保存するパス。
401    :type outfile: str | os.PathLike
402    :param text: 読み上げるテキスト。
403    :type text: str
404    :param voice: 読み上げに使用する音声名。Irodori-TTSでは仮想的な音声であり、 ``default`` が推奨されます。デフォルトは ``DEFAULT_IRODORI_VOICE``。
405    :type voice: str
406    :param speak_rate: 音声の速さ。Irodori-TTSでは直接制御できないため無視されます。
407    :type speak_rate: float | None
408    :param speak_pitch: 音声のピッチ。Irodori-TTSでは直接制御できないため無視されます。
409    :type speak_pitch: float | None
410    :param caption: 音声のスタイルを制御するためのキャプション。例: "落ち着いた自然な声で、明瞭に読み上げる。"。デフォルトは ``DEFAULT_CAPTION``。
411    :type caption: str | None
412    :param ref_wav: スタイル参照に使用する単一のWAVファイルのパス。 ``ref_wavs`` と同時に指定された場合、結合されます。
413    :type ref_wav: str | os.PathLike | None
414    :param ref_wavs: スタイル参照に使用する複数のWAVファイルのパスのイテラブルまたはセミコロン区切りの文字列。
415    :type ref_wavs: Iterable[str | os.PathLike] | str | None
416    :param model: ロード済みのIrodori-TTSランタイムインスタンス。指定されない場合、 ``load_model`` を使って新規にロードされます。
417    :type model: irodori_tts.inference_runtime.InferenceRuntime | None
418    :param model_id: ロードするIrodori-TTSモデルID (``model`` がNoneの場合にのみ使用されます)。デフォルトは ``DEFAULT_IRODORI_MODEL``。
419    :type model_id: str
420    :param device: モデルの推論に使用するデバイス (``model`` がNoneの場合にのみ使用されます)。デフォルトは ``DEFAULT_DEVICE``。
421    :type device: str
422    :param precision: モデルの推論に使用する精度 (``model`` がNoneの場合にのみ使用されます)。デフォルトは ``DEFAULT_PRECISION``。
423    :type precision: str
424    :param codec_device: コーデックの推論に使用するデバイス (``model`` がNoneの場合にのみ使用されます)。
425    :type codec_device: str | None
426    :param codec_precision: コーデックの推論に使用する精度 (``model`` がNoneの場合にのみ使用されます)。
427    :type codec_precision: str | None
428    :param num_steps: サンプリングステップ数。デフォルトは ``DEFAULT_NUM_STEPS``。
429    :type num_steps: int
430    :param cfg_scale_text: テキストに対するCFGスケール。デフォルトは ``DEFAULT_CFG_SCALE_TEXT``。
431    :type cfg_scale_text: float
432    :param cfg_scale_caption: キャプションに対するCFGスケール。デフォルトは ``DEFAULT_CFG_SCALE_CAPTION``。
433    :type cfg_scale_caption: float
434    :param cfg_scale_speaker: 話者に対するCFGスケール (参照WAVがある場合のみ有効)。デフォルトは ``DEFAULT_CFG_SCALE_SPEAKER``。
435    :type cfg_scale_speaker: float
436    :param duration_scale: 音声の全体的な長さを調整するスケールファクター。デフォルトは 1.0。
437    :type duration_scale: float
438    :param seed: 乱数シード。None, "random" または空文字列の場合、シードはランダムに生成されます。デフォルトは ``DEFAULT_SEED``。
439    :type seed: int | str | None
440    :param lora_adapter: 使用するLoRAアダプターのパス。
441    :type lora_adapter: str | None
442    :returns: 生成されたWAVファイルの絶対パス。生成に失敗した場合はNone。
443    :rtype: str | None
444    """
445    text = str(text).strip()
446    if not text:
447        print("❌ tktts_irodori.speak(): 読み上げテキストが空です")
448        return None
449
450    if _speaker_key(voice) not in {
451        "default",
452        "irodori",
453        "irodoritts",
454        "標準",
455        "デフォルト",
456    }:
457        print(
458            f"    ** Warning: Irodori-TTSには固定話者 [{voice}] がないため "
459            "voice=defaultとして扱います"
460        )
461    if speak_rate is not None or speak_pitch is not None:
462        print(
463            "    ** Warning: Irodori-TTSでは speak_rate/speak_pitch を"
464            "直接指定できないため無視します"
465        )
466
467    references = _as_ref_wavs(ref_wavs)
468    if ref_wav is not None:
469        references = _as_ref_wavs(ref_wav) + references
470
471    if model is None:
472        model = load_model(
473            model_id=model_id,
474            device=device,
475            precision=precision,
476            codec_device=codec_device,
477            codec_precision=codec_precision,
478        )
479
480    try:
481        result = model.synthesize(
482            SamplingRequest(
483                text=text,
484                caption=str(caption).strip() if caption else None,
485                ref_wav=None,
486                ref_wavs=references or None,
487                no_ref=not references,
488                ref_normalize_db=-16.0,
489                ref_ensure_max=True,
490                num_candidates=1,
491                decode_mode="sequential",
492                seconds=None,
493                duration_scale=float(duration_scale),
494                max_ref_seconds=None,
495                num_steps=int(num_steps),
496                seed=_as_optional_int(seed, DEFAULT_SEED),
497                cfg_guidance_mode="independent",
498                cfg_scale_text=float(cfg_scale_text),
499                cfg_scale_caption=float(cfg_scale_caption),
500                cfg_scale_speaker=(float(cfg_scale_speaker) if references else 0.0),
501                cfg_min_t=0.5,
502                cfg_max_t=1.0,
503                context_kv_cache=True,
504                t_schedule_mode="linear",
505                sway_coeff=-1.0,
506                trim_tail=True,
507                lora_adapter=lora_adapter,
508            ),
509            log_fn=lambda message: print(f"    {message}"),
510        )
511        output_path = save_wav(outfile, result.audio, result.sample_rate)
512    except Exception as exc:
513        print(f"❌ Irodori-TTS生成エラー: {exc}")
514        return None
515
516    if Path(output_path).exists():
517        print(
518            f"    ** 一時ファイル [{output_path}] を保存しました "
519            f"(seed={result.used_seed})"
520        )
521        return str(output_path)
522
523    print(f"    ** Error: ファイル [{output_path}] の出力に失敗しました")
524    return None
525
526
527def _cfg_value(cfg, name: str, default: Any = None) -> Any:
528    """設定オブジェクトから属性値を取得します。
529
530    :param cfg: 設定オブジェクト。Noneの場合、デフォルト値を返します。
531    :type cfg: Any
532    :param name: 取得する属性の名前。
533    :type name: str
534    :param default: 属性が見つからない場合、または ``cfg`` がNoneの場合に返すデフォルト値。
535    :type default: Any
536    :returns: 設定値またはデフォルト値。
537    :rtype: Any
538    """
539    if cfg is None:
540        return default
541    return getattr(cfg, name, default)
542
543
544def speak_dialogue(
545    dialogue,
546    replacements,
547    target_voices,
548    speakers=None,
549    temp_dir=".",
550    outfile=None,
551    ext="wav",
552    cfg=None,
553    *,
554    caption: str | None = None,
555    ref_wav: str | os.PathLike | None = None,
556    ref_wavs: Iterable[str | os.PathLike] | str | None = None,
557    model=None,
558    model_id: str = DEFAULT_IRODORI_MODEL,
559    device: str = DEFAULT_DEVICE,
560    precision: str = DEFAULT_PRECISION,
561):
562    """tkttsダイアログシーケンスのための一時WAVファイルを生成します。
563
564    与えられたダイアログ(会話のリスト)を処理し、各発話に対してIrodori-TTSを使用して
565    一時的なWAVファイルを生成します。
566    ``cfg`` オブジェクトを介してIrodori-TTS固有のパラメータ(例: ``irodori_caption``, ``irodori_seed``)を
567    細かく制御できます。
568
569    :param dialogue: 処理するダイアログアイテムのリスト。各アイテムは ``split_dialogue`` が処理できる形式です。
570    :type dialogue: list
571    :param replacements: テキストに適用する置換辞書。
572    :type replacements: dict[str, str]
573    :param target_voices: ターゲットとする音声名のリストまたは単一の音声名。
574    :type target_voices: list[str] | str
575    :param speakers: 話者IDと音声名のマッピング辞書。デフォルトは空の辞書。
576    :type speakers: dict[str, str] | None
577    :param temp_dir: 一時的なWAVファイルを保存するディレクトリ。デフォルトはカレントディレクトリ。
578    :type temp_dir: str | os.PathLike
579    :param outfile: tkttsインターフェース互換性のため。実際には使用されません。
580    :type outfile: Any
581    :param ext: 生成される音声ファイルの拡張子。デフォルトは "wav"。
582    :type ext: str
583    :param cfg: 設定オブジェクト。このオブジェクトの属性(例: ``cfg.irodori_caption``)を通じて、
584                Irodori-TTSの各種パラメータをオーバーライドできます。
585    :type cfg: Any
586    :param caption: 音声のスタイルを制御するためのキャプション。 ``cfg.irodori_caption`` が設定されている場合はそちらが優先されます。
587    :type caption: str | None
588    :param ref_wav: スタイル参照に使用する単一のWAVファイルのパス。 ``cfg.irodori_ref_wav`` が設定されている場合はそちらが優先されます。
589    :type ref_wav: str | os.PathLike | None
590    :param ref_wavs: スタイル参照に使用する複数のWAVファイルのパス。 ``cfg.irodori_ref_wavs`` が設定されている場合はそちらが優先されます。
591    :type ref_wavs: Iterable[str | os.PathLike] | str | None
592    :param model: ロード済みのIrodori-TTSランタイムインスタンス。指定されない場合、 ``load_model`` を使って新規にロードされます。
593    :type model: irodori_tts.inference_runtime.InferenceRuntime | None
594    :param model_id: ロードするIrodori-TTSモデルID (``model`` がNoneの場合にのみ使用されます)。 ``cfg.irodori_model_id`` が設定されている場合はそちらが優先されます。
595    :type model_id: str
596    :param device: モデルの推論に使用するデバイス (``model`` がNoneの場合にのみ使用されます)。 ``cfg.irodori_device`` が設定されている場合はそちらが優先されます。
597    :type device: str
598    :param precision: モデルの推論に使用する精度 (``model`` がNoneの場合にのみ使用されます)。 ``cfg.irodori_precision`` が設定されている場合はそちらが優先されます。
599    :type precision: str
600    :param codec_device: コーデックの推論に使用するデバイス (``model`` がNoneの場合にのみ使用されます)。 ``cfg.irodori_codec_device`` が設定されている場合はそちらが優先されます。
601    :type codec_device: str | None
602    :param codec_precision: コーデックの推論に使用する精度 (``model`` がNoneの場合にのみ使用されます)。 ``cfg.irodori_codec_precision`` が設定されている場合はそちらが優先されます。
603    :type codec_precision: str | None
604    :returns: 生成が成功したかを示す真偽値と、生成された一時ファイルのパスのリストのタプル。
605    :rtype: tuple[bool, list[str]]
606    """
607    del outfile  # Kept in the signature for tktts backend compatibility.
608    speakers = {} if speakers is None else speakers
609
610    print("tktts_irodori.speak_dialogue(): target_voices:", target_voices)
611
612    model_id = _cfg_value(cfg, "irodori_model_id", model_id)
613    device = _cfg_value(cfg, "irodori_device", device)
614    precision = _cfg_value(cfg, "irodori_precision", precision)
615    codec_device = _cfg_value(cfg, "irodori_codec_device", None)
616    codec_precision = _cfg_value(cfg, "irodori_codec_precision", None)
617
618    if model is None:
619        try:
620            model = load_model(
621                model_id=model_id,
622                device=device,
623                precision=precision,
624                codec_device=codec_device,
625                codec_precision=codec_precision,
626            )
627        except Exception as exc:
628            print(f"❌ Irodori-TTSモデル読み込みエラー: {exc}")
629            return False, []
630
631    effective_caption = (
632        caption
633        if caption is not None
634        else _cfg_value(cfg, "irodori_caption", DEFAULT_CAPTION)
635    )
636    effective_ref_wav = (
637        ref_wav
638        if ref_wav is not None
639        else _cfg_value(cfg, "irodori_ref_wav", None)
640    )
641    effective_ref_wavs = (
642        ref_wavs
643        if ref_wavs is not None
644        else _cfg_value(cfg, "irodori_ref_wavs", None)
645    )
646    num_steps = _cfg_value(cfg, "irodori_num_steps", DEFAULT_NUM_STEPS)
647    cfg_scale_text = _cfg_value(
648        cfg, "irodori_cfg_scale_text", DEFAULT_CFG_SCALE_TEXT
649    )
650    cfg_scale_caption = _cfg_value(
651        cfg, "irodori_cfg_scale_caption", DEFAULT_CFG_SCALE_CAPTION
652    )
653    cfg_scale_speaker = _cfg_value(
654        cfg, "irodori_cfg_scale_speaker", DEFAULT_CFG_SCALE_SPEAKER
655    )
656    duration_scale = _cfg_value(cfg, "irodori_duration_scale", 1.0)
657    seed = _cfg_value(cfg, "irodori_seed", DEFAULT_SEED)
658    lora_adapter = _cfg_value(cfg, "irodori_lora_adapter", None)
659
660    tmpfiles = []
661    voices_info = get_available_voices_info(model)
662    idx = 1
663    is_monologue = bool(_cfg_value(cfg, "monologue", False))
664
665    for i, dialogue_item in enumerate(dialogue):
666        print()
667        print(f"Dialogue {i:04d}:")
668        dialogue_list = split_dialogue(
669            dialogue_item,
670            target_voices,
671            speakers=speakers,
672            default_voice=DEFAULT_IRODORI_VOICE,
673            is_monologue=is_monologue,
674        )
675
676        for speaker, text in dialogue_list:
677            tmpfile = os.path.join(temp_dir, f"tmp_{idx:03d}.{ext}")
678            text = apply_replacements(text, replacements)
679            if isinstance(target_voices, str):
680                speaker = target_voices
681
682            try:
683                voices_info, target_voice = resolve_speaker_id(
684                    speaker,
685                    voices_info=voices_info,
686                    model=model,
687                )
688            except ValueError as exc:
689                print(exc)
690                return False, tmpfiles
691
692            print(f"  {idx:04d}: voice={speaker} ({target_voice}): {text}")
693
694            generated = speak(
695                outfile=tmpfile,
696                text=text,
697                voice=target_voice,
698                speak_rate=_cfg_value(cfg, "fspeak_rate", None),
699                speak_pitch=_cfg_value(cfg, "fspeak_pitch", None),
700                caption=effective_caption,
701                ref_wav=effective_ref_wav,
702                ref_wavs=effective_ref_wavs,
703                model=model,
704                num_steps=num_steps,
705                cfg_scale_text=cfg_scale_text,
706                cfg_scale_caption=cfg_scale_caption,
707                cfg_scale_speaker=cfg_scale_speaker,
708                duration_scale=duration_scale,
709                seed=seed,
710                lora_adapter=lora_adapter,
711            )
712            if generated is None:
713                return False, tmpfiles
714
715            tmpfiles.append(tmpfile)
716            idx += 1
717
718    return True, tmpfiles
719
720
721if __name__ == "__main__":
722    list_available_voices()