cockolo terada commited on
Commit
d9fd6b3
·
verified ·
1 Parent(s): 28343bf

Update gradio_tabs/single.py

Browse files
Files changed (1) hide show
  1. gradio_tabs/single.py +38 -44
gradio_tabs/single.py CHANGED
@@ -492,8 +492,8 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
492
  gr.update(visible=True), # Row
493
  gr.update(value=f"**{i+1}**"), # Item Number Display
494
  gr.update(value=item['audio_path']), # Audio
495
- # ▼▼▼ 変更: ダウンロードボタンにもファイルパスを設定 ▼▼▼
496
- gr.update(value=item['audio_path'], visible=True), # Download File
497
  # ▲▲▲ 変更 ▲▲▲
498
  gr.update(value=info_text) # Info Markdown
499
  ])
@@ -502,8 +502,8 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
502
  gr.update(visible=False), # Row
503
  gr.update(value=""), # Item Number Display
504
  gr.update(value=None), # Audio
505
- # ▼▼▼ 変更: ダウンロードボタンも非表示に ▼▼▼
506
- gr.update(value=None, visible=False), # Download File
507
  # ▲▲▲ 変更 ▲▲▲
508
  gr.update(value="") # Info
509
  ])
@@ -525,8 +525,8 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
525
  with gr.Row(elem_classes="audio-output-row"):
526
  audio_item_columns = []
527
  audio_outputs = []
528
- # ▼▼▼ 変更: ダウンロード用コンポーネントのリストを追加 ▼▼▼
529
- download_files = []
530
  # ▲▲▲ 変更 ▲▲▲
531
  to_workbench_buttons = []
532
  synthesized_text_states = []
@@ -539,8 +539,8 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
539
  label=f"結果 {i+1}", elem_classes="compact-audio",
540
  type="filepath", interactive=False
541
  ))
542
- # ▼▼▼ 変更: ダウンロードボタンを追加 ▼▼▼
543
- download_files.append(gr.File(label="ダウンロード", interactive=False))
544
  # ▲▲▲ 変更 ▲▲▲
545
  with gr.Row():
546
  to_workbench_buttons.append(gr.Button("🛠️ キープ", scale=2))
@@ -606,8 +606,8 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
606
  add_merged_to_workbench_button = gr.Button("2.キープに追加", variant="primary")
607
  delete_originals_checkbox = gr.Checkbox(label="結合時に自動で元ファイルを削除", value=False, interactive=True)
608
  preview_audio_player = gr.Audio(label="結合結果プレビュー", interactive=False, type="filepath")
609
- # ▼▼▼ 変更: プレビュー用ダウンロードボタンを追加 ▼▼▼
610
- preview_download_file = gr.File(label="プレビューをダウンロード", interactive=False)
611
  # ▲▲▲ 変更 ▲▲▲
612
 
613
  ITEMS_PER_COLUMN = 4
@@ -616,11 +616,11 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
616
  with parent_column:
617
  with gr.Row(visible=False, elem_classes="workbench-item-row") as item_row:
618
  item_num_display = gr.Markdown(f"**{i+1}**", elem_classes=["text-center"])
619
- # ▼▼▼ 変更: AudioとDownloadを同じColumnに入れる ▼▼▼
620
  with gr.Column(scale=4):
621
  audio = gr.Audio(label=f"音声 {i+1}", interactive=False, type="filepath")
622
- download = gr.File(label="ダウンロード", interactive=False, visible=False)
623
- # ▲▲▲ 変更 ▲▲▲
 
624
  with gr.Column(scale=4): info = gr.Markdown()
625
  delete_btn = gr.Button("削除", scale=1, variant="primary")
626
  # ▼▼▼ 変更: downloadコンポーネントを辞書に追加 ▼▼▼
@@ -653,7 +653,6 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
653
  """モデルリストを再読み込みし、UIとバックエンドの状態を同期させる。"""
654
  MERGER_CACHE_PATH = Path("/tmp/sbv2_merger_cache")
655
 
656
- # FNモードと融☆合モードは排他的なため、FNモードがオンなら融☆合モードはオフとして扱う
657
  if use_fn_model_mode:
658
  use_symlink_mode = False
659
 
@@ -687,21 +686,18 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
687
  value = None
688
 
689
  if use_fn_model_mode:
690
- # FNモデルモード: FN1-10のみ表示し、カスタム順でソート
691
  ui_model_list = [name for name in current_available_models if fn_model_pattern.match(name)]
692
  sorted_list = sort_models_by_custom_order(ui_model_list, FN_MODE_MODEL_ORDER)
693
  value = sorted_list[0] if sorted_list else None
694
  model_dropdown_update = gr.update(choices=sorted_list, value=value)
695
 
696
  elif use_symlink_mode:
697
- # 融☆合モデルモード: シンボリックリンクのみ表示
698
  ui_model_list_names = [p.name for p in assets_root_path.iterdir() if p.is_symlink()]
699
  formatted_choices = format_and_sort_model_names(ui_model_list_names)
700
  value = formatted_choices[0][1] if formatted_choices else None
701
  model_dropdown_update = gr.update(choices=formatted_choices, value=value)
702
 
703
  else:
704
- # 通常モード: FNモデル, whisper, シンボリックリンクを除外し、カスタム順でソート
705
  ui_model_list = [
706
  name for name in current_available_models
707
  if name != 'whisper'
@@ -746,8 +742,8 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
746
  error_outputs.extend([
747
  gr.update(visible=False), # audio_item_columns
748
  gr.update(value=None), # audio_outputs
749
- # ▼▼▼ 変更: ダウンロードボタンのエラー時更新を追加 ▼▼▼
750
- gr.update(value=None), # download_files
751
  # ▲▲▲ 変更 ▲▲▲
752
  ])
753
  for _ in range(ITEMS_PER_ROW - 1):
@@ -755,6 +751,7 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
755
  for _ in range(MAX_AUDIO_OUTPUTS):
756
  error_outputs.append("") # synthesized_text_states
757
 
 
758
  if re.search(INVALID_FILENAME_CHARS_PATTERN, text):
759
  found_chars = "".join(sorted(list(set(re.findall(INVALID_FILENAME_CHARS_PATTERN, text)))))
760
  error_outputs[0] = f"❌ [エラー] テキストに使用できない文字が含まれています: {found_chars}"
@@ -768,7 +765,6 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
768
  if not style_display_name:
769
  error_outputs[0] = "❌ [エラー] スタイルが選択されていません。"
770
  return tuple(error_outputs)
771
-
772
  internal_style_key = None
773
  for key, data in styles_data.items():
774
  if data.get("display_name") == style_display_name: internal_style_key = key; break
@@ -793,6 +789,7 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
793
  final_audio_paths = []
794
  generated_texts = []
795
 
 
796
  if generation_mode == "発音ガチャ2":
797
  try:
798
  ratio_list = [float(x.strip()) for x in random_text_ratio_str.split(',') if x.strip()]
@@ -887,8 +884,8 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
887
  audio_val = final_audio_paths[i] if is_visible else None
888
  final_outputs.append(gr.update(visible=is_visible))
889
  final_outputs.append(gr.update(value=audio_val))
890
- # ▼▼▼ 変更: ダウンロードボタンにもファイルパスを渡す ▼▼▼
891
- final_outputs.append(gr.update(value=audio_val))
892
  # ▲▲▲ 変更 ▲▲▲
893
 
894
  num_dummies_needed = (ITEMS_PER_ROW - (num_generated % ITEMS_PER_ROW)) % ITEMS_PER_ROW if num_generated > 0 else 0
@@ -953,27 +950,25 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
953
 
954
  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)):
955
  log_messages = []
 
 
 
 
 
 
956
  if not workbench_list:
957
  log_messages.append("⚠️ [結合プレビュー警告] キープに音声がありません。")
958
- final_status = (current_status + "\n" + "\n".join(log_messages)).strip()
959
- # ▼▼▼ 変更: 戻り値にダウンロードボタンの更新を追加 ▼▼▼
960
- return final_status, None, None, {}
961
- # ▲▲▲ 変更 ▲▲▲
962
  idx1, idx2 = int(first_audio_num) - 1, int(second_audio_num) - 1
963
  if not (0 <= idx1 < len(workbench_list) and 0 <= idx2 < len(workbench_list)):
964
  log_messages.append(f"⚠️ [結合プレビュー警告] 指定された番号(#{first_audio_num}, #{second_audio_num})の音声が見つかりません。")
965
- final_status = (current_status + "\n" + "\n".join(log_messages)).strip()
966
- # ▼▼▼ 変更: 戻り値にダウンロードボタンの更新を追加 ▼▼▼
967
- return final_status, None, None, {}
968
- # ▲▲▲ 変更 ▲▲▲
969
  item1, item2 = workbench_list[idx1], workbench_list[idx2]
970
  audio_path1, audio_path2 = item1.get("audio_path"), item2.get("audio_path")
971
  if not audio_path1 or not Path(audio_path1).exists() or not audio_path2 or not Path(audio_path2).exists():
972
  log_messages.append("❌ [結合プレビューエラー] 音声ファイルが見つかりません。ファイルが削除された可能性があります。")
973
- final_status = (current_status + "\n" + "\n".join(log_messages)).strip()
974
- # ▼▼▼ 変更: 戻り値にダウンロードボタンの更新を追加 ▼▼▼
975
- return final_status, None, None, {}
976
- # ▲▲▲ 変更 ▲▲▲
977
  progress(0, desc="結合準備中...")
978
  try:
979
  segment1, segment2 = AudioSegment.from_file(audio_path1), AudioSegment.from_file(audio_path2)
@@ -993,10 +988,8 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
993
  progress(1, desc="結合完了")
994
  except Exception as e:
995
  log_messages.append(f"❌ [結合プレビューエラー] 音声の結合中にエラーが発生しました: {e}")
996
- final_status = (current_status + "\n" + "\n".join(log_messages)).strip()
997
- # ▼▼▼ 変更: 戻り値にダウンロードボタンの更新を追加 ▼▼▼
998
- return final_status, None, None, {}
999
- # ▲▲▲ 変更 ▲▲▲
1000
  output_filename = f"merged_preview_{uuid.uuid4().hex[:8]}.wav"
1001
  temp_path = Path(tempfile.gettempdir()) / output_filename
1002
  combined_audio.export(temp_path, format="wav")
@@ -1007,8 +1000,9 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
1007
  metadata = {"text": f"{item1.get('text', '')} | {item2.get('text', '')}", "display_models": sorted(list(all_display_models)), "original_models": sorted(list(all_original_models)), "audio_path": str(temp_path), "timestamp": datetime.datetime.now(JST).isoformat()}
1008
  log_messages.append("✅ 結合プレビューが生成されました。")
1009
  final_status = (current_status + "\n" + "\n".join(log_messages)).strip()
1010
- # ▼▼▼ 変更: 戻り値にダウンロードボタンの更新を追加 ▼▼▼
1011
- return final_status, str(temp_path), str(temp_path), metadata
 
1012
  # ▲▲▲ 変更 ▲▲▲
1013
 
1014
  def action_add_merged_to_workbench(current_status: str, preview_data: Dict, current_workbench_list: List[Dict], delete_originals: bool, first_audio_num: int, second_audio_num: int) -> Tuple:
@@ -1073,10 +1067,10 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
1073
  current_styles_dropdown.change(on_style_dropdown_select, inputs=[current_styles_dropdown, all_styles_data_state], outputs=[style_weight_for_synth_slider])
1074
  use_assist_text_checkbox.change(lambda x: (gr.update(visible=x), gr.update(visible=x)), inputs=[use_assist_text_checkbox], outputs=[assist_text_textbox, assist_text_weight_slider])
1075
 
1076
- # ▼▼▼ 変更: generate_buttonのoutputsにdownload_filesを追加 ▼▼▼
1077
  generate_outputs = [status_textbox, audio_output_area]
1078
  for i in range(MAX_AUDIO_OUTPUTS):
1079
- generate_outputs.extend([audio_item_columns[i], audio_outputs[i], download_files[i]])
1080
  generate_outputs.extend(dummy_audio_item_columns)
1081
  generate_outputs.extend(synthesized_text_states)
1082
  # ▲▲▲ 変更 ▲▲▲
@@ -1115,7 +1109,7 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
1115
  outputs=[status_textbox, workbench_state] + all_workbench_ui_components,
1116
  )
1117
 
1118
- # ▼▼▼ 変更: merge_preview_buttonのoutputsにpreview_download_fileを追加 ▼▼▼
1119
  merge_preview_button.click(
1120
  fn=action_merge_preview,
1121
  inputs=[
@@ -1125,7 +1119,7 @@ def create_synthesis_app(model_holder: TTSModelHolder) -> gr.Blocks:
1125
  merge_pause_input,
1126
  workbench_state
1127
  ],
1128
- outputs=[status_textbox, preview_audio_player, preview_download_file, merged_preview_state]
1129
  )
1130
  # ▲▲▲ 変更 ▲▲▲
1131
 
 
492
  gr.update(visible=True), # Row
493
  gr.update(value=f"**{i+1}**"), # Item Number Display
494
  gr.update(value=item['audio_path']), # Audio
495
+ # ▼▼▼ 変更: DownloadButtonの更新 ▼▼▼
496
+ gr.update(value=item['audio_path'], visible=True), # Download Button
497
  # ▲▲▲ 変更 ▲▲▲
498
  gr.update(value=info_text) # Info Markdown
499
  ])
 
502
  gr.update(visible=False), # Row
503
  gr.update(value=""), # Item Number Display
504
  gr.update(value=None), # Audio
505
+ # ▼▼▼ 変更: DownloadButtonの更新 ▼▼▼
506
+ gr.update(value=None, visible=False), # Download Button
507
  # ▲▲▲ 変更 ▲▲▲
508
  gr.update(value="") # Info
509
  ])
 
525
  with gr.Row(elem_classes="audio-output-row"):
526
  audio_item_columns = []
527
  audio_outputs = []
528
+ # ▼▼▼ 変更: ダウンロードボタンのリスト ▼▼▼
529
+ download_buttons = []
530
  # ▲▲▲ 変更 ▲▲▲
531
  to_workbench_buttons = []
532
  synthesized_text_states = []
 
539
  label=f"結果 {i+1}", elem_classes="compact-audio",
540
  type="filepath", interactive=False
541
  ))
542
+ # ▼▼▼ 変更: gr.Fileからgr.DownloadButtonに変更 ▼▼▼
543
+ download_buttons.append(gr.DownloadButton("ダウンロード", size="sm", visible=False))
544
  # ▲▲▲ 変更 ▲▲▲
545
  with gr.Row():
546
  to_workbench_buttons.append(gr.Button("🛠️ キープ", scale=2))
 
606
  add_merged_to_workbench_button = gr.Button("2.キープに追加", variant="primary")
607
  delete_originals_checkbox = gr.Checkbox(label="結合時に自動で元ファイルを削除", value=False, interactive=True)
608
  preview_audio_player = gr.Audio(label="結合結果プレビュー", interactive=False, type="filepath")
609
+ # ▼▼▼ 変更: プレビュー用ダウンロードボタン ▼▼▼
610
+ preview_download_button = gr.DownloadButton("プレビューをダウンロード", visible=False)
611
  # ▲▲▲ 変更 ▲▲▲
612
 
613
  ITEMS_PER_COLUMN = 4
 
616
  with parent_column:
617
  with gr.Row(visible=False, elem_classes="workbench-item-row") as item_row:
618
  item_num_display = gr.Markdown(f"**{i+1}**", elem_classes=["text-center"])
 
619
  with gr.Column(scale=4):
620
  audio = gr.Audio(label=f"音声 {i+1}", interactive=False, type="filepath")
621
+ # ▼▼▼ 変更: gr.Fileからgr.DownloadButtonに変更 ▼▼▼
622
+ download = gr.DownloadButton("ダウンロード", size="sm", visible=False)
623
+ # ▲▲▲ 変更 ▲▲▲
624
  with gr.Column(scale=4): info = gr.Markdown()
625
  delete_btn = gr.Button("削除", scale=1, variant="primary")
626
  # ▼▼▼ 変更: downloadコンポーネントを辞書に追加 ▼▼▼
 
653
  """モデルリストを再読み込みし、UIとバックエンドの状態を同期させる。"""
654
  MERGER_CACHE_PATH = Path("/tmp/sbv2_merger_cache")
655
 
 
656
  if use_fn_model_mode:
657
  use_symlink_mode = False
658
 
 
686
  value = None
687
 
688
  if use_fn_model_mode:
 
689
  ui_model_list = [name for name in current_available_models if fn_model_pattern.match(name)]
690
  sorted_list = sort_models_by_custom_order(ui_model_list, FN_MODE_MODEL_ORDER)
691
  value = sorted_list[0] if sorted_list else None
692
  model_dropdown_update = gr.update(choices=sorted_list, value=value)
693
 
694
  elif use_symlink_mode:
 
695
  ui_model_list_names = [p.name for p in assets_root_path.iterdir() if p.is_symlink()]
696
  formatted_choices = format_and_sort_model_names(ui_model_list_names)
697
  value = formatted_choices[0][1] if formatted_choices else None
698
  model_dropdown_update = gr.update(choices=formatted_choices, value=value)
699
 
700
  else:
 
701
  ui_model_list = [
702
  name for name in current_available_models
703
  if name != 'whisper'
 
742
  error_outputs.extend([
743
  gr.update(visible=False), # audio_item_columns
744
  gr.update(value=None), # audio_outputs
745
+ # ▼▼▼ 変更: ダウンロードボタンのエラー時更新 ▼▼▼
746
+ gr.update(value=None, visible=False), # download_buttons
747
  # ▲▲▲ 変更 ▲▲▲
748
  ])
749
  for _ in range(ITEMS_PER_ROW - 1):
 
751
  for _ in range(MAX_AUDIO_OUTPUTS):
752
  error_outputs.append("") # synthesized_text_states
753
 
754
+ # (バリデーションロジックは変更なし)
755
  if re.search(INVALID_FILENAME_CHARS_PATTERN, text):
756
  found_chars = "".join(sorted(list(set(re.findall(INVALID_FILENAME_CHARS_PATTERN, text)))))
757
  error_outputs[0] = f"❌ [エラー] テキストに使用できない文字が含まれています: {found_chars}"
 
765
  if not style_display_name:
766
  error_outputs[0] = "❌ [エラー] スタイルが選択されていません。"
767
  return tuple(error_outputs)
 
768
  internal_style_key = None
769
  for key, data in styles_data.items():
770
  if data.get("display_name") == style_display_name: internal_style_key = key; break
 
789
  final_audio_paths = []
790
  generated_texts = []
791
 
792
+ # (音声合成ロジックは変更なし)
793
  if generation_mode == "発音ガチャ2":
794
  try:
795
  ratio_list = [float(x.strip()) for x in random_text_ratio_str.split(',') if x.strip()]
 
884
  audio_val = final_audio_paths[i] if is_visible else None
885
  final_outputs.append(gr.update(visible=is_visible))
886
  final_outputs.append(gr.update(value=audio_val))
887
+ # ▼▼▼ 変更: ダウンロードボタンの更新 ▼▼▼
888
+ final_outputs.append(gr.update(value=audio_val, visible=is_visible))
889
  # ▲▲▲ 変更 ▲▲▲
890
 
891
  num_dummies_needed = (ITEMS_PER_ROW - (num_generated % ITEMS_PER_ROW)) % ITEMS_PER_ROW if num_generated > 0 else 0
 
950
 
951
  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)):
952
  log_messages = []
953
+ error_return = (
954
+ (current_status + "\n" + "\n".join(log_messages)).strip(),
955
+ None,
956
+ gr.update(value=None, visible=False),
957
+ {}
958
+ )
959
  if not workbench_list:
960
  log_messages.append("⚠️ [結合プレビュー警告] キープに音声がありません。")
961
+ return error_return
 
 
 
962
  idx1, idx2 = int(first_audio_num) - 1, int(second_audio_num) - 1
963
  if not (0 <= idx1 < len(workbench_list) and 0 <= idx2 < len(workbench_list)):
964
  log_messages.append(f"⚠️ [結合プレビュー警告] 指定された番号(#{first_audio_num}, #{second_audio_num})の音声が見つかりません。")
965
+ return error_return
 
 
 
966
  item1, item2 = workbench_list[idx1], workbench_list[idx2]
967
  audio_path1, audio_path2 = item1.get("audio_path"), item2.get("audio_path")
968
  if not audio_path1 or not Path(audio_path1).exists() or not audio_path2 or not Path(audio_path2).exists():
969
  log_messages.append("❌ [結合プレビューエラー] 音声ファイルが見つかりません。ファイルが削除された可能性があります。")
970
+ return error_return
971
+
 
 
972
  progress(0, desc="結合準備中...")
973
  try:
974
  segment1, segment2 = AudioSegment.from_file(audio_path1), AudioSegment.from_file(audio_path2)
 
988
  progress(1, desc="結合完了")
989
  except Exception as e:
990
  log_messages.append(f"❌ [結合プレビューエラー] 音声の結合中にエラーが発生しました: {e}")
991
+ return error_return
992
+
 
 
993
  output_filename = f"merged_preview_{uuid.uuid4().hex[:8]}.wav"
994
  temp_path = Path(tempfile.gettempdir()) / output_filename
995
  combined_audio.export(temp_path, format="wav")
 
1000
  metadata = {"text": f"{item1.get('text', '')} | {item2.get('text', '')}", "display_models": sorted(list(all_display_models)), "original_models": sorted(list(all_original_models)), "audio_path": str(temp_path), "timestamp": datetime.datetime.now(JST).isoformat()}
1001
  log_messages.append("✅ 結合プレビューが生成されました。")
1002
  final_status = (current_status + "\n" + "\n".join(log_messages)).strip()
1003
+
1004
+ # ▼▼▼ 変更: DownloadButtonの更新を追加 ▼▼▼
1005
+ return final_status, str(temp_path), gr.update(value=str(temp_path), visible=True), metadata
1006
  # ▲▲▲ 変更 ▲▲▲
1007
 
1008
  def action_add_merged_to_workbench(current_status: str, preview_data: Dict, current_workbench_list: List[Dict], delete_originals: bool, first_audio_num: int, second_audio_num: int) -> Tuple:
 
1067
  current_styles_dropdown.change(on_style_dropdown_select, inputs=[current_styles_dropdown, all_styles_data_state], outputs=[style_weight_for_synth_slider])
1068
  use_assist_text_checkbox.change(lambda x: (gr.update(visible=x), gr.update(visible=x)), inputs=[use_assist_text_checkbox], outputs=[assist_text_textbox, assist_text_weight_slider])
1069
 
1070
+ # ▼▼▼ 変更: generate_buttonのoutputsにdownload_buttonsを追加 ▼▼▼
1071
  generate_outputs = [status_textbox, audio_output_area]
1072
  for i in range(MAX_AUDIO_OUTPUTS):
1073
+ generate_outputs.extend([audio_item_columns[i], audio_outputs[i], download_buttons[i]])
1074
  generate_outputs.extend(dummy_audio_item_columns)
1075
  generate_outputs.extend(synthesized_text_states)
1076
  # ▲▲▲ 変更 ▲▲▲
 
1109
  outputs=[status_textbox, workbench_state] + all_workbench_ui_components,
1110
  )
1111
 
1112
+ # ▼▼▼ 変更: merge_preview_buttonのoutputsにpreview_download_buttonを追加 ▼▼▼
1113
  merge_preview_button.click(
1114
  fn=action_merge_preview,
1115
  inputs=[
 
1119
  merge_pause_input,
1120
  workbench_state
1121
  ],
1122
+ outputs=[status_textbox, preview_audio_player, preview_download_button, merged_preview_state]
1123
  )
1124
  # ▲▲▲ 変更 ▲▲▲
1125