tori29umai commited on
Commit
12764be
·
verified ·
1 Parent(s): 82b1671

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +188 -56
app.py CHANGED
@@ -7,6 +7,7 @@ import requests
7
  from requests.adapters import HTTPAdapter
8
  from urllib3.util.retry import Retry
9
  import json
 
10
 
11
  os.environ['HF_HOME'] = os.path.abspath(os.path.realpath(os.path.join(os.path.dirname(__file__), './hf_download')))
12
 
@@ -109,6 +110,76 @@ else:
109
  models = {}
110
  cpu_fallback_mode = not GPU_AVAILABLE # GPUが利用できない場合、CPU代替モードを使用
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  # モデルロード関数を使用
113
  def load_models():
114
  global models, cpu_fallback_mode, GPU_INITIALIZED
@@ -419,6 +490,7 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
419
 
420
  job_id = generate_timestamp()
421
  last_output_filename = None
 
422
  history_pixels = None
423
  history_latents = None
424
  total_generated_latent_frames = 0
@@ -588,7 +660,14 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
588
  try:
589
  output_filename = os.path.join(outputs_folder, f'{job_id}_final_{total_generated_latent_frames}.mp4')
590
  save_bcthw_as_mp4(history_pixels, output_filename, fps=30, crf=18)
591
- stream.output_queue.push(('file', output_filename))
 
 
 
 
 
 
 
592
  except Exception as e:
593
  print(f"最終動画保存中にエラーが発生しました: {e}")
594
 
@@ -611,7 +690,7 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
611
  traceback.print_exc()
612
  # 完全に終了せずに次のイテレーションを試みる
613
  if last_output_filename:
614
- stream.output_queue.push(('file', last_output_filename))
615
  continue
616
 
617
  if not high_vram and not cpu_fallback_mode:
@@ -724,7 +803,7 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
724
  # 既に生成された動画がある場合、最後に生成された動画を返す
725
  if last_output_filename:
726
  print(f"【デバッグ】部分的に生成された動画あり: {last_output_filename}、この動画を返します")
727
- stream.output_queue.push(('file', last_output_filename))
728
  error_msg = "ユーザーにより生成プロセスが中断されましたが、部分的な動画は生成されています"
729
  else:
730
  print("【デバッグ】部分的に生成された動画なし、中断メッセージを返します")
@@ -742,7 +821,7 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
742
 
743
  # 既に生成された動画がある場合、最後に生成された動画を返す
744
  if last_output_filename:
745
- stream.output_queue.push(('file', last_output_filename))
746
 
747
  # エラーメッセージを作成
748
  error_msg = f"サンプリングプロセス中にエラーが発生しましたが、部分的に生成された動画を返します: {e}"
@@ -767,7 +846,7 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
767
  traceback.print_exc()
768
 
769
  if last_output_filename:
770
- stream.output_queue.push(('file', last_output_filename))
771
  stream.output_queue.push(('error', error_msg))
772
  stream.output_queue.push(('end', None))
773
  return
@@ -787,7 +866,7 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
787
  print(error_msg)
788
 
789
  if last_output_filename:
790
- stream.output_queue.push(('file', last_output_filename))
791
  continue
792
 
793
  try:
@@ -816,18 +895,24 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
816
  save_start_time = time.time()
817
  save_bcthw_as_mp4(history_pixels, output_filename, fps=30, crf=18)
818
  print(f"動画保存完了、所要時間: {time.time() - save_start_time:.2f}秒")
819
-
 
 
 
 
820
  print(f'デコード完了。現在の潜在変数形状 {real_history_latents.shape}; ピクセル形状 {history_pixels.shape}')
821
 
822
  last_output_filename = output_filename
823
- stream.output_queue.push(('file', output_filename))
 
 
824
  except Exception as e:
825
  print(f"動画のデコードまたは保存中にエラーが発生しました: {e}")
826
  traceback.print_exc()
827
 
828
  # 既に生成された動画がある場合、最後に生成された動画を返す
829
  if last_output_filename:
830
- stream.output_queue.push(('file', last_output_filename))
831
 
832
  # エラー情報を記録
833
  error_msg = f"動画のデコードまたは保存中にエラーが発生しました: {e}"
@@ -861,7 +946,7 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
861
  # 既に生成された動画がある場合、最後に生成された動画を返す
862
  if last_output_filename:
863
  print(f"【デバッグ】外部例外処理: 生成済み部分動画を返す {last_output_filename}")
864
- stream.output_queue.push(('file', last_output_filename))
865
  else:
866
  print("【デバッグ】外部例外処理: 生成済み動画が見つかりません")
867
 
@@ -891,8 +976,8 @@ if IN_HF_SPACE and 'spaces' in globals():
891
  gpu_memory_preservation = 6
892
 
893
 
894
- # UI状態の初期化
895
- yield None, None, '', '', gr.update(interactive=False), gr.update(interactive=True)
896
 
897
  try:
898
  stream = AsyncStream()
@@ -901,7 +986,9 @@ if IN_HF_SPACE and 'spaces' in globals():
901
  async_run(worker, input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache)
902
 
903
  output_filename = None
 
904
  prev_output_filename = None
 
905
  error_message = None
906
 
907
  # ワーカーの出力を継続的にチェック
@@ -910,15 +997,17 @@ if IN_HF_SPACE and 'spaces' in globals():
910
  flag, data = stream.output_queue.next()
911
 
912
  if flag == 'file':
913
- output_filename = data
 
914
  prev_output_filename = output_filename
915
- # ファイル成功時にエラー表示をクリア
916
- yield output_filename, gr.update(), gr.update(), '', gr.update(interactive=False), gr.update(interactive=True)
 
917
 
918
  if flag == 'progress':
919
  preview, desc, html = data
920
  # 進捗更新時にエラーメッセージを変更せず、停止ボタンがインタラクティブであることを確認
921
- yield gr.update(), gr.update(visible=True, value=preview), desc, html, gr.update(interactive=False), gr.update(interactive=True)
922
 
923
  if flag == 'error':
924
  error_message = data
@@ -926,16 +1015,17 @@ if IN_HF_SPACE and 'spaces' in globals():
926
  # 即時表示せず、end信号を待機
927
 
928
  if flag == 'end':
929
- # 最後の動画ファイルがある場合、確実に返す
930
  if output_filename is None and prev_output_filename is not None:
931
  output_filename = prev_output_filename
 
932
 
933
  # エラーメッセージがある場合、わかりやすいエラー表示を作成
934
  if error_message:
935
- yield output_filename, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
936
  else:
937
  # 成功時にエラー表示をしない
938
- yield output_filename, gr.update(visible=False), gr.update(), '', gr.update(interactive=True), gr.update(interactive=False)
939
  break
940
  except Exception as e:
941
  print(f"出力処理中にエラーが発生しました: {e}")
@@ -944,11 +1034,11 @@ if IN_HF_SPACE and 'spaces' in globals():
944
  if current_time - last_update_time > 60: # 60秒間更新がない場合、処理がフリーズした可能性
945
  print(f"処理がフリーズした可能性があります。{current_time - last_update_time:.1f}秒間更新がありません")
946
 
947
- # 部分的に生成された動画がある場合、それを返す
948
  if prev_output_filename:
949
- yield prev_output_filename, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
950
  else:
951
- yield None, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
952
  break
953
 
954
  except Exception as e:
@@ -956,7 +1046,7 @@ if IN_HF_SPACE and 'spaces' in globals():
956
  traceback.print_exc()
957
  error_msg = str(e)
958
 
959
- yield None, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
960
 
961
  process = process_with_gpu
962
  else:
@@ -971,8 +1061,8 @@ else:
971
  rs = 0.0
972
  gpu_memory_preservation = 6
973
 
974
- # UI状態の初期化
975
- yield None, None, '', '', gr.update(interactive=False), gr.update(interactive=True)
976
 
977
  try:
978
  stream = AsyncStream()
@@ -981,7 +1071,9 @@ else:
981
  async_run(worker, input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache)
982
 
983
  output_filename = None
 
984
  prev_output_filename = None
 
985
  error_message = None
986
 
987
  # ワーカーの出力を継続的にチェック
@@ -990,15 +1082,17 @@ else:
990
  flag, data = stream.output_queue.next()
991
 
992
  if flag == 'file':
993
- output_filename = data
 
994
  prev_output_filename = output_filename
995
- # ファイル成功時にエラー表示をクリア
996
- yield output_filename, gr.update(), gr.update(), '', gr.update(interactive=False), gr.update(interactive=True)
 
997
 
998
  if flag == 'progress':
999
  preview, desc, html = data
1000
- # 進捗更新時にエラーメッセージを変更せず、停止ボタンがインタラクティブであることを確認
1001
- yield gr.update(), gr.update(visible=True, value=preview), desc, html, gr.update(interactive=False), gr.update(interactive=True)
1002
 
1003
  if flag == 'error':
1004
  error_message = data
@@ -1006,16 +1100,17 @@ else:
1006
  # 即時表示せず、end信号を待機
1007
 
1008
  if flag == 'end':
1009
- # 最後の動画ファイルがある場合、確実に返す
1010
  if output_filename is None and prev_output_filename is not None:
1011
  output_filename = prev_output_filename
 
1012
 
1013
  # エラーメッセージがある場合、わかりやすいエラー表示を作成
1014
  if error_message:
1015
- yield output_filename, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
1016
  else:
1017
  # 成功時にエラー表示をしない
1018
- yield output_filename, gr.update(visible=False), gr.update(), '', gr.update(interactive=True), gr.update(interactive=False)
1019
  break
1020
  except Exception as e:
1021
  print(f"出力処理中にエラーが発生しました: {e}")
@@ -1024,11 +1119,11 @@ else:
1024
  if current_time - last_update_time > 60: # 60秒間更新がない場合、処理がフリーズした可能性
1025
  print(f"処理がフリーズした可能性があります。{current_time - last_update_time:.1f}秒間更新がありません")
1026
 
1027
- # 部分的に生成された動画がある場合、それを返す
1028
  if prev_output_filename:
1029
- yield prev_output_filename, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
1030
  else:
1031
- yield None, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
1032
  break
1033
 
1034
  except Exception as e:
@@ -1036,7 +1131,7 @@ else:
1036
  traceback.print_exc()
1037
  error_msg = str(e)
1038
 
1039
- yield None, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
1040
 
1041
 
1042
  def end_process():
@@ -1219,6 +1314,32 @@ def make_custom_css():
1219
  .error {
1220
  display: none !important;
1221
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1222
  """
1223
 
1224
  # CSSを結合
@@ -1285,16 +1406,16 @@ with block:
1285
 
1286
  seed = gr.Number(
1287
  label="シード値 / Seed",
1288
- value=31337,
1289
  precision=0
1290
  )
1291
 
1292
  # タッチ操作を最適化するためにslider-containerクラスを追加
1293
  with gr.Group(elem_classes="slider-container"):
1294
  total_second_length = gr.Slider(
1295
- label="動画の長さ(最大3秒) / Video Length (max 3 seconds)",
1296
- minimum=0.5,
1297
- maximum=3,
1298
  value=1,
1299
  step=0.1
1300
  )
@@ -1309,16 +1430,27 @@ with block:
1309
  elem_classes="preview-container"
1310
  )
1311
 
1312
- # 動画結果コンテナ
1313
- result_video = gr.Video(
1314
- label="生成された動画 / Generated Video",
1315
- autoplay=True,
1316
- show_share_button=True, # 共有ボタンを追加
1317
- height=512,
1318
- loop=True,
1319
- elem_classes="video-container",
1320
- elem_id="result-video"
1321
- )
 
 
 
 
 
 
 
 
 
 
 
1322
 
1323
  gr.HTML("<div ='sampling_note' class='note'>注意:逆順サンプリングのため、終了動作が開始動作より先に生成されます。開始動作が動画に表示されていない場合は、しばらくお待ちください。後で生成されます。</div>")
1324
  gr.HTML("<div ='sampling_note' class='note'>Note that the ending actions will be generated before the starting actions due to the inverted sampling. If the starting action is not in the video, you just need to wait, and it will be generated later.</div>")
@@ -1328,16 +1460,16 @@ with block:
1328
  progress_desc = gr.Markdown('', elem_classes='no-generating-animation')
1329
  progress_bar = gr.HTML('', elem_classes='no-generating-animation')
1330
 
1331
- # エラーメッセージエリア - カスタムエラーメッセージ形式をサポートするHTMLコンポーネントを使用
1332
  error_message = gr.HTML('', elem_id='error-message', visible=True)
1333
 
1334
- # 処理関数
1335
  ips = [input_image, prompt, n_prompt, seed, total_second_length, use_teacache]
 
1336
 
1337
  # 開始と終了ボタンのイベント
1338
- start_button.click(fn=process, inputs=ips, outputs=[result_video, preview_image, progress_desc, progress_bar, start_button, end_button])
1339
  end_button.click(fn=end_process)
1340
 
1341
 
1342
- block.launch()
1343
-
 
7
  from requests.adapters import HTTPAdapter
8
  from urllib3.util.retry import Retry
9
  import json
10
+ import subprocess # FFmpeg実行用に追加
11
 
12
  os.environ['HF_HOME'] = os.path.abspath(os.path.realpath(os.path.join(os.path.dirname(__file__), './hf_download')))
13
 
 
110
  models = {}
111
  cpu_fallback_mode = not GPU_AVAILABLE # GPUが利用できない場合、CPU代替モードを使用
112
 
113
+ # 最後のフレームを抽出する関数を追加
114
+ def extract_last_frame(video_path, output_path):
115
+ """
116
+ FFmpegを使用して動画の最後のフレームを抽出します
117
+
118
+ Args:
119
+ video_path (str): 対象動画のパス
120
+ output_path (str): 出力画像のパス
121
+
122
+ Returns:
123
+ bool: 抽出に成功した場合はTrue、失敗した場合はFalse
124
+ """
125
+ try:
126
+ # 動画からフレーム数を取得するコマンド
127
+ probe_cmd = [
128
+ 'ffprobe',
129
+ '-v', 'error',
130
+ '-select_streams', 'v:0',
131
+ '-count_packets',
132
+ '-show_entries', 'stream=nb_read_packets',
133
+ '-of', 'csv=p=0',
134
+ video_path
135
+ ]
136
+
137
+ # コマンドを実行してフレーム数を取得
138
+ frame_count = int(subprocess.check_output(probe_cmd).decode('utf-8').strip())
139
+
140
+ # 最後のフレームを抽出するコマンド(0から始まるので-1する)
141
+ extract_cmd = [
142
+ 'ffmpeg',
143
+ '-y', # 既存ファイルを上書き
144
+ '-i', video_path,
145
+ '-vf', f'select=eq(n\\,{frame_count-1})',
146
+ '-vframes', '1',
147
+ output_path
148
+ ]
149
+
150
+ # コマンドを実行
151
+ subprocess.run(extract_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
152
+
153
+ # ファイルが生成されたか確認
154
+ if os.path.exists(output_path):
155
+ print(f"最後のフレームを抽出しました: {output_path}")
156
+ return True
157
+ else:
158
+ print(f"最後のフレーム抽出に失敗しました")
159
+ return False
160
+ except Exception as e:
161
+ print(f"フレーム抽出中にエラーが発生しました: {e}")
162
+ # 別の方法を試す(単純にシーク)
163
+ try:
164
+ simple_extract_cmd = [
165
+ 'ffmpeg',
166
+ '-y',
167
+ '-sseof', '-1', # 最後のフレームにシーク
168
+ '-i', video_path,
169
+ '-update', '1',
170
+ '-q:v', '1',
171
+ output_path
172
+ ]
173
+ subprocess.run(simple_extract_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
174
+
175
+ if os.path.exists(output_path):
176
+ print(f"代替方法で最後のフレームを抽出しました: {output_path}")
177
+ return True
178
+ except Exception as e2:
179
+ print(f"代替フレーム抽出方法でもエラーが発生しました: {e2}")
180
+
181
+ return False
182
+
183
  # モデルロード関数を使用
184
  def load_models():
185
  global models, cpu_fallback_mode, GPU_INITIALIZED
 
490
 
491
  job_id = generate_timestamp()
492
  last_output_filename = None
493
+ last_frame_filename = None
494
  history_pixels = None
495
  history_latents = None
496
  total_generated_latent_frames = 0
 
660
  try:
661
  output_filename = os.path.join(outputs_folder, f'{job_id}_final_{total_generated_latent_frames}.mp4')
662
  save_bcthw_as_mp4(history_pixels, output_filename, fps=30, crf=18)
663
+
664
+ # 最後のフレームを抽出して保存
665
+ last_frame_filename = os.path.join(outputs_folder, f'{job_id}_final_{total_generated_latent_frames}_last_frame.png')
666
+ extract_success = extract_last_frame(output_filename, last_frame_filename)
667
+
668
+ # 抽出成功したかどうかに応じたファイル名を送信
669
+ last_frame_path = last_frame_filename if extract_success else None
670
+ stream.output_queue.push(('file', (output_filename, last_frame_path)))
671
  except Exception as e:
672
  print(f"最終動画保存中にエラーが発生しました: {e}")
673
 
 
690
  traceback.print_exc()
691
  # 完全に終了せずに次のイテレーションを試みる
692
  if last_output_filename:
693
+ stream.output_queue.push(('file', (last_output_filename, last_frame_filename)))
694
  continue
695
 
696
  if not high_vram and not cpu_fallback_mode:
 
803
  # 既に生成された動画がある場合、最後に生成された動画を返す
804
  if last_output_filename:
805
  print(f"【デバッグ】部分的に生成された動画あり: {last_output_filename}、この動画を返します")
806
+ stream.output_queue.push(('file', (last_output_filename, last_frame_filename)))
807
  error_msg = "ユーザーにより生成プロセスが中断されましたが、部分的な動画は生成されています"
808
  else:
809
  print("【デバッグ】部分的に生成された動画なし、中断メッセージを返します")
 
821
 
822
  # 既に生成された動画がある場合、最後に生成された動画を返す
823
  if last_output_filename:
824
+ stream.output_queue.push(('file', (last_output_filename, last_frame_filename)))
825
 
826
  # エラーメッセージを作成
827
  error_msg = f"サンプリングプロセス中にエラーが発生しましたが、部分的に生成された動画を返します: {e}"
 
846
  traceback.print_exc()
847
 
848
  if last_output_filename:
849
+ stream.output_queue.push(('file', (last_output_filename, last_frame_filename)))
850
  stream.output_queue.push(('error', error_msg))
851
  stream.output_queue.push(('end', None))
852
  return
 
866
  print(error_msg)
867
 
868
  if last_output_filename:
869
+ stream.output_queue.push(('file', (last_output_filename, last_frame_filename)))
870
  continue
871
 
872
  try:
 
895
  save_start_time = time.time()
896
  save_bcthw_as_mp4(history_pixels, output_filename, fps=30, crf=18)
897
  print(f"動画保存完了、所要時間: {time.time() - save_start_time:.2f}秒")
898
+
899
+ # 最後のフレームを抽出して保存
900
+ last_frame_filename = os.path.join(outputs_folder, f'{job_id}_{total_generated_latent_frames}_last_frame.png')
901
+ extract_success = extract_last_frame(output_filename, last_frame_filename)
902
+
903
  print(f'デコード完了。現在の潜在変数形状 {real_history_latents.shape}; ピクセル形状 {history_pixels.shape}')
904
 
905
  last_output_filename = output_filename
906
+ # 抽出成功したらファイル名も送信、失敗した場合はNoneを送信
907
+ last_frame_path = last_frame_filename if extract_success else None
908
+ stream.output_queue.push(('file', (output_filename, last_frame_path)))
909
  except Exception as e:
910
  print(f"動画のデコードまたは保存中にエラーが発生しました: {e}")
911
  traceback.print_exc()
912
 
913
  # 既に生成された動画がある場合、最後に生成された動画を返す
914
  if last_output_filename:
915
+ stream.output_queue.push(('file', (last_output_filename, last_frame_filename)))
916
 
917
  # エラー情報を記録
918
  error_msg = f"動画のデコードまたは保存中にエラーが発生しました: {e}"
 
946
  # 既に生成された動画がある場合、最後に生成された動画を返す
947
  if last_output_filename:
948
  print(f"【デバッグ】外部例外処理: 生成済み部分動画を返す {last_output_filename}")
949
+ stream.output_queue.push(('file', (last_output_filename, last_frame_filename)))
950
  else:
951
  print("【デバッグ】外部例外処理: 生成済み動画が見つかりません")
952
 
 
976
  gpu_memory_preservation = 6
977
 
978
 
979
+ # UI状態の初期化 - last_frame_image用の更新を追加
980
+ yield None, None, None, '', '', gr.update(interactive=False), gr.update(interactive=True)
981
 
982
  try:
983
  stream = AsyncStream()
 
986
  async_run(worker, input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache)
987
 
988
  output_filename = None
989
+ last_frame_filename = None
990
  prev_output_filename = None
991
+ prev_last_frame_filename = None
992
  error_message = None
993
 
994
  # ワーカーの出力を継続的にチェック
 
997
  flag, data = stream.output_queue.next()
998
 
999
  if flag == 'file':
1000
+ # data形式が(動画パス, 最後のフレームパス)に変更
1001
+ output_filename, last_frame_filename = data
1002
  prev_output_filename = output_filename
1003
+ prev_last_frame_filename = last_frame_filename
1004
+ # ファイル成功時に出力を更新
1005
+ yield output_filename, last_frame_filename, gr.update(), gr.update(), '', gr.update(interactive=False), gr.update(interactive=True)
1006
 
1007
  if flag == 'progress':
1008
  preview, desc, html = data
1009
  # 進捗更新時にエラーメッセージを変更せず、停止ボタンがインタラクティブであることを確認
1010
+ yield gr.update(), gr.update(), gr.update(visible=True, value=preview), desc, html, gr.update(interactive=False), gr.update(interactive=True)
1011
 
1012
  if flag == 'error':
1013
  error_message = data
 
1015
  # 即時表示せず、end信号を待機
1016
 
1017
  if flag == 'end':
1018
+ # 最後の動画ファイルと最後のフレーム画像がある場合、確実に返す
1019
  if output_filename is None and prev_output_filename is not None:
1020
  output_filename = prev_output_filename
1021
+ last_frame_filename = prev_last_frame_filename
1022
 
1023
  # エラーメッセージがある場合、わかりやすいエラー表示を作成
1024
  if error_message:
1025
+ yield output_filename, last_frame_filename, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
1026
  else:
1027
  # 成功時にエラー表示をしない
1028
+ yield output_filename, last_frame_filename, gr.update(visible=False), gr.update(), '', gr.update(interactive=True), gr.update(interactive=False)
1029
  break
1030
  except Exception as e:
1031
  print(f"出力処理中にエラーが発生しました: {e}")
 
1034
  if current_time - last_update_time > 60: # 60秒間更新がない場合、処理がフリーズした可能性
1035
  print(f"処理がフリーズした可能性があります。{current_time - last_update_time:.1f}秒間更新がありません")
1036
 
1037
+ # 部分的に生成された動画と最後のフレーム画像がある場合、それを返す
1038
  if prev_output_filename:
1039
+ yield prev_output_filename, prev_last_frame_filename, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
1040
  else:
1041
+ yield None, None, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
1042
  break
1043
 
1044
  except Exception as e:
 
1046
  traceback.print_exc()
1047
  error_msg = str(e)
1048
 
1049
+ yield None, None, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
1050
 
1051
  process = process_with_gpu
1052
  else:
 
1061
  rs = 0.0
1062
  gpu_memory_preservation = 6
1063
 
1064
+ # UI状態の初期化 - last_frame_image用の更新を追加
1065
+ yield None, None, None, '', '', gr.update(interactive=False), gr.update(interactive=True)
1066
 
1067
  try:
1068
  stream = AsyncStream()
 
1071
  async_run(worker, input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache)
1072
 
1073
  output_filename = None
1074
+ last_frame_filename = None
1075
  prev_output_filename = None
1076
+ prev_last_frame_filename = None
1077
  error_message = None
1078
 
1079
  # ワーカーの出力を継続的にチェック
 
1082
  flag, data = stream.output_queue.next()
1083
 
1084
  if flag == 'file':
1085
+ # data形式が(動画パス, 最後のフレームパス)に変更
1086
+ output_filename, last_frame_filename = data
1087
  prev_output_filename = output_filename
1088
+ prev_last_frame_filename = last_frame_filename
1089
+ # ファイル成功時の出力更新
1090
+ yield output_filename, last_frame_filename, gr.update(), gr.update(), '', gr.update(interactive=False), gr.update(interactive=True)
1091
 
1092
  if flag == 'progress':
1093
  preview, desc, html = data
1094
+ # 進捗更新時
1095
+ yield gr.update(), gr.update(), gr.update(visible=True, value=preview), desc, html, gr.update(interactive=False), gr.update(interactive=True)
1096
 
1097
  if flag == 'error':
1098
  error_message = data
 
1100
  # 即時表示せず、end信号を待機
1101
 
1102
  if flag == 'end':
1103
+ # 最後の動画ファイルと最後のフレーム画像がある場合、確実に返す
1104
  if output_filename is None and prev_output_filename is not None:
1105
  output_filename = prev_output_filename
1106
+ last_frame_filename = prev_last_frame_filename
1107
 
1108
  # エラーメッセージがある場合、わかりやすいエラー表示を作成
1109
  if error_message:
1110
+ yield output_filename, last_frame_filename, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
1111
  else:
1112
  # 成功時にエラー表示をしない
1113
+ yield output_filename, last_frame_filename, gr.update(visible=False), gr.update(), '', gr.update(interactive=True), gr.update(interactive=False)
1114
  break
1115
  except Exception as e:
1116
  print(f"出力処理中にエラーが発生しました: {e}")
 
1119
  if current_time - last_update_time > 60: # 60秒間更新がない場合、処理がフリーズした可能性
1120
  print(f"処理がフリーズした可能性があります。{current_time - last_update_time:.1f}秒間更新がありません")
1121
 
1122
+ # 部分的に生成された動画と最後のフレーム画像がある場合、それを返す
1123
  if prev_output_filename:
1124
+ yield prev_output_filename, prev_last_frame_filename, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
1125
  else:
1126
+ yield None, None, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
1127
  break
1128
 
1129
  except Exception as e:
 
1131
  traceback.print_exc()
1132
  error_msg = str(e)
1133
 
1134
+ yield None, None, gr.update(visible=False), gr.update(), gr.update(interactive=True), gr.update(interactive=False)
1135
 
1136
 
1137
  def end_process():
 
1314
  .error {
1315
  display: none !important;
1316
  }
1317
+
1318
+ /* 最後のフレーム表示エリアのスタイル */
1319
+ .last-frame-container {
1320
+ margin-top: 15px;
1321
+ border: 1px solid #ddd;
1322
+ border-radius: 8px;
1323
+ overflow: hidden;
1324
+ }
1325
+
1326
+ /* 最後のフレームとビデオの並べ表示(大画面用) */
1327
+ @media (min-width: 1024px) {
1328
+ .media-container {
1329
+ display: flex;
1330
+ flex-direction: row;
1331
+ gap: 15px;
1332
+ }
1333
+
1334
+ .video-container {
1335
+ flex: 2;
1336
+ }
1337
+
1338
+ .last-frame-container {
1339
+ flex: 1;
1340
+ max-width: 320px;
1341
+ }
1342
+ }
1343
  """
1344
 
1345
  # CSSを結合
 
1406
 
1407
  seed = gr.Number(
1408
  label="シード値 / Seed",
1409
+ value=1234,
1410
  precision=0
1411
  )
1412
 
1413
  # タッチ操作を最適化するためにslider-containerクラスを追加
1414
  with gr.Group(elem_classes="slider-container"):
1415
  total_second_length = gr.Slider(
1416
+ label="動画の長さ(最大3秒) / Video Length (max 1.5 seconds)",
1417
+ minimum=1.0,
1418
+ maximum=1.5,
1419
  value=1,
1420
  step=0.1
1421
  )
 
1430
  elem_classes="preview-container"
1431
  )
1432
 
1433
+ # media-containerクラスを追加して動画と最後のフレームを並べて表示(大画面用)
1434
+ with gr.Group(elem_classes="media-container"):
1435
+ # 動画結果コンテナ
1436
+ result_video = gr.Video(
1437
+ label="生成された動画 / Generated Video",
1438
+ autoplay=True,
1439
+ show_share_button=True,
1440
+ height=512,
1441
+ loop=True,
1442
+ elem_classes="video-container",
1443
+ elem_id="result-video"
1444
+ )
1445
+
1446
+ # 新規追加:最後のフレーム表示用のImage要素
1447
+ last_frame_image = gr.Image(
1448
+ label="最後のフレーム / Last Frame",
1449
+ height=320,
1450
+ show_share_button=True,
1451
+ elem_classes="last-frame-container",
1452
+ elem_id="last-frame-image"
1453
+ )
1454
 
1455
  gr.HTML("<div ='sampling_note' class='note'>注意:逆順サンプリングのため、終了動作が開始動作より先に生成されます。開始動作が動画に表示されていない場合は、しばらくお待ちください。後で生成されます。</div>")
1456
  gr.HTML("<div ='sampling_note' class='note'>Note that the ending actions will be generated before the starting actions due to the inverted sampling. If the starting action is not in the video, you just need to wait, and it will be generated later.</div>")
 
1460
  progress_desc = gr.Markdown('', elem_classes='no-generating-animation')
1461
  progress_bar = gr.HTML('', elem_classes='no-generating-animation')
1462
 
1463
+ # エラーメッセージエリア
1464
  error_message = gr.HTML('', elem_id='error-message', visible=True)
1465
 
1466
+ # 処理関数の入出力を更新
1467
  ips = [input_image, prompt, n_prompt, seed, total_second_length, use_teacache]
1468
+ ops = [result_video, last_frame_image, preview_image, progress_desc, progress_bar, start_button, end_button]
1469
 
1470
  # 開始と終了ボタンのイベント
1471
+ start_button.click(fn=process, inputs=ips, outputs=ops)
1472
  end_button.click(fn=end_process)
1473
 
1474
 
1475
+ block.launch()