cockolo terada commited on
Commit
0d63b88
·
verified ·
1 Parent(s): 6607eec

Update gradio_tabs/single.py

Browse files
Files changed (1) hide show
  1. gradio_tabs/single.py +34 -52
gradio_tabs/single.py CHANGED
@@ -51,10 +51,8 @@ class TTSModelHolder:
51
  p.mkdir(parents=True, exist_ok=True)
52
  # 起動時に一度だけサンプルモデルを作成するロジック
53
  if not any(p.iterdir()):
54
- # ▼▼▼ 変更: printをENABLE_LOGGINGで制御 ▼▼▼
55
  if ENABLE_LOGGING:
56
  print("No models found in model_assets. Creating sample models...")
57
- # ▲▲▲ 変更 ▲▲▲
58
  # Sample Model 1
59
  model1_path = p / "MyModel1"
60
  model1_path.mkdir(parents=True, exist_ok=True)
@@ -82,10 +80,8 @@ class TTSModelHolder:
82
  json.dump(style_settings_data, f, indent=2, ensure_ascii=False)
83
 
84
  # FNモデル (FN1-10)
85
- # ▼▼▼ 変更: printをENABLE_LOGGINGで制御 ▼▼▼
86
  if ENABLE_LOGGING:
87
  print("Creating FN models (FN1-10)...")
88
- # ▲▲▲ 変更 ▲▲▲
89
  for i in range(1, 11):
90
  fn_path = p / f"FN{i}"
91
  fn_path.mkdir(exist_ok=True)
@@ -94,10 +90,8 @@ class TTSModelHolder:
94
  json.dump({"data": {"style2id": {"Neutral": 0}}}, f)
95
 
96
  # whisperモデル (非表示用)
97
- # ▼▼▼ 変更: printをENABLE_LOGGINGで制御 ▼▼▼
98
  if ENABLE_LOGGING:
99
  print("Creating 'whisper' model...")
100
- # ▲▲▲ 変更 ▲▲▲
101
  whisper_path = p / "whisper"
102
  whisper_path.mkdir(exist_ok=True)
103
  (whisper_path / "G_0.safetensors").touch()
@@ -111,33 +105,25 @@ class TTSModelHolder:
111
  """
112
  if self.root_dir.is_dir():
113
  self.model_names = sorted([d.name for d in self.root_dir.iterdir() if d.is_dir()])
114
- # ▼▼▼ 変更: printをENABLE_LOGGINGで制御 ▼▼▼
115
  if ENABLE_LOGGING:
116
  print(f"TTSModelHolder model list refreshed. Known models: {self.model_names}")
117
- # ▲▲▲ 変更 ▲▲▲
118
  else:
119
  self.model_names = []
120
- # ▼▼▼ 変更: printをENABLE_LOGGINGで制御 ▼▼▼
121
  if ENABLE_LOGGING:
122
  print("TTSModelHolder root directory not found.")
123
- # ▲▲▲ 変更 ▲▲▲
124
  return self.model_names
125
 
126
  def get_model(self, model_name, model_path):
127
- # ▼▼▼ 変更: printをENABLE_LOGGINGで制御 ▼▼▼
128
  if ENABLE_LOGGING:
129
  print(f"Loading model: {model_name} (file: {Path(model_path).name})")
130
- # ▲▲▲ 変更 ▲▲▲
131
  if model_name not in self.model_names:
132
  error_msg = (
133
  f"Model '{model_name}' is not in the known list of TTSModelHolder. "
134
  f"Current list: {self.model_names}. "
135
  "Please refresh the model list by toggling the symlink checkbox or clicking the refresh button."
136
  )
137
- # ▼▼▼ 変更: printをエラーなので残すか、制御するか検討。ここでは制御対象に含める。▼▼▼
138
  if ENABLE_LOGGING:
139
  print(f"[ERROR] {error_msg}")
140
- # ▲▲▲ 変更 ▲▲▲
141
  raise ValueError(error_msg)
142
 
143
  self.current_model = MockTTSModel()
@@ -149,10 +135,8 @@ class MockTTSModel:
149
 
150
  def infer(self, text, **kwargs):
151
  length_scale = kwargs.get('length', 1.0)
152
- # ▼▼▼ 変更: printをENABLE_LOGGINGで制御 ▼▼▼
153
  if ENABLE_LOGGING:
154
  print(f"Inferencing with text '{text}' and style: {kwargs.get('style')} and weight: {kwargs.get('style_weight')}, length_scale: {length_scale}")
155
- # ▲▲▲ 変更 ▲▲▲
156
  sampling_rate = 44100
157
  base_duration = max(1, len(text) // 5)
158
  duration = base_duration * length_scale
@@ -237,10 +221,8 @@ def sort_models_by_custom_order(model_list: List[str], custom_order: List[str])
237
 
238
  def set_random_seed(seed: int):
239
  if seed >= 0:
240
- # ▼▼▼ 変更: printをENABLE_LOGGINGで制御 ▼▼▼
241
  if ENABLE_LOGGING:
242
  print(f"Setting random seed to: {seed}")
243
- # ▲▲▲ 変更 ▲▲▲
244
  torch.manual_seed(seed)
245
  if torch.cuda.is_available():
246
  torch.cuda.manual_seed(seed)
@@ -423,19 +405,15 @@ def process_single_synthesis_webui(
423
  current_model_file_path = Path(current_model_file_path_str)
424
  log_messages = []
425
  set_random_seed(seed_arg)
426
- # ▼▼▼ 変更: ログ追加をENABLE_LOGGINGで制御 ▼▼▼
427
  if seed_arg >= 0 and ENABLE_LOGGING:
428
  log_messages.append(f"乱数シードを {seed_arg} に固定しました。")
429
- # ▲▲▲ 変更 ▲▲▲
430
  try:
431
  model_holder_ref.get_model(current_model_name, current_model_file_path)
432
  if model_holder_ref.current_model is None:
433
  msg = f"モデルのロード失敗: {current_model_name} (ファイル: {current_model_file_path.name})"
434
  log_messages.append(f"❌ [エラー] {msg}"); return False, log_messages, None
435
- # ▼▼▼ 変更: ログ追加をENABLE_LOGGINGで制御 ▼▼▼
436
  if ENABLE_LOGGING:
437
  log_messages.append(f"使用モデル: {current_model_name} (ファイル: {current_model_file_path.name})")
438
- # ▲▲▲ 変更 ▲▲▲
439
  except Exception as e:
440
  msg = f"モデルロードエラー '{current_model_name}' (ファイル: {current_model_file_path.name}): {e}"
441
  log_messages.append(f"❌ [エラー] {msg}"); return False, log_messages, None
@@ -446,10 +424,8 @@ def process_single_synthesis_webui(
446
  speaker_id = model_spk2id[speaker_name_arg]
447
  elif model_spk2id:
448
  speaker_id = list(model_spk2id.values())[0]
449
- # ▼▼▼ 変更: ログ追加をENABLE_LOGGINGで制御 ▼▼▼
450
  if ENABLE_LOGGING:
451
  log_messages.append(f"音声合成中...")
452
- # ▲▲▲ 変更 ▲▲▲
453
  start_time_synth = datetime.datetime.now(JST)
454
  try:
455
  length_for_model = 1.0 / length_scale_arg if length_scale_arg != 0 else 1.0
@@ -467,10 +443,8 @@ def process_single_synthesis_webui(
467
  except Exception as e:
468
  msg = f"予期せぬエラー: {e}"; log_messages.append(f"❌ [エラー] {msg}"); return False, log_messages, None
469
  duration_synth = (datetime.datetime.now(JST) - start_time_synth).total_seconds()
470
- # ▼▼▼ 変更: ログ追加をENABLE_LOGGINGで制御 ▼▼▼
471
  if ENABLE_LOGGING:
472
  log_messages.append(f"音声合成成功。音声長: {len(audio_data)/sr:.2f}s, 処理時間: {duration_synth:.2f}s.")
473
- # ▲▲▲ 変更 ▲▲▲
474
  return True, log_messages, (sr, audio_data)
475
 
476
 
@@ -679,7 +653,7 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
679
  all_workbench_ui_components.extend([item["container"], item["item_num_display"], item["audio"], item["download"], item["info"]])
680
 
681
 
682
- # --- UIイベントハンドラ関数 (一部変更あり) ---
683
  def load_styles_for_ui(selected_model_name: Optional[str]):
684
  if not selected_model_name: return gr.update(choices=[], value=None), gr.update(value=DEFAULT_STYLE_WEIGHT), {}
685
  model_path = assets_root_path / selected_model_name
@@ -695,6 +669,7 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
695
  default_weight = styles_map[first_key].get("weight", DEFAULT_STYLE_WEIGHT)
696
  return gr.update(choices=display_names, value=default_display_name), gr.update(value=default_weight), styles_map
697
 
 
698
  def action_refresh_model_list(use_fn_model_mode: bool, use_symlink_mode: bool):
699
  """モデルリストを再読み込みし、UIとバックエンドの状態を同期させる。"""
700
  MERGER_CACHE_PATH = Path("/tmp/sbv2_merger_cache")
@@ -731,20 +706,16 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
731
  fn_model_pattern = re.compile(r'^FN([1-9]|10)$')
732
  current_available_models = model_holder.model_names
733
 
734
- model_dropdown_update = None
735
- value = None
736
 
737
  if use_fn_model_mode:
738
  ui_model_list = [name for name in current_available_models if fn_model_pattern.match(name)]
739
- sorted_list = sort_models_by_custom_order(ui_model_list, FN_MODE_MODEL_ORDER)
740
- value = sorted_list[0] if sorted_list else None
741
- model_dropdown_update = gr.update(choices=sorted_list, value=value)
742
 
743
  elif use_symlink_mode:
744
  ui_model_list_names = [p.name for p in assets_root_path.iterdir() if p.is_symlink()]
745
- formatted_choices = format_and_sort_model_names(ui_model_list_names)
746
- value = formatted_choices[0][1] if formatted_choices else None
747
- model_dropdown_update = gr.update(choices=formatted_choices, value=value)
748
 
749
  else:
750
  ui_model_list = [
@@ -753,13 +724,32 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
753
  and not fn_model_pattern.match(name)
754
  and not (assets_root_path / name).is_symlink()
755
  ]
756
- sorted_list = sort_models_by_custom_order(ui_model_list, NORMAL_MODE_MODEL_ORDER)
757
- value = sorted_list[0] if sorted_list else None
758
- model_dropdown_update = gr.update(choices=sorted_list, value=value)
759
-
760
- style_dropdown_update, style_weight_update, styles_data_state_update = load_styles_for_ui(value)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
761
 
762
  return model_dropdown_update, style_dropdown_update, style_weight_update, styles_data_state_update
 
763
 
764
  def on_model_select_change(selected_model_name: Optional[str]):
765
  style_dropdown_update, style_weight_update, styles_data_state_update = load_styles_for_ui(selected_model_name)
@@ -803,8 +793,8 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
803
  found_chars = "".join(sorted(list(set(re.findall(INVALID_FILENAME_CHARS_PATTERN, text)))))
804
  error_outputs[0] = f"❌ [エラー] テキストに使用できない文字が含まれています: {found_chars}"
805
  return tuple(error_outputs)
806
- if not model_name:
807
- error_outputs[0] = "❌ [エラー] モデルフォルダが選択されていません。"
808
  return tuple(error_outputs)
809
  if not text.strip():
810
  error_outputs[0] = "❌ [エラー] テキストが入力されていません。"
@@ -867,9 +857,9 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
867
  ratio_list = [0.5]
868
  all_logs.append("⚠️ [警告] カタカナ化の割合の解析に失敗したため、0.5 を使用します。")
869
 
 
870
  if ENABLE_LOGGING:
871
  all_logs.append(f"--- 発音ガチャ2 モード (pyopenjtalk) ---")
872
- internal_mode = int(random_text_mode) + 1
873
  all_logs.append(f"粒度: {random_text_mode} (内部モード: {internal_mode}), カタカナ化割合候補: {ratio_list}")
874
 
875
  generated_variations: Dict[str, List[str]] = {}
@@ -939,14 +929,12 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
939
 
940
  final_outputs = []
941
 
942
- # ▼▼▼ 変更: ログ表示をENABLE_LOGGINGで制御 ▼▼▼
943
  if ENABLE_LOGGING:
944
  status_message = "\n".join(all_logs)
945
  else:
946
  essential_logs = [log for log in all_logs if any(prefix in log for prefix in ["✅", "❌", "⚠️", "ℹ️"])]
947
  status_message = "\n".join(essential_logs)
948
  final_outputs.append(status_message)
949
- # ▲▲▲ 変更 ▲▲▲
950
 
951
  final_outputs.append(gr.update(visible=num_generated > 0))
952
 
@@ -1008,13 +996,11 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
1008
 
1009
  ui_updates = update_workbench_ui(updated_list)
1010
  log_messages.append("✅ キープに音声を追加しました。")
1011
- # ▼▼▼ 変更: ログ表示をENABLE_LOGGINGで制御 ▼▼▼
1012
  if ENABLE_LOGGING:
1013
  final_status = (current_status + "\n" + "\n".join(log_messages)).strip()
1014
  else:
1015
  essential_logs = [log for log in log_messages if any(prefix in log for prefix in ["✅", "❌", "⚠️", "ℹ️"])]
1016
  final_status = "\n".join(essential_logs).strip()
1017
- # ▲▲▲ 変更 ▲▲▲
1018
  return (final_status, updated_list) + ui_updates
1019
 
1020
  def remove_from_workbench(current_status: str, index_to_remove: int, current_workbench_list: List[Dict]) -> Tuple:
@@ -1042,13 +1028,11 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
1042
 
1043
  updated_list = [item for i, item in enumerate(safe_workbench_list) if i != index_to_remove]
1044
  ui_updates = update_workbench_ui(updated_list)
1045
- # ▼▼▼ 変更: ログ表示をENABLE_LOGGINGで制御 ▼▼▼
1046
  if ENABLE_LOGGING:
1047
  final_status = (current_status + "\n" + "\n".join(log_messages)).strip()
1048
  else:
1049
  essential_logs = [log for log in log_messages if any(prefix in log for prefix in ["✅", "❌", "⚠️", "ℹ️"])]
1050
  final_status = "\n".join(essential_logs).strip()
1051
- # ▲▲▲ 変更 ▲▲▲
1052
  return (final_status, updated_list) + ui_updates
1053
 
1054
  def action_merge_preview(current_status: str, first_audio_num: int, second_audio_num: int, pause_ms: int, workbench_list: List[Dict], progress=gr.Progress(track_tqdm=True)):
@@ -1187,7 +1171,7 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
1187
  return (final_status, final_workbench_list) + ui_updates
1188
 
1189
 
1190
- # --- イベントリスナー接続 (変更なし) ---
1191
  def on_fn_mode_change(is_fn_mode_on: bool) -> gr.Checkbox:
1192
  if is_fn_mode_on: return gr.update(value=False)
1193
  return gr.update()
@@ -1289,10 +1273,8 @@ if __name__ == "__main__":
1289
 
1290
  merger_cache_path = Path("/tmp/sbv2_merger_cache")
1291
  mock_model_holder = TTSModelHolder()
1292
- # ▼▼▼ 変更: printをENABLE_LOGGINGで制御 ▼▼▼
1293
  if ENABLE_LOGGING:
1294
  print(f"Initial models loaded by TTSModelHolder: {mock_model_holder.model_names}")
1295
- # ▲▲▲ 変更 ▲▲▲
1296
 
1297
  app = create_synthesis_app(mock_model_holder)
1298
 
 
51
  p.mkdir(parents=True, exist_ok=True)
52
  # 起動時に一度だけサンプルモデルを作成するロジック
53
  if not any(p.iterdir()):
 
54
  if ENABLE_LOGGING:
55
  print("No models found in model_assets. Creating sample models...")
 
56
  # Sample Model 1
57
  model1_path = p / "MyModel1"
58
  model1_path.mkdir(parents=True, exist_ok=True)
 
80
  json.dump(style_settings_data, f, indent=2, ensure_ascii=False)
81
 
82
  # FNモデル (FN1-10)
 
83
  if ENABLE_LOGGING:
84
  print("Creating FN models (FN1-10)...")
 
85
  for i in range(1, 11):
86
  fn_path = p / f"FN{i}"
87
  fn_path.mkdir(exist_ok=True)
 
90
  json.dump({"data": {"style2id": {"Neutral": 0}}}, f)
91
 
92
  # whisperモデル (非表示用)
 
93
  if ENABLE_LOGGING:
94
  print("Creating 'whisper' model...")
 
95
  whisper_path = p / "whisper"
96
  whisper_path.mkdir(exist_ok=True)
97
  (whisper_path / "G_0.safetensors").touch()
 
105
  """
106
  if self.root_dir.is_dir():
107
  self.model_names = sorted([d.name for d in self.root_dir.iterdir() if d.is_dir()])
 
108
  if ENABLE_LOGGING:
109
  print(f"TTSModelHolder model list refreshed. Known models: {self.model_names}")
 
110
  else:
111
  self.model_names = []
 
112
  if ENABLE_LOGGING:
113
  print("TTSModelHolder root directory not found.")
 
114
  return self.model_names
115
 
116
  def get_model(self, model_name, model_path):
 
117
  if ENABLE_LOGGING:
118
  print(f"Loading model: {model_name} (file: {Path(model_path).name})")
 
119
  if model_name not in self.model_names:
120
  error_msg = (
121
  f"Model '{model_name}' is not in the known list of TTSModelHolder. "
122
  f"Current list: {self.model_names}. "
123
  "Please refresh the model list by toggling the symlink checkbox or clicking the refresh button."
124
  )
 
125
  if ENABLE_LOGGING:
126
  print(f"[ERROR] {error_msg}")
 
127
  raise ValueError(error_msg)
128
 
129
  self.current_model = MockTTSModel()
 
135
 
136
  def infer(self, text, **kwargs):
137
  length_scale = kwargs.get('length', 1.0)
 
138
  if ENABLE_LOGGING:
139
  print(f"Inferencing with text '{text}' and style: {kwargs.get('style')} and weight: {kwargs.get('style_weight')}, length_scale: {length_scale}")
 
140
  sampling_rate = 44100
141
  base_duration = max(1, len(text) // 5)
142
  duration = base_duration * length_scale
 
221
 
222
  def set_random_seed(seed: int):
223
  if seed >= 0:
 
224
  if ENABLE_LOGGING:
225
  print(f"Setting random seed to: {seed}")
 
226
  torch.manual_seed(seed)
227
  if torch.cuda.is_available():
228
  torch.cuda.manual_seed(seed)
 
405
  current_model_file_path = Path(current_model_file_path_str)
406
  log_messages = []
407
  set_random_seed(seed_arg)
 
408
  if seed_arg >= 0 and ENABLE_LOGGING:
409
  log_messages.append(f"乱数シードを {seed_arg} に固定しました。")
 
410
  try:
411
  model_holder_ref.get_model(current_model_name, current_model_file_path)
412
  if model_holder_ref.current_model is None:
413
  msg = f"モデルのロード失敗: {current_model_name} (ファイル: {current_model_file_path.name})"
414
  log_messages.append(f"❌ [エラー] {msg}"); return False, log_messages, None
 
415
  if ENABLE_LOGGING:
416
  log_messages.append(f"使用モデル: {current_model_name} (ファイル: {current_model_file_path.name})")
 
417
  except Exception as e:
418
  msg = f"モデルロードエラー '{current_model_name}' (ファイル: {current_model_file_path.name}): {e}"
419
  log_messages.append(f"❌ [エラー] {msg}"); return False, log_messages, None
 
424
  speaker_id = model_spk2id[speaker_name_arg]
425
  elif model_spk2id:
426
  speaker_id = list(model_spk2id.values())[0]
 
427
  if ENABLE_LOGGING:
428
  log_messages.append(f"音声合成中...")
 
429
  start_time_synth = datetime.datetime.now(JST)
430
  try:
431
  length_for_model = 1.0 / length_scale_arg if length_scale_arg != 0 else 1.0
 
443
  except Exception as e:
444
  msg = f"予期せぬエラー: {e}"; log_messages.append(f"❌ [エラー] {msg}"); return False, log_messages, None
445
  duration_synth = (datetime.datetime.now(JST) - start_time_synth).total_seconds()
 
446
  if ENABLE_LOGGING:
447
  log_messages.append(f"音声合成成功。音声長: {len(audio_data)/sr:.2f}s, 処理時間: {duration_synth:.2f}s.")
 
448
  return True, log_messages, (sr, audio_data)
449
 
450
 
 
653
  all_workbench_ui_components.extend([item["container"], item["item_num_display"], item["audio"], item["download"], item["info"]])
654
 
655
 
656
+ # --- UIイベントハンドラ関数 (action_refresh_model_list を修正) ---
657
  def load_styles_for_ui(selected_model_name: Optional[str]):
658
  if not selected_model_name: return gr.update(choices=[], value=None), gr.update(value=DEFAULT_STYLE_WEIGHT), {}
659
  model_path = assets_root_path / selected_model_name
 
669
  default_weight = styles_map[first_key].get("weight", DEFAULT_STYLE_WEIGHT)
670
  return gr.update(choices=display_names, value=default_display_name), gr.update(value=default_weight), styles_map
671
 
672
+ # ▼▼▼ 修正: この関数を堅牢化してエラーを防ぐ ▼▼▼
673
  def action_refresh_model_list(use_fn_model_mode: bool, use_symlink_mode: bool):
674
  """モデルリストを再読み込みし、UIとバックエンドの状態を同期させる。"""
675
  MERGER_CACHE_PATH = Path("/tmp/sbv2_merger_cache")
 
706
  fn_model_pattern = re.compile(r'^FN([1-9]|10)$')
707
  current_available_models = model_holder.model_names
708
 
709
+ final_choices = []
710
+ final_value_for_style_load = None
711
 
712
  if use_fn_model_mode:
713
  ui_model_list = [name for name in current_available_models if fn_model_pattern.match(name)]
714
+ final_choices = sort_models_by_custom_order(ui_model_list, FN_MODE_MODEL_ORDER)
 
 
715
 
716
  elif use_symlink_mode:
717
  ui_model_list_names = [p.name for p in assets_root_path.iterdir() if p.is_symlink()]
718
+ final_choices = format_and_sort_model_names(ui_model_list_names)
 
 
719
 
720
  else:
721
  ui_model_list = [
 
724
  and not fn_model_pattern.match(name)
725
  and not (assets_root_path / name).is_symlink()
726
  ]
727
+ final_choices = sort_models_by_custom_order(ui_model_list, NORMAL_MODE_MODEL_ORDER)
728
+
729
+ if not final_choices:
730
+ # 選択肢が空の場���、エラーを防ぐためにダミー項目を設定し、ドロップダウンを無効化
731
+ model_dropdown_update = gr.update(
732
+ choices=["(利用可能なモデルがありません)"],
733
+ value="(利用可能なモデルがありません)",
734
+ interactive=False
735
+ )
736
+ final_value_for_style_load = None
737
+ else:
738
+ # 選択肢がある場合、通常通り設定
739
+ is_tuple_choices = isinstance(final_choices[0], tuple)
740
+ actual_value = final_choices[0][1] if is_tuple_choices else final_choices[0]
741
+
742
+ model_dropdown_update = gr.update(
743
+ choices=final_choices,
744
+ value=actual_value,
745
+ interactive=True
746
+ )
747
+ final_value_for_style_load = actual_value
748
+
749
+ style_dropdown_update, style_weight_update, styles_data_state_update = load_styles_for_ui(final_value_for_style_load)
750
 
751
  return model_dropdown_update, style_dropdown_update, style_weight_update, styles_data_state_update
752
+ # ▲▲▲ 修正 ▲▲▲
753
 
754
  def on_model_select_change(selected_model_name: Optional[str]):
755
  style_dropdown_update, style_weight_update, styles_data_state_update = load_styles_for_ui(selected_model_name)
 
793
  found_chars = "".join(sorted(list(set(re.findall(INVALID_FILENAME_CHARS_PATTERN, text)))))
794
  error_outputs[0] = f"❌ [エラー] テキストに使用できない文字が含まれています: {found_chars}"
795
  return tuple(error_outputs)
796
+ if not model_name or model_name == "(利用可能なモデルがありません)": # ダミー項目もチェック
797
+ error_outputs[0] = "❌ [エラー] モデルが選択されていません。"
798
  return tuple(error_outputs)
799
  if not text.strip():
800
  error_outputs[0] = "❌ [エラー] テキストが入力されていません。"
 
857
  ratio_list = [0.5]
858
  all_logs.append("⚠️ [警告] カタカナ化の割合の解析に失敗したため、0.5 を使用します。")
859
 
860
+ internal_mode = int(random_text_mode) + 1
861
  if ENABLE_LOGGING:
862
  all_logs.append(f"--- 発音ガチャ2 モード (pyopenjtalk) ---")
 
863
  all_logs.append(f"粒度: {random_text_mode} (内部モード: {internal_mode}), カタカナ化割合候補: {ratio_list}")
864
 
865
  generated_variations: Dict[str, List[str]] = {}
 
929
 
930
  final_outputs = []
931
 
 
932
  if ENABLE_LOGGING:
933
  status_message = "\n".join(all_logs)
934
  else:
935
  essential_logs = [log for log in all_logs if any(prefix in log for prefix in ["✅", "❌", "⚠️", "ℹ️"])]
936
  status_message = "\n".join(essential_logs)
937
  final_outputs.append(status_message)
 
938
 
939
  final_outputs.append(gr.update(visible=num_generated > 0))
940
 
 
996
 
997
  ui_updates = update_workbench_ui(updated_list)
998
  log_messages.append("✅ キープに音声を追加しました。")
 
999
  if ENABLE_LOGGING:
1000
  final_status = (current_status + "\n" + "\n".join(log_messages)).strip()
1001
  else:
1002
  essential_logs = [log for log in log_messages if any(prefix in log for prefix in ["✅", "❌", "⚠️", "ℹ️"])]
1003
  final_status = "\n".join(essential_logs).strip()
 
1004
  return (final_status, updated_list) + ui_updates
1005
 
1006
  def remove_from_workbench(current_status: str, index_to_remove: int, current_workbench_list: List[Dict]) -> Tuple:
 
1028
 
1029
  updated_list = [item for i, item in enumerate(safe_workbench_list) if i != index_to_remove]
1030
  ui_updates = update_workbench_ui(updated_list)
 
1031
  if ENABLE_LOGGING:
1032
  final_status = (current_status + "\n" + "\n".join(log_messages)).strip()
1033
  else:
1034
  essential_logs = [log for log in log_messages if any(prefix in log for prefix in ["✅", "❌", "⚠️", "ℹ️"])]
1035
  final_status = "\n".join(essential_logs).strip()
 
1036
  return (final_status, updated_list) + ui_updates
1037
 
1038
  def action_merge_preview(current_status: str, first_audio_num: int, second_audio_num: int, pause_ms: int, workbench_list: List[Dict], progress=gr.Progress(track_tqdm=True)):
 
1171
  return (final_status, final_workbench_list) + ui_updates
1172
 
1173
 
1174
+ # --- イベントリスナー接続 ---
1175
  def on_fn_mode_change(is_fn_mode_on: bool) -> gr.Checkbox:
1176
  if is_fn_mode_on: return gr.update(value=False)
1177
  return gr.update()
 
1273
 
1274
  merger_cache_path = Path("/tmp/sbv2_merger_cache")
1275
  mock_model_holder = TTSModelHolder()
 
1276
  if ENABLE_LOGGING:
1277
  print(f"Initial models loaded by TTSModelHolder: {mock_model_holder.model_names}")
 
1278
 
1279
  app = create_synthesis_app(mock_model_holder)
1280