ginipick commited on
Commit
17a13c7
·
verified ·
1 Parent(s): 08b28a3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +307 -507
app.py CHANGED
@@ -10,7 +10,7 @@ import json
10
 
11
  os.environ['HF_HOME'] = os.path.abspath(os.path.realpath(os.path.join(os.path.dirname(__file__), './hf_download')))
12
 
13
- # 添加中英双语翻译字典
14
  translations = {
15
  "en": {
16
  "title": "FramePack - Image to Video Generation",
@@ -44,54 +44,54 @@ translations = {
44
  "partial_video": "Processing error, but partial video has been generated",
45
  "processing_interrupt": "Processing was interrupted, but partial video has been generated"
46
  },
47
- "zh": {
48
- "title": "FramePack - 图像到视频生成",
49
- "upload_image": "上传图像",
50
- "prompt": "提示词",
51
- "quick_prompts": "快速提示词列表",
52
- "start_generation": "开始生成",
53
- "stop_generation": "结束生成",
54
- "use_teacache": "使用TeaCache",
55
- "teacache_info": "速度更快,但可能会使手指和手的生成效果稍差。",
56
- "negative_prompt": "负面提示词",
57
- "seed": "随机种子",
58
- "video_length": "视频长度(最大5)",
59
- "latent_window": "潜在窗口大小",
60
- "steps": "推理步数",
61
- "steps_info": "不建议修改此值。",
62
- "cfg_scale": "CFG Scale",
63
- "distilled_cfg": "蒸馏CFG比例",
64
- "distilled_cfg_info": "不建议修改此值。",
65
- "cfg_rescale": "CFG重缩放",
66
- "gpu_memory": "GPU推理保留内存(GB)(值越大速度越慢)",
67
- "gpu_memory_info": "如果出现OOM错误,请将此值设置得更大。值越大,速度越慢。",
68
- "next_latents": "下一批潜变量",
69
- "generated_video": "生成的视频",
70
- "sampling_note": "注意:由于采样是倒序的,结束动作将在开始动作之前生成。如果视频中没有出现起始动作,请继续等待,它将在稍后生成。",
71
- "error_message": "错误信息",
72
- "processing_error": "处理过程出错",
73
- "network_error": "网络连接不稳定,模型下载超时。请稍后再试。",
74
- "memory_error": "GPU内存不足,请尝试增加GPU推理保留内存值或降低视频长度。",
75
- "model_error": "模型加载失败,可能是网络问题或服务器负载过高。请稍后再试。",
76
- "partial_video": "处理过程中出现错误,但已生成部分视频",
77
- "processing_interrupt": "处理过程中断,但已生成部分视频"
78
  }
79
  }
80
 
81
- # 语言切换功能
82
  def get_translation(key, lang="en"):
83
  if lang in translations and key in translations[lang]:
84
  return translations[lang][key]
85
- # 默认返回英文
86
  return translations["en"].get(key, key)
87
 
88
- # 默认语言设置
89
  current_language = "en"
90
 
91
- # 切换语言函数
92
  def switch_language():
93
  global current_language
94
- current_language = "zh" if current_language == "en" else "en"
95
  return current_language
96
 
97
  import gradio as gr
@@ -102,21 +102,21 @@ import safetensors.torch as sf
102
  import numpy as np
103
  import math
104
 
105
- # 检查是否在Hugging Face Space环境中
106
  IN_HF_SPACE = os.environ.get('SPACE_ID') is not None
107
 
108
- # 添加变量跟踪GPU可用性
109
  GPU_AVAILABLE = False
110
  GPU_INITIALIZED = False
111
  last_update_time = time.time()
112
 
113
- # 如果在Hugging Face Space中,导入spaces模块
114
  if IN_HF_SPACE:
115
  try:
116
  import spaces
117
- print("Hugging Face Space环境中运行,已导入spaces模块")
118
 
119
- # 检查GPU可用性
120
  try:
121
  GPU_AVAILABLE = torch.cuda.is_available()
122
  print(f"GPU available: {GPU_AVAILABLE}")
@@ -124,19 +124,19 @@ if IN_HF_SPACE:
124
  print(f"GPU device name: {torch.cuda.get_device_name(0)}")
125
  print(f"GPU memory: {torch.cuda.get_device_properties(0).total_memory / 1e9} GB")
126
 
127
- # 尝试进行小型GPU操作,确认GPU实际可用
128
  test_tensor = torch.zeros(1, device='cuda')
129
  test_tensor = test_tensor + 1
130
  del test_tensor
131
- print("成功进行GPU测试操作")
132
  else:
133
- print("警告: CUDA报告可用,但未检测到GPU设备")
134
  except Exception as e:
135
  GPU_AVAILABLE = False
136
- print(f"检查GPU时出错: {e}")
137
- print("将使用CPU模式运行")
138
  except ImportError:
139
- print("未能导入spaces模块,可能不在Hugging Face Space环境中")
140
  GPU_AVAILABLE = torch.cuda.is_available()
141
 
142
  from PIL import Image
@@ -156,65 +156,61 @@ from diffusers_helper.bucket_tools import find_nearest_bucket
156
  outputs_folder = './outputs/'
157
  os.makedirs(outputs_folder, exist_ok=True)
158
 
159
- # Spaces环境中,我们延迟所有CUDA操作
160
  if not IN_HF_SPACE:
161
- # 仅在非Spaces环境中获取CUDA内存
162
  try:
163
  if torch.cuda.is_available():
164
  free_mem_gb = get_cuda_free_memory_gb(gpu)
165
- print(f'Free VRAM {free_mem_gb} GB')
166
  else:
167
- free_mem_gb = 6.0 # 默认值
168
- print("CUDA不可用,使用默认的内存设置")
169
  except Exception as e:
170
- free_mem_gb = 6.0 # 默认值
171
- print(f"获取CUDA内存时出错: {e},使用默认的内存设置")
172
 
173
  high_vram = free_mem_gb > 60
174
- print(f'High-VRAM Mode: {high_vram}')
175
  else:
176
- # Spaces环境中使用默认值
177
- print("Spaces环境中使用默认内存设置")
178
  try:
179
  if GPU_AVAILABLE:
180
- free_mem_gb = torch.cuda.get_device_properties(0).total_memory / 1e9 * 0.9 # 使用90%的GPU内存
181
- high_vram = free_mem_gb > 10 # 更保守的条件
182
  else:
183
- free_mem_gb = 6.0 # 默认值
184
  high_vram = False
185
  except Exception as e:
186
- print(f"获取GPU内存时出错: {e}")
187
- free_mem_gb = 6.0 # 默认值
188
  high_vram = False
189
 
190
- print(f'GPU内存: {free_mem_gb:.2f} GB, High-VRAM Mode: {high_vram}')
191
 
192
- # 使用models变量存储全局模型引用
193
  models = {}
194
- cpu_fallback_mode = not GPU_AVAILABLE # 如果GPU不可用,使用CPU回退模式
195
 
196
- # 使用加载模型的函数
197
  def load_models():
198
  global models, cpu_fallback_mode, GPU_INITIALIZED
199
 
200
  if GPU_INITIALIZED:
201
- print("模型已加载,跳过重复加载")
202
  return models
203
 
204
- print("开始加载模型...")
205
-
206
  try:
207
- # 设置设备,根据GPU可用性确定
208
  device = 'cuda' if GPU_AVAILABLE and not cpu_fallback_mode else 'cpu'
209
- model_device = 'cpu' # 初始加载到CPU
210
-
211
- # 降低精度以节省内存
212
  dtype = torch.float16 if GPU_AVAILABLE else torch.float32
213
  transformer_dtype = torch.bfloat16 if GPU_AVAILABLE else torch.float32
214
 
215
- print(f"使用设备: {device}, 模型精度: {dtype}, Transformer精度: {transformer_dtype}")
216
 
217
- # 加载模型
218
  try:
219
  text_encoder = LlamaModel.from_pretrained("hunyuanvideo-community/HunyuanVideo", subfolder='text_encoder', torch_dtype=dtype).to(model_device)
220
  text_encoder_2 = CLIPTextModel.from_pretrained("hunyuanvideo-community/HunyuanVideo", subfolder='text_encoder_2', torch_dtype=dtype).to(model_device)
@@ -227,12 +223,11 @@ def load_models():
227
 
228
  transformer = HunyuanVideoTransformer3DModelPacked.from_pretrained('lllyasviel/FramePackI2V_HY', torch_dtype=transformer_dtype).to(model_device)
229
 
230
- print("成功加载所有模型")
231
  except Exception as e:
232
- print(f"加载模型时出错: {e}")
233
- print("尝试降低精度重新加载...")
234
-
235
- # 降低精度重试
236
  dtype = torch.float32
237
  transformer_dtype = torch.float32
238
  cpu_fallback_mode = True
@@ -248,7 +243,7 @@ def load_models():
248
 
249
  transformer = HunyuanVideoTransformer3DModelPacked.from_pretrained('lllyasviel/FramePackI2V_HY', torch_dtype=transformer_dtype).to('cpu')
250
 
251
- print("使用CPU模式成功加载所有模型")
252
 
253
  vae.eval()
254
  text_encoder.eval()
@@ -263,7 +258,6 @@ def load_models():
263
  transformer.high_quality_fp32_output_for_inference = True
264
  print('transformer.high_quality_fp32_output_for_inference = True')
265
 
266
- # 设置模型精度
267
  if not cpu_fallback_mode:
268
  transformer.to(dtype=transformer_dtype)
269
  vae.to(dtype=dtype)
@@ -280,7 +274,7 @@ def load_models():
280
  if torch.cuda.is_available() and not cpu_fallback_mode:
281
  try:
282
  if not high_vram:
283
- # DynamicSwapInstaller is same as huggingface's enable_sequential_offload but 3x faster
284
  DynamicSwapInstaller.install_model(transformer, device=device)
285
  DynamicSwapInstaller.install_model(text_encoder, device=device)
286
  else:
@@ -289,14 +283,13 @@ def load_models():
289
  image_encoder.to(device)
290
  vae.to(device)
291
  transformer.to(device)
292
- print(f"成功将模型移动到{device}设备")
293
  except Exception as e:
294
- print(f"移动模型到{device}时出错: {e}")
295
- print("回退到CPU模式")
296
  cpu_fallback_mode = True
297
-
298
- # 保存到全局变量
299
- models = {
300
  'text_encoder': text_encoder,
301
  'text_encoder_2': text_encoder_2,
302
  'tokenizer': tokenizer,
@@ -308,13 +301,13 @@ def load_models():
308
  }
309
 
310
  GPU_INITIALIZED = True
311
- print(f"模型加载完成,运行模式: {'CPU' if cpu_fallback_mode else 'GPU'}")
 
312
  return models
313
  except Exception as e:
314
- print(f"加载模型过程中发生错误: {e}")
315
  traceback.print_exc()
316
 
317
- # 记录更详细的错误信息
318
  error_info = {
319
  "error": str(e),
320
  "traceback": traceback.format_exc(),
@@ -322,144 +315,124 @@ def load_models():
322
  "device": "cpu" if cpu_fallback_mode else "cuda",
323
  }
324
 
325
- # 保存错误信息到文件,方便排查
326
  try:
327
  with open(os.path.join(outputs_folder, "error_log.txt"), "w") as f:
328
  f.write(str(error_info))
329
  except:
330
  pass
331
 
332
- # 返回空字典,允许应用继续尝试运行
333
  cpu_fallback_mode = True
334
  return {}
335
 
336
-
337
- # 使用Hugging Face Spaces GPU装饰器
338
  if IN_HF_SPACE and 'spaces' in globals() and GPU_AVAILABLE:
339
  try:
340
  @spaces.GPU
341
  def initialize_models():
342
- """在@spaces.GPU装饰器内初始化模型"""
343
  global GPU_INITIALIZED
344
  try:
345
  result = load_models()
346
  GPU_INITIALIZED = True
347
  return result
348
  except Exception as e:
349
- print(f"使用spaces.GPU初始化模型时出错: {e}")
350
  traceback.print_exc()
351
  global cpu_fallback_mode
352
  cpu_fallback_mode = True
353
- # 不使用装饰器再次尝试
354
  return load_models()
355
  except Exception as e:
356
- print(f"创建spaces.GPU装饰器时出错: {e}")
357
- # 如果装饰器出错,直接使用非装饰器版本
358
  def initialize_models():
359
  return load_models()
360
 
361
-
362
- # 以下函数内部会延迟获取模型
363
  def get_models():
364
- """获取模型,如果尚未加载则加载模型"""
365
  global models, GPU_INITIALIZED
366
 
367
- # 添加模型加载锁,防止并发加载
368
  model_loading_key = "__model_loading__"
369
 
370
  if not models:
371
- # 检查是否正在加载模型
372
  if model_loading_key in globals():
373
- print("模型正在加载中,等待...")
374
- # 等待模型加载完成
375
  import time
376
  start_wait = time.time()
377
  while not models and model_loading_key in globals():
378
  time.sleep(0.5)
379
- # 超过60秒认为加载失败
380
  if time.time() - start_wait > 60:
381
- print("等待模型加载超时")
382
  break
383
 
384
  if models:
385
  return models
386
 
387
  try:
388
- # 设置加载标记
389
  globals()[model_loading_key] = True
390
 
391
  if IN_HF_SPACE and 'spaces' in globals() and GPU_AVAILABLE and not cpu_fallback_mode:
392
  try:
393
- print("使用@spaces.GPU装饰器加载模型")
394
- models = initialize_models()
 
395
  except Exception as e:
396
- print(f"使用GPU装饰器加载模型失败: {e}")
397
- print("尝试直接加载模型")
398
- models = load_models()
399
  else:
400
- print("直接加载模型")
401
- models = load_models()
 
402
  except Exception as e:
403
- print(f"加载模型时发生未预期的错误: {e}")
404
  traceback.print_exc()
405
- # 确保有一个空字典
406
- models = {}
407
  finally:
408
- # 无论成功与否,都移除加载标记
409
  if model_loading_key in globals():
410
  del globals()[model_loading_key]
411
 
412
  return models
413
 
414
-
415
  stream = AsyncStream()
416
 
417
-
418
  @torch.no_grad()
419
  def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache):
420
  global last_update_time
421
  last_update_time = time.time()
422
 
423
- # 限制视频长度不超过5秒
424
  total_second_length = min(total_second_length, 5.0)
425
 
426
- # 获取模型
427
  try:
428
- models = get_models()
429
- if not models:
430
- error_msg = "模型加载失败,请检查日志获取详细信息"
431
  print(error_msg)
432
  stream.output_queue.push(('error', error_msg))
433
  stream.output_queue.push(('end', None))
434
  return
435
 
436
- text_encoder = models['text_encoder']
437
- text_encoder_2 = models['text_encoder_2']
438
- tokenizer = models['tokenizer']
439
- tokenizer_2 = models['tokenizer_2']
440
- vae = models['vae']
441
- feature_extractor = models['feature_extractor']
442
- image_encoder = models['image_encoder']
443
- transformer = models['transformer']
444
  except Exception as e:
445
- error_msg = f"获取模型时出错: {e}"
446
  print(error_msg)
447
  traceback.print_exc()
448
  stream.output_queue.push(('error', error_msg))
449
  stream.output_queue.push(('end', None))
450
  return
451
 
452
- # 确定设备
453
  device = 'cuda' if GPU_AVAILABLE and not cpu_fallback_mode else 'cpu'
454
- print(f"使用设备: {device} 进行推理")
455
-
456
- # 调整参数以适应CPU模式
457
  if cpu_fallback_mode:
458
- print("CPU模式下使用更精简的参数")
459
- # 减小处理大小以加快CPU处理
460
  latent_window_size = min(latent_window_size, 5)
461
- steps = min(steps, 15) # 减少步数
462
- total_second_length = min(total_second_length, 2.0) # CPU模式下进一步限制视频长度
463
 
464
  total_latent_sections = (total_second_length * 30) / (latent_window_size * 4)
465
  total_latent_sections = int(max(round(total_latent_sections), 1))
@@ -473,17 +446,15 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
473
  stream.output_queue.push(('progress', (None, '', make_progress_bar_html(0, 'Starting ...'))))
474
 
475
  try:
476
- # Clean GPU
477
  if not high_vram and not cpu_fallback_mode:
478
  try:
479
  unload_complete_models(
480
  text_encoder, text_encoder_2, image_encoder, vae, transformer
481
  )
482
  except Exception as e:
483
- print(f"卸载模型时出错: {e}")
484
- # 继续执行,不中断流程
485
-
486
- # Text encoding
487
  last_update_time = time.time()
488
  stream.output_queue.push(('progress', (None, '', make_progress_bar_html(0, 'Text encoding ...'))))
489
 
@@ -502,14 +473,14 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
502
  llama_vec, llama_attention_mask = crop_or_pad_yield_mask(llama_vec, length=512)
503
  llama_vec_n, llama_attention_mask_n = crop_or_pad_yield_mask(llama_vec_n, length=512)
504
  except Exception as e:
505
- error_msg = f"文本编码过程出错: {e}"
506
  print(error_msg)
507
  traceback.print_exc()
508
  stream.output_queue.push(('error', error_msg))
509
  stream.output_queue.push(('end', None))
510
  return
511
 
512
- # Processing input image
513
  last_update_time = time.time()
514
  stream.output_queue.push(('progress', (None, '', make_progress_bar_html(0, 'Image processing ...'))))
515
 
@@ -517,26 +488,24 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
517
  H, W, C = input_image.shape
518
  height, width = find_nearest_bucket(H, W, resolution=640)
519
 
520
- # 如果是CPU模式,缩小处理尺寸
521
  if cpu_fallback_mode:
522
  height = min(height, 320)
523
  width = min(width, 320)
524
 
525
  input_image_np = resize_and_center_crop(input_image, target_width=width, target_height=height)
526
-
527
  Image.fromarray(input_image_np).save(os.path.join(outputs_folder, f'{job_id}.png'))
528
 
529
  input_image_pt = torch.from_numpy(input_image_np).float() / 127.5 - 1
530
  input_image_pt = input_image_pt.permute(2, 0, 1)[None, :, None]
531
  except Exception as e:
532
- error_msg = f"图像处理过程出错: {e}"
533
  print(error_msg)
534
  traceback.print_exc()
535
  stream.output_queue.push(('error', error_msg))
536
  stream.output_queue.push(('end', None))
537
  return
538
 
539
- # VAE encoding
540
  last_update_time = time.time()
541
  stream.output_queue.push(('progress', (None, '', make_progress_bar_html(0, 'VAE encoding ...'))))
542
 
@@ -546,14 +515,14 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
546
 
547
  start_latent = vae_encode(input_image_pt, vae)
548
  except Exception as e:
549
- error_msg = f"VAE编码过程出错: {e}"
550
  print(error_msg)
551
  traceback.print_exc()
552
  stream.output_queue.push(('error', error_msg))
553
  stream.output_queue.push(('end', None))
554
  return
555
 
556
- # CLIP Vision
557
  last_update_time = time.time()
558
  stream.output_queue.push(('progress', (None, '', make_progress_bar_html(0, 'CLIP Vision encoding ...'))))
559
 
@@ -564,14 +533,14 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
564
  image_encoder_output = hf_clip_vision_encode(input_image_np, feature_extractor, image_encoder)
565
  image_encoder_last_hidden_state = image_encoder_output.last_hidden_state
566
  except Exception as e:
567
- error_msg = f"CLIP Vision编码过程出错: {e}"
568
  print(error_msg)
569
  traceback.print_exc()
570
  stream.output_queue.push(('error', error_msg))
571
  stream.output_queue.push(('end', None))
572
  return
573
 
574
- # Dtype
575
  try:
576
  llama_vec = llama_vec.to(transformer.dtype)
577
  llama_vec_n = llama_vec_n.to(transformer.dtype)
@@ -579,14 +548,14 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
579
  clip_l_pooler_n = clip_l_pooler_n.to(transformer.dtype)
580
  image_encoder_last_hidden_state = image_encoder_last_hidden_state.to(transformer.dtype)
581
  except Exception as e:
582
- error_msg = f"数据类型转换出错: {e}"
583
  print(error_msg)
584
  traceback.print_exc()
585
  stream.output_queue.push(('error', error_msg))
586
  stream.output_queue.push(('end', None))
587
  return
588
 
589
- # Sampling
590
  last_update_time = time.time()
591
  stream.output_queue.push(('progress', (None, '', make_progress_bar_html(0, 'Start sampling ...'))))
592
 
@@ -598,7 +567,7 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
598
  history_pixels = None
599
  total_generated_latent_frames = 0
600
  except Exception as e:
601
- error_msg = f"初始化历史状态出错: {e}"
602
  print(error_msg)
603
  traceback.print_exc()
604
  stream.output_queue.push(('error', error_msg))
@@ -606,13 +575,8 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
606
  return
607
 
608
  latent_paddings = reversed(range(total_latent_sections))
609
-
610
  if total_latent_sections > 4:
611
- # In theory the latent_paddings should follow the above sequence, but it seems that duplicating some
612
- # items looks better than expanding it when total_latent_sections > 4
613
- # One can try to remove below trick and just
614
- # use `latent_paddings = list(reversed(range(total_latent_sections)))` to compare
615
- latent_paddings = [3] + [2] * (total_latent_sections - 3) + [1, 0]
616
 
617
  for latent_padding in latent_paddings:
618
  last_update_time = time.time()
@@ -620,14 +584,14 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
620
  latent_padding_size = latent_padding * latent_window_size
621
 
622
  if stream.input_queue.top() == 'end':
623
- # 确保在结束时保存当前的视频
624
  if history_pixels is not None and total_generated_latent_frames > 0:
625
  try:
626
  output_filename = os.path.join(outputs_folder, f'{job_id}_final_{total_generated_latent_frames}.mp4')
627
  save_bcthw_as_mp4(history_pixels, output_filename, fps=30)
628
  stream.output_queue.push(('file', output_filename))
629
  except Exception as e:
630
- print(f"保存最终视频时出错: {e}")
631
 
632
  stream.output_queue.push(('end', None))
633
  return
@@ -643,10 +607,9 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
643
  clean_latents_post, clean_latents_2x, clean_latents_4x = history_latents[:, :, :1 + 2 + 16, :, :].split([1, 2, 16], dim=2)
644
  clean_latents = torch.cat([clean_latents_pre, clean_latents_post], dim=2)
645
  except Exception as e:
646
- error_msg = f"准备采样数据时出错: {e}"
647
  print(error_msg)
648
  traceback.print_exc()
649
- # 尝试继续下一轮迭代而不是完全终止
650
  if last_output_filename:
651
  stream.output_queue.push(('file', last_output_filename))
652
  continue
@@ -656,15 +619,13 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
656
  unload_complete_models()
657
  move_model_to_device_with_memory_preservation(transformer, target_device=device, preserved_memory_gb=gpu_memory_preservation)
658
  except Exception as e:
659
- print(f"移动transformerGPU时出错: {e}")
660
- # 继续执行,可能影响性能但不必终止
661
 
662
  if use_teacache and not cpu_fallback_mode:
663
  try:
664
  transformer.initialize_teacache(enable_teacache=True, num_steps=steps)
665
  except Exception as e:
666
- print(f"初始化teacache时出错: {e}")
667
- # 禁用teacache并继续
668
  transformer.initialize_teacache(enable_teacache=False)
669
  else:
670
  transformer.initialize_teacache(enable_teacache=False)
@@ -674,25 +635,10 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
674
  last_update_time = time.time()
675
 
676
  try:
677
- # 首先检查是否有停止信号
678
- print(f"【调试】回调函数: 步骤 {d['i']}, 检查是否有停止信号")
679
- try:
680
- queue_top = stream.input_queue.top()
681
- print(f"【调试】回调函数: 队列顶部信号 = {queue_top}")
682
-
683
- if queue_top == 'end':
684
- print("【调试】回调函数: 检测到停止信号,准备中断...")
685
- try:
686
- stream.output_queue.push(('end', None))
687
- print("【调试】回调函数: 成功向输出队列推送end信号")
688
- except Exception as e:
689
- print(f"【调试】回调函数: 向输出队列推送end信号失败: {e}")
690
-
691
- print("【调试】回调函数: 即将抛出KeyboardInterrupt异常")
692
- raise KeyboardInterrupt('用户主动结束任务')
693
- except Exception as e:
694
- print(f"【调试】回调函数: 检查队列顶部信号出错: {e}")
695
-
696
  preview = d['denoised']
697
  preview = vae_decode_fake(preview)
698
 
@@ -702,25 +648,19 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
702
  current_step = d['i'] + 1
703
  percentage = int(100.0 * current_step / steps)
704
  hint = f'Sampling {current_step}/{steps}'
705
- desc = f'Total generated frames: {int(max(0, total_generated_latent_frames * 4 - 3))}, Video length: {max(0, (total_generated_latent_frames * 4 - 3) / 30) :.2f} seconds (FPS-30). The video is being extended now ...'
706
  stream.output_queue.push(('progress', (preview, desc, make_progress_bar_html(percentage, hint))))
707
- except KeyboardInterrupt as e:
708
- # 捕获并重新抛出中断异常,确保它能传播到采样函数
709
- print(f"【调试】回调函数: 捕获到KeyboardInterrupt: {e}")
710
- print("【调试】回调函数: 重新抛出中断异常,确保传播到采样函数")
711
  raise
712
  except Exception as e:
713
- print(f"【调试】回调函数中出错: {e}")
714
- # 不中断采样过程
715
- print(f"【调试】回调函数: 步骤 {d['i']} 完成")
716
  return
717
 
718
  try:
719
  sampling_start_time = time.time()
720
- print(f"开始采样,设备: {device}, 数据类��: {transformer.dtype}, 使用TeaCache: {use_teacache and not cpu_fallback_mode}")
721
 
722
  try:
723
- print("【调试】开始sample_hunyuan采样流程")
724
  generated_latents = sample_hunyuan(
725
  transformer=transformer,
726
  sampler='unipc',
@@ -730,7 +670,6 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
730
  real_guidance_scale=cfg,
731
  distilled_guidance_scale=gs,
732
  guidance_rescale=rs,
733
- # shift=3.0,
734
  num_inference_steps=steps,
735
  generator=rnd,
736
  prompt_embeds=llama_vec,
@@ -752,43 +691,28 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
752
  callback=callback,
753
  )
754
 
755
- print(f"【调试】采样完成,用时: {time.time() - sampling_start_time:.2f}")
756
  except KeyboardInterrupt as e:
757
- # 用户主动中断
758
- print(f"【调试】捕获到KeyboardInterrupt: {e}")
759
- print("【调试】用户主动中断采样过程,处理中断逻辑")
760
-
761
- # 如果已经有生成的视频,返回最后生成的视频
762
  if last_output_filename:
763
- print(f"【调试】已有部分生成视频: {last_output_filename},返回此视频")
764
  stream.output_queue.push(('file', last_output_filename))
765
- error_msg = "用户中断生成过程,但已生成部分视频"
766
  else:
767
- print("【调试】没有部分生成视频,返回中断消息")
768
- error_msg = "用户中断生成过程,未生成视频"
769
 
770
- print(f"【调试】推送错误消息: {error_msg}")
771
  stream.output_queue.push(('error', error_msg))
772
- print("【调试】推送end信号")
773
  stream.output_queue.push(('end', None))
774
- print("【调试】中断处理完成,返回")
775
  return
776
  except Exception as e:
777
- print(f"采样过程中出错: {e}")
778
  traceback.print_exc()
779
-
780
- # 如果已经有生成的视频,返回最后生成的视频
781
  if last_output_filename:
782
  stream.output_queue.push(('file', last_output_filename))
783
-
784
- # 创建错误信息
785
- error_msg = f"采样过程中出错,但已返回部分生成的视频: {e}"
786
  stream.output_queue.push(('error', error_msg))
787
  else:
788
- # 如果没有生成的视频,返回错误信息
789
- error_msg = f"采样过程中出错,无法生成视频: {e}"
790
  stream.output_queue.push(('error', error_msg))
791
-
792
  stream.output_queue.push(('end', None))
793
  return
794
 
@@ -799,10 +723,9 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
799
  total_generated_latent_frames += int(generated_latents.shape[2])
800
  history_latents = torch.cat([generated_latents.to(history_latents), history_latents], dim=2)
801
  except Exception as e:
802
- error_msg = f"处理生成的潜变量时出错: {e}"
803
  print(error_msg)
804
  traceback.print_exc()
805
-
806
  if last_output_filename:
807
  stream.output_queue.push(('file', last_output_filename))
808
  stream.output_queue.push(('error', error_msg))
@@ -814,23 +737,21 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
814
  offload_model_from_device_for_memory_preservation(transformer, target_device=device, preserved_memory_gb=8)
815
  load_model_as_complete(vae, target_device=device)
816
  except Exception as e:
817
- print(f"管理模型内存时出错: {e}")
818
- # 继续执行
819
 
820
  try:
821
  real_history_latents = history_latents[:, :, :total_generated_latent_frames, :, :]
822
  except Exception as e:
823
- error_msg = f"处理历史潜变量时出错: {e}"
824
  print(error_msg)
825
-
826
  if last_output_filename:
827
  stream.output_queue.push(('file', last_output_filename))
828
  continue
829
 
830
  try:
831
  vae_start_time = time.time()
832
- print(f"开始VAE解码,潜变量形状: {real_history_latents.shape}")
833
-
834
  if history_pixels is None:
835
  history_pixels = vae_decode(real_history_latents, vae).cpu()
836
  else:
@@ -840,100 +761,76 @@ def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_wind
840
  current_pixels = vae_decode(real_history_latents[:, :, :section_latent_frames], vae).cpu()
841
  history_pixels = soft_append_bcthw(current_pixels, history_pixels, overlapped_frames)
842
 
843
- print(f"VAE解码完成,用时: {time.time() - vae_start_time:.2f}")
844
-
845
  if not high_vram and not cpu_fallback_mode:
846
  try:
847
  unload_complete_models()
848
  except Exception as e:
849
- print(f"卸载模型时出错: {e}")
850
 
851
  output_filename = os.path.join(outputs_folder, f'{job_id}_{total_generated_latent_frames}.mp4')
852
 
853
  save_start_time = time.time()
854
  save_bcthw_as_mp4(history_pixels, output_filename, fps=30)
855
- print(f"保存视频完成,用时: {time.time() - save_start_time:.2f}")
856
 
857
- print(f'Decoded. Current latent shape {real_history_latents.shape}; pixel shape {history_pixels.shape}')
858
 
859
  last_output_filename = output_filename
860
  stream.output_queue.push(('file', output_filename))
861
  except Exception as e:
862
- print(f"视频解码或保存过程中出错: {e}")
863
  traceback.print_exc()
864
-
865
- # 如果已经有生成的视频,返回最后生成的视频
866
  if last_output_filename:
867
  stream.output_queue.push(('file', last_output_filename))
868
-
869
- # 记录错误信息
870
- error_msg = f"视频解码或保存过程中出错: {e}"
871
  stream.output_queue.push(('error', error_msg))
872
-
873
- # 尝试继续下一次迭代
874
  continue
875
 
876
  if is_last_section:
877
  break
878
  except Exception as e:
879
- print(f"【调试】处理过程中出现错误: {e}, 类型: {type(e)}")
880
- print(f"【调试】错误详情:")
881
  traceback.print_exc()
882
 
883
- # 检查是否是中断类型异常
884
  if isinstance(e, KeyboardInterrupt):
885
- print("【调试】捕获到外层KeyboardInterrupt异常")
886
 
887
  if not high_vram and not cpu_fallback_mode:
888
  try:
889
- print("【调试】尝试卸载模型以释放资源")
890
  unload_complete_models(
891
  text_encoder, text_encoder_2, image_encoder, vae, transformer
892
  )
893
- print("【调试】模型卸载成功")
894
  except Exception as unload_error:
895
- print(f"【调试】卸载模型时出错: {unload_error}")
896
- pass
897
 
898
- # 如果已经有生成的视频,返回最后生成的视频
899
  if last_output_filename:
900
- print(f"【调试】外层异常处理: 返回已生成的部分视频 {last_output_filename}")
901
  stream.output_queue.push(('file', last_output_filename))
902
- else:
903
- print("【调试】外层异常处理: 未找到已生成的视频")
904
 
905
- # 返回错误信息
906
- error_msg = f"处理��程中出现错误: {e}"
907
- print(f"【调试】外层异常处理: 推送错误信息: {error_msg}")
908
  stream.output_queue.push(('error', error_msg))
909
 
910
- # 确保总是返回end信号
911
- print("【调试】工作函数结束,推送end信号")
912
  stream.output_queue.push(('end', None))
913
  return
914
 
915
-
916
- # 使用Hugging Face Spaces GPU装饰器处理进程函数
917
  if IN_HF_SPACE and 'spaces' in globals():
918
  @spaces.GPU
919
  def process_with_gpu(input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache):
920
  global stream
921
  assert input_image is not None, 'No input image!'
922
 
923
- # 初始化UI状态
924
  yield None, None, '', '', gr.update(interactive=False), gr.update(interactive=True)
925
 
926
  try:
927
  stream = AsyncStream()
928
-
929
- # 异步启动worker
930
  async_run(worker, input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache)
931
 
932
  output_filename = None
933
  prev_output_filename = None
934
  error_message = None
935
 
936
- # 持续检查worker的输出
937
  while True:
938
  try:
939
  flag, data = stream.output_queue.next()
@@ -941,50 +838,40 @@ if IN_HF_SPACE and 'spaces' in globals():
941
  if flag == 'file':
942
  output_filename = data
943
  prev_output_filename = output_filename
944
- # 清除错误显示,确保文件成功时不显示错误
945
  yield output_filename, gr.update(), gr.update(), '', gr.update(interactive=False), gr.update(interactive=True)
946
 
947
  if flag == 'progress':
948
  preview, desc, html = data
949
- # 更新进度时不改变错误信息,并确保停止按钮可交互
950
  yield gr.update(), gr.update(visible=True, value=preview), desc, html, gr.update(interactive=False), gr.update(interactive=True)
951
 
952
  if flag == 'error':
953
  error_message = data
954
- print(f"收到错误消息: {error_message}")
955
- # 不立即显示,等待end信号
956
 
957
  if flag == 'end':
958
- # 如果有最后的视频文件,确保返回
959
  if output_filename is None and prev_output_filename is not None:
960
  output_filename = prev_output_filename
961
 
962
- # 如果有错误消息,创建友好的错误显示
963
  if error_message:
964
  error_html = create_error_html(error_message)
965
  yield output_filename, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
966
  else:
967
- # 确保成功完成时不显示任何错误
968
  yield output_filename, gr.update(visible=False), gr.update(), '', gr.update(interactive=True), gr.update(interactive=False)
969
  break
970
  except Exception as e:
971
- print(f"处理输出时出错: {e}")
972
- # 检查是否长时间没有更新
973
  current_time = time.time()
974
- if current_time - last_update_time > 60: # 60秒没有更新,可能卡住了
975
- print(f"处理似乎卡住了,已经 {current_time - last_update_time:.1f} 秒没有更新")
976
-
977
- # 如果有部分生成的视频,返回
978
  if prev_output_filename:
979
- error_html = create_error_html("处理超时,但已生成部分视频", is_timeout=True)
980
  yield prev_output_filename, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
981
  else:
982
- error_html = create_error_html(f"处理超时: {e}", is_timeout=True)
983
  yield None, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
984
  break
985
-
986
  except Exception as e:
987
- print(f"启动处理时出错: {e}")
988
  traceback.print_exc()
989
  error_msg = str(e)
990
 
@@ -997,20 +884,16 @@ else:
997
  global stream
998
  assert input_image is not None, 'No input image!'
999
 
1000
- # 初始化UI状态
1001
  yield None, None, '', '', gr.update(interactive=False), gr.update(interactive=True)
1002
 
1003
  try:
1004
  stream = AsyncStream()
1005
-
1006
- # 异步启动worker
1007
  async_run(worker, input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache)
1008
 
1009
  output_filename = None
1010
  prev_output_filename = None
1011
  error_message = None
1012
 
1013
- # 持续检查worker的输出
1014
  while True:
1015
  try:
1016
  flag, data = stream.output_queue.next()
@@ -1018,107 +901,85 @@ else:
1018
  if flag == 'file':
1019
  output_filename = data
1020
  prev_output_filename = output_filename
1021
- # 清除错误显示,确保文件成功时不显示错误
1022
  yield output_filename, gr.update(), gr.update(), '', gr.update(interactive=False), gr.update(interactive=True)
1023
 
1024
  if flag == 'progress':
1025
  preview, desc, html = data
1026
- # 更新进度时不改变错误信息,并确保停止按钮可交互
1027
  yield gr.update(), gr.update(visible=True, value=preview), desc, html, gr.update(interactive=False), gr.update(interactive=True)
1028
 
1029
  if flag == 'error':
1030
  error_message = data
1031
- print(f"收到错误消息: {error_message}")
1032
- # 不立即显示,等待end信号
1033
 
1034
  if flag == 'end':
1035
- # 如果有最后的视频文件,确保返回
1036
  if output_filename is None and prev_output_filename is not None:
1037
  output_filename = prev_output_filename
1038
 
1039
- # 如果有错误消息,创建友好的错误显示
1040
  if error_message:
1041
  error_html = create_error_html(error_message)
1042
  yield output_filename, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
1043
  else:
1044
- # 确保成功完成时不显示任何错误
1045
  yield output_filename, gr.update(visible=False), gr.update(), '', gr.update(interactive=True), gr.update(interactive=False)
1046
  break
1047
  except Exception as e:
1048
- print(f"处理输出时出错: {e}")
1049
- # 检查是否长时间没有更新
1050
  current_time = time.time()
1051
- if current_time - last_update_time > 60: # 60秒没有更新,可能卡住了
1052
- print(f"处理似乎卡住了,已经 {current_time - last_update_time:.1f} 秒没有更新")
1053
-
1054
- # 如果有部分生成的视频,返回
1055
  if prev_output_filename:
1056
- error_html = create_error_html("处理超时,但已生成部分视频", is_timeout=True)
1057
  yield prev_output_filename, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
1058
  else:
1059
- error_html = create_error_html(f"处理超时: {e}", is_timeout=True)
1060
  yield None, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
1061
  break
1062
-
1063
  except Exception as e:
1064
- print(f"启动处理时出错: {e}")
1065
  traceback.print_exc()
1066
  error_msg = str(e)
1067
 
1068
  error_html = create_error_html(error_msg)
1069
  yield None, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
1070
 
1071
-
1072
  def end_process():
1073
- """停止生成过程函数 - 通过在队列中推送'end'信号来中断生成"""
1074
- print("【调试】用户点击了停止按钮,发送停止信号...")
1075
- # 确保stream已初始化
1076
  if 'stream' in globals() and stream is not None:
1077
- # 在推送前检查队列状态
1078
  try:
1079
  current_top = stream.input_queue.top()
1080
- print(f"【调试】当前队列顶部信号: {current_top}")
1081
  except Exception as e:
1082
- print(f"【调试】检查队列状态出错: {e}")
1083
-
1084
- # 推送end信号
1085
  try:
1086
  stream.input_queue.push('end')
1087
- print("【调试】成功推送end信号到队列")
1088
-
1089
- # 验证信号是否成功推送
1090
  try:
1091
  current_top_after = stream.input_queue.top()
1092
- print(f"【调试】推送后队列顶部信号: {current_top_after}")
1093
  except Exception as e:
1094
- print(f"【调试】验证推送后队列状态出错: {e}")
1095
-
1096
  except Exception as e:
1097
- print(f"【调试】推送end信号到队列失败: {e}")
1098
  else:
1099
- print("【调试】警告: stream未初始化,无法发送停止信号")
1100
  return None
1101
 
1102
-
1103
  quick_prompts = [
1104
  'The girl dances gracefully, with clear movements, full of charm.',
1105
  'A character doing some simple body movements.',
1106
  ]
1107
  quick_prompts = [[x] for x in quick_prompts]
1108
 
1109
-
1110
- # 创建一个自定义CSS,增加响应式布局支持
1111
  def make_custom_css():
1112
  progress_bar_css = make_progress_bar_css()
1113
 
1114
  responsive_css = """
1115
- /* 基础响应式设置 */
 
1116
  #app-container {
1117
  max-width: 100%;
1118
  margin: 0 auto;
1119
  }
1120
 
1121
- /* 语言切换按钮样式 */
1122
  #language-toggle {
1123
  position: fixed;
1124
  top: 10px;
@@ -1133,27 +994,23 @@ def make_custom_css():
1133
  font-size: 14px;
1134
  }
1135
 
1136
- /* 页面标题样式 */
1137
  h1 {
1138
  font-size: 2rem;
1139
  text-align: center;
1140
  margin-bottom: 1rem;
1141
  }
1142
 
1143
- /* 按钮样式 */
1144
  .start-btn, .stop-btn {
1145
  min-height: 45px;
1146
  font-size: 1rem;
1147
  }
1148
 
1149
- /* 移动设备样式 - 小屏幕 */
1150
  @media (max-width: 768px) {
1151
  h1 {
1152
  font-size: 1.5rem;
1153
  margin-bottom: 0.5rem;
1154
  }
1155
 
1156
- /* 单列布局 */
1157
  .mobile-full-width {
1158
  flex-direction: column !important;
1159
  }
@@ -1163,66 +1020,55 @@ def make_custom_css():
1163
  flex-grow: 1;
1164
  }
1165
 
1166
- /* 调整视频大小 */
1167
  .video-container {
1168
  height: auto !important;
1169
  }
1170
 
1171
- /* 调整按钮大小 */
1172
  .button-container button {
1173
  min-height: 50px;
1174
  font-size: 1rem;
1175
  touch-action: manipulation;
1176
  }
1177
 
1178
- /* 调整滑块 */
1179
  .slider-container input[type="range"] {
1180
  height: 30px;
1181
  }
1182
  }
1183
 
1184
- /* 平板设备样式 */
1185
  @media (min-width: 769px) and (max-width: 1024px) {
1186
  .tablet-adjust {
1187
  width: 48% !important;
1188
  }
1189
  }
1190
 
1191
- /* 黑暗模式支持 */
1192
  @media (prefers-color-scheme: dark) {
1193
  .dark-mode-text {
1194
  color: #f0f0f0;
1195
  }
1196
-
1197
  .dark-mode-bg {
1198
  background-color: #2a2a2a;
1199
  }
1200
  }
1201
 
1202
- /* 增强可访问性 */
1203
  button, input, select, textarea {
1204
- font-size: 16px; /* 防止iOS缩放 */
1205
  }
1206
 
1207
- /* 触摸优化 */
1208
  button, .interactive-element {
1209
  min-height: 44px;
1210
  min-width: 44px;
1211
  }
1212
 
1213
- /* 提高对比度 */
1214
  .high-contrast {
1215
  color: #fff;
1216
  background-color: #000;
1217
  }
1218
 
1219
- /* 进度条样式增强 */
1220
  .progress-container {
1221
  margin-top: 10px;
1222
  margin-bottom: 10px;
1223
  }
1224
 
1225
- /* 错误消息样式 */
1226
  #error-message {
1227
  color: #ff4444;
1228
  font-weight: bold;
@@ -1231,7 +1077,6 @@ def make_custom_css():
1231
  margin-top: 10px;
1232
  }
1233
 
1234
- /* 确保错误容器正确显示 */
1235
  .error-message {
1236
  background-color: rgba(255, 0, 0, 0.1);
1237
  padding: 10px;
@@ -1240,19 +1085,16 @@ def make_custom_css():
1240
  border: 1px solid #ffcccc;
1241
  }
1242
 
1243
- /* 处理多语言错误消息 */
1244
- .error-msg-en, .error-msg-zh {
1245
  font-weight: bold;
1246
  }
1247
 
1248
- /* 错误图标 */
1249
  .error-icon {
1250
  color: #ff4444;
1251
  font-size: 18px;
1252
  margin-right: 8px;
1253
  }
1254
 
1255
- /* 确保空错误消息不显示背景和边框 */
1256
  #error-message:empty {
1257
  background-color: transparent;
1258
  border: none;
@@ -1260,37 +1102,26 @@ def make_custom_css():
1260
  margin: 0;
1261
  }
1262
 
1263
- /* 修复Gradio默认错误显示 */
1264
  .error {
1265
  display: none !important;
1266
  }
1267
  """
1268
 
1269
- # 合并CSS
1270
- combined_css = progress_bar_css + responsive_css
1271
- return combined_css
1272
-
1273
 
1274
  css = make_custom_css()
1275
  block = gr.Blocks(css=css).queue()
1276
  with block:
1277
- # 添加语言切换功能
1278
  gr.HTML("""
1279
  <div id="app-container">
1280
- <button id="language-toggle" onclick="toggleLanguage()">中文/English</button>
1281
  </div>
1282
  <script>
1283
- // 全局变量,存储当前语言
1284
  window.currentLang = "en";
1285
-
1286
- // 语言切换函数
1287
  function toggleLanguage() {
1288
- window.currentLang = window.currentLang === "en" ? "zh" : "en";
1289
 
1290
- // 获取所有带有data-i18n属性的元素
1291
  const elements = document.querySelectorAll('[data-i18n]');
1292
-
1293
- // 遍历并切换语言
1294
  elements.forEach(el => {
1295
  const key = el.getAttribute('data-i18n');
1296
  const translations = {
@@ -1318,50 +1149,37 @@ with block:
1318
  "next_latents": "Next Latents",
1319
  "generated_video": "Generated Video",
1320
  "sampling_note": "Note: Due to reversed sampling, ending actions will be generated before starting actions. If the starting action is not in the video, please wait, it will be generated later.",
1321
- "error_message": "Error",
1322
- "processing_error": "Processing error",
1323
- "network_error": "Network connection is unstable, model download timed out. Please try again later.",
1324
- "memory_error": "GPU memory insufficient, please try increasing GPU memory preservation value or reduce video length.",
1325
- "model_error": "Failed to load model, possibly due to network issues or high server load. Please try again later.",
1326
- "partial_video": "Processing error, but partial video has been generated",
1327
- "processing_interrupt": "Processing was interrupted, but partial video has been generated"
1328
  },
1329
- "zh": {
1330
- "title": "FramePack - 图像到视频生成",
1331
- "upload_image": "上传图像",
1332
- "prompt": "提示词",
1333
- "quick_prompts": "快速提示词列表",
1334
- "start_generation": "开始生成",
1335
- "stop_generation": "结束生成",
1336
- "use_teacache": "使用TeaCache",
1337
- "teacache_info": "速度更快,但可能会使手指和手的生成效果稍差。",
1338
- "negative_prompt": "负面提示词",
1339
- "seed": "随机种子",
1340
- "video_length": "视频长度(最大5)",
1341
- "latent_window": "潜在窗口大小",
1342
- "steps": "推理步数",
1343
- "steps_info": "不建议修改此值。",
1344
- "cfg_scale": "CFG Scale",
1345
- "distilled_cfg": "蒸馏CFG比例",
1346
- "distilled_cfg_info": "不建议修改此值。",
1347
- "cfg_rescale": "CFG重缩放",
1348
- "gpu_memory": "GPU推理保留内存(GB)(值越大速度越慢)",
1349
- "gpu_memory_info": "如果出现OOM错误,请将此值设置得更大。值越大,速度越慢。",
1350
- "next_latents": "下一批潜变量",
1351
- "generated_video": "生成的视频",
1352
- "sampling_note": "注意:由于采样是倒序的,结束动作将在开始动作之前生成。如果视频中没有出现起始动作,请继续等待,它将在稍后生成。",
1353
- "error_message": "错误信息",
1354
- "processing_error": "处理过程出错",
1355
- "network_error": "网络连接不稳定,模型下载超时。请稍后再试。",
1356
- "memory_error": "GPU内存不足,请尝试增加GPU推理保留内存值或降低视频长度。",
1357
- "model_error": "模型加载失败,可能是网络问题或服务器负载过高。请稍后再试。",
1358
- "partial_video": "处理过程中出现错误,但已生成部分视频",
1359
- "processing_interrupt": "处理过程中断,但已生成部分视频"
1360
  }
1361
  };
1362
 
1363
  if (translations[window.currentLang] && translations[window.currentLang][key]) {
1364
- // 根据元素类型设置文本
1365
  if (el.tagName === 'BUTTON') {
1366
  el.textContent = translations[window.currentLang][key];
1367
  } else if (el.tagName === 'LABEL') {
@@ -1372,39 +1190,35 @@ with block:
1372
  }
1373
  });
1374
 
1375
- // 更新页面上其他元素
1376
  document.querySelectorAll('.bilingual-label').forEach(el => {
1377
  const enText = el.getAttribute('data-en');
1378
- const zhText = el.getAttribute('data-zh');
1379
- el.textContent = window.currentLang === 'en' ? enText : zhText;
1380
  });
1381
 
1382
- // 处理错误消息容器
1383
  document.querySelectorAll('[data-lang]').forEach(el => {
1384
- el.style.display = el.getAttribute('data-lang') === window.currentLang ? 'block' : 'none';
1385
  });
1386
  }
1387
 
1388
- // 页面加载后初始化
1389
  document.addEventListener('DOMContentLoaded', function() {
1390
- // 添加data-i18n属性到需要国际化的元素
1391
  setTimeout(() => {
1392
- // 给所有标签添加i18n属性
1393
  const labelMap = {
1394
  "Upload Image": "upload_image",
1395
- "上传图像": "upload_image",
1396
  "Prompt": "prompt",
1397
- "提示词": "prompt",
1398
  "Quick Prompts": "quick_prompts",
1399
- "快速提示词列表": "quick_prompts",
1400
- "Generate": "start_generation",
1401
- "开始生成": "start_generation",
1402
  "Stop": "stop_generation",
1403
- "结束生成": "stop_generation",
1404
- // 添加其他标签映射...
1405
  };
1406
 
1407
- // 处理标签
1408
  document.querySelectorAll('label, span, button').forEach(el => {
1409
  const text = el.textContent.trim();
1410
  if (labelMap[text]) {
@@ -1412,84 +1226,80 @@ with block:
1412
  }
1413
  });
1414
 
1415
- // 添加特定元素的i18n属性
1416
  const titleEl = document.querySelector('h1');
1417
  if (titleEl) titleEl.setAttribute('data-i18n', 'title');
1418
 
1419
- // 初始化标签语言
1420
  toggleLanguage();
1421
  }, 1000);
1422
  });
1423
  </script>
1424
  """)
 
 
1425
 
1426
- # 标题使用data-i18n属性以便JavaScript切换
1427
- gr.HTML("<h1 data-i18n='title'>FramePack - Image to Video Generation / 图像到视频生成</h1>")
1428
-
1429
- # 使用带有mobile-full-width类的响应式行
1430
  with gr.Row(elem_classes="mobile-full-width"):
1431
  with gr.Column(scale=1, elem_classes="mobile-full-width"):
1432
- # 添加双语标签 - 上传图像
1433
  input_image = gr.Image(
1434
  sources='upload',
1435
  type="numpy",
1436
- label="Upload Image / 上传图像",
1437
  elem_id="input-image",
1438
  height=320
1439
  )
1440
 
1441
- # 添加双语标签 - 提示词
1442
  prompt = gr.Textbox(
1443
- label="Prompt / 提示词",
1444
  value='',
1445
  elem_id="prompt-input"
1446
  )
1447
 
1448
- # 添加双语标签 - 快速提示词
1449
  example_quick_prompts = gr.Dataset(
1450
  samples=quick_prompts,
1451
- label='Quick Prompts / 快速提示词列表',
1452
  samples_per_page=1000,
1453
  components=[prompt]
1454
  )
1455
- example_quick_prompts.click(lambda x: x[0], inputs=[example_quick_prompts], outputs=prompt, show_progress=False, queue=False)
 
 
 
 
 
 
1456
 
1457
- # 按钮添加样式和双语标签
1458
  with gr.Row(elem_classes="button-container"):
1459
  start_button = gr.Button(
1460
- value="Generate / 开始生成",
1461
  elem_classes="start-btn",
1462
  elem_id="start-button",
1463
  variant="primary"
1464
  )
1465
 
1466
  end_button = gr.Button(
1467
- value="Stop / 结束生成",
1468
  elem_classes="stop-btn",
1469
  elem_id="stop-button",
1470
  interactive=False
1471
  )
1472
 
1473
- # 参数设置区域
1474
  with gr.Group():
1475
  use_teacache = gr.Checkbox(
1476
- label='Use TeaCache / 使用TeaCache',
1477
  value=True,
1478
- info='Faster speed, but may result in slightly worse finger and hand generation. / 速度更快,但可能会使手指和手的生成效果稍差。'
1479
  )
1480
 
1481
- n_prompt = gr.Textbox(label="Negative Prompt / 负面提示词", value="", visible=False) # Not used
1482
 
1483
  seed = gr.Number(
1484
- label="Seed / 随机种子",
1485
  value=31337,
1486
  precision=0
1487
  )
1488
-
1489
- # 添加slider-container类以便CSS触摸优化
1490
  with gr.Group(elem_classes="slider-container"):
1491
  total_second_length = gr.Slider(
1492
- label="Video Length (max 5 seconds) / 视频长度(最大5秒)",
1493
  minimum=1,
1494
  maximum=5,
1495
  value=5,
@@ -1497,7 +1307,7 @@ with block:
1497
  )
1498
 
1499
  latent_window_size = gr.Slider(
1500
- label="Latent Window Size / 潜在窗口大小",
1501
  minimum=1,
1502
  maximum=33,
1503
  value=9,
@@ -1506,12 +1316,12 @@ with block:
1506
  )
1507
 
1508
  steps = gr.Slider(
1509
- label="Inference Steps / 推理步数",
1510
  minimum=1,
1511
  maximum=100,
1512
  value=25,
1513
  step=1,
1514
- info='Changing this value is not recommended. / 不建议修改此值。'
1515
  )
1516
 
1517
  cfg = gr.Slider(
@@ -1524,16 +1334,16 @@ with block:
1524
  )
1525
 
1526
  gs = gr.Slider(
1527
- label="Distilled CFG Scale / 蒸馏CFG比例",
1528
  minimum=1.0,
1529
  maximum=32.0,
1530
  value=10.0,
1531
  step=0.01,
1532
- info='Changing this value is not recommended. / 不建议修改此值。'
1533
  )
1534
 
1535
  rs = gr.Slider(
1536
- label="CFG Rescale / CFG重缩放",
1537
  minimum=0.0,
1538
  maximum=1.0,
1539
  value=0.0,
@@ -1542,111 +1352,101 @@ with block:
1542
  )
1543
 
1544
  gpu_memory_preservation = gr.Slider(
1545
- label="GPU Memory (GB) / GPU推理保留内存(GB)",
1546
  minimum=6,
1547
  maximum=128,
1548
  value=6,
1549
  step=0.1,
1550
- info="Set this to a larger value if you encounter OOM errors. Larger values cause slower speed. / 如果出现OOM错误,请将此值设置得更大。值越大,速度越慢。"
1551
  )
1552
 
1553
- # 右侧预览和结果列
1554
  with gr.Column(scale=1, elem_classes="mobile-full-width"):
1555
- # 预览图像
1556
  preview_image = gr.Image(
1557
- label="Preview / 预览",
1558
  height=200,
1559
  visible=False,
1560
  elem_classes="preview-container"
1561
  )
1562
 
1563
- # 视频结果容器
1564
  result_video = gr.Video(
1565
- label="Generated Video / 生成的视频",
1566
  autoplay=True,
1567
- show_share_button=True, # 添加分享按钮
1568
  height=512,
1569
  loop=True,
1570
  elem_classes="video-container",
1571
  elem_id="result-video"
1572
  )
1573
 
1574
- # 双语说明
1575
- gr.HTML("<div data-i18n='sampling_note' class='note'>Note: Due to reversed sampling, ending actions will be generated before starting actions. If the starting action is not in the video, please wait, it will be generated later.</div>")
1576
 
1577
- # 进度指示器
1578
  with gr.Group(elem_classes="progress-container"):
1579
  progress_desc = gr.Markdown('', elem_classes='no-generating-animation')
1580
  progress_bar = gr.HTML('', elem_classes='no-generating-animation')
1581
 
1582
- # 错误信息区域 - 确保使用HTML组件以支持我们的自定义错误消息格式
1583
  error_message = gr.HTML('', elem_id='error-message', visible=True)
1584
 
1585
- # 处理函数
1586
  ips = [input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache]
1587
-
1588
- # 开始和结束按钮事件
1589
- start_button.click(fn=process, inputs=ips, outputs=[result_video, preview_image, progress_desc, progress_bar, start_button, end_button])
1590
- end_button.click(fn=end_process)
1591
 
 
 
 
 
1592
 
1593
- block.launch()
1594
 
1595
- # 创建友好的错误显示HTML
1596
  def create_error_html(error_msg, is_timeout=False):
1597
- """创建双语错误消息HTML"""
1598
- # 提供更友好��中英文双语错误信息
1599
  en_msg = ""
1600
- zh_msg = ""
1601
 
1602
  if is_timeout:
1603
- en_msg = "Processing timed out, but partial video may have been generated" if "部分视频" in error_msg else f"Processing timed out: {error_msg}"
1604
- zh_msg = "处理超时,但已生成部分视频" if "部分视频" in error_msg else f"处理超时: {error_msg}"
1605
- elif "模型加载失败" in error_msg:
1606
- en_msg = "Failed to load models. The Space may be experiencing high traffic or GPU issues."
1607
- zh_msg = "模型加载失败,可能是Space流量过高或GPU资源不足。"
1608
- elif "GPU" in error_msg or "CUDA" in error_msg or "内存" in error_msg or "memory" in error_msg:
1609
- en_msg = "GPU memory insufficient or GPU error. Try increasing GPU memory preservation value or reduce video length."
1610
- zh_msg = "GPU内存不足或GPU错误,请尝试增加GPU推理保留内存值或降低视频长度。"
1611
- elif "采样过程中出错" in error_msg:
1612
- if "部分" in error_msg:
1613
- en_msg = "Error during sampling process, but partial video has been generated."
1614
- zh_msg = "采样过程中出错,但已生成部分视频。"
 
 
 
 
1615
  else:
1616
- en_msg = "Error during sampling process. Unable to generate video."
1617
- zh_msg = "采样过程中出错,无法生成视频。"
1618
- elif "模型下载超时" in error_msg or "网络连接不稳定" in error_msg or "ReadTimeoutError" in error_msg or "ConnectionError" in error_msg:
1619
- en_msg = "Network connection is unstable, model download timed out. Please try again later."
1620
- zh_msg = "网络连接不稳定,模型下载超时。请稍后再试。"
1621
- elif "VAE" in error_msg or "解码" in error_msg or "decode" in error_msg:
1622
- en_msg = "Error during video decoding or saving process. Try again with a different seed."
1623
- zh_msg = "视频解码或保存过程中出错,请尝试使用不同的随机种子。"
1624
  else:
1625
  en_msg = f"Processing error: {error_msg}"
1626
- zh_msg = f"处理过程出错: {error_msg}"
1627
 
1628
- # 创建双语错误消息HTML - 添加有用的图标并确保CSS样式适用
1629
  return f"""
1630
  <div class="error-message" id="custom-error-container">
1631
  <div class="error-msg-en" data-lang="en">
1632
  <span class="error-icon">⚠️</span> {en_msg}
1633
  </div>
1634
- <div class="error-msg-zh" data-lang="zh">
1635
- <span class="error-icon">⚠️</span> {zh_msg}
1636
  </div>
1637
  </div>
1638
  <script>
1639
- // 根据当前语言显示相应的错误消息
1640
  (function() {{
1641
  const errorContainer = document.getElementById('custom-error-container');
1642
  if (errorContainer) {{
1643
- const currentLang = window.currentLang || 'en'; // 默认英语
1644
  const errMsgs = errorContainer.querySelectorAll('[data-lang]');
1645
  errMsgs.forEach(msg => {{
1646
- msg.style.display = msg.getAttribute('data-lang') === currentLang ? 'block' : 'none';
1647
  }});
1648
-
1649
- // 确保Gradio默认错误UI不显示
1650
  const defaultErrorElements = document.querySelectorAll('.error');
1651
  defaultErrorElements.forEach(el => {{
1652
  el.style.display = 'none';
@@ -1654,4 +1454,4 @@ def create_error_html(error_msg, is_timeout=False):
1654
  }}
1655
  }})();
1656
  </script>
1657
- """
 
10
 
11
  os.environ['HF_HOME'] = os.path.abspath(os.path.realpath(os.path.join(os.path.dirname(__file__), './hf_download')))
12
 
13
+ # 영어/한국어 번역 딕셔너리
14
  translations = {
15
  "en": {
16
  "title": "FramePack - Image to Video Generation",
 
44
  "partial_video": "Processing error, but partial video has been generated",
45
  "processing_interrupt": "Processing was interrupted, but partial video has been generated"
46
  },
47
+ "ko": {
48
+ "title": "FramePack - 이미지에서 동영상 생성",
49
+ "upload_image": "이미지 업로드",
50
+ "prompt": "프롬프트",
51
+ "quick_prompts": "빠른 프롬프트 목록",
52
+ "start_generation": "생성 시작",
53
+ "stop_generation": "생성 중지",
54
+ "use_teacache": "TeaCache 사용",
55
+ "teacache_info": "더 빠른 속도를 제공하지만 손가락이나 손 생성 품질이 약간 떨어질 수 있습니다.",
56
+ "negative_prompt": "부정 프롬프트",
57
+ "seed": "랜덤 시드",
58
+ "video_length": "동영상 길이 (최대 5)",
59
+ "latent_window": "잠재 윈도우 크기",
60
+ "steps": "추론 스텝 수",
61
+ "steps_info": "이 값을 변경하는 것은 권장되지 않습니다.",
62
+ "cfg_scale": "CFG 스케일",
63
+ "distilled_cfg": "증류된 CFG 스케일",
64
+ "distilled_cfg_info": "이 값을 변경하는 것은 권장되지 않습니다.",
65
+ "cfg_rescale": "CFG 재스케일",
66
+ "gpu_memory": "GPU 메모리 보존 (GB) (값이 클수록 속도가 느려짐)",
67
+ "gpu_memory_info": "OOM 오류가 발생하면 이 값을 더 크게 설정하십시오. 값이 클수록 속도가 느려집니다.",
68
+ "next_latents": "다음 잠재 변수",
69
+ "generated_video": "생성된 동영상",
70
+ "sampling_note": "주의: 역순 샘플링 때문에, 종료 동작이 시작 동작보다 먼저 생성됩니다. 시작 동작이 동영상에 나타나지 않으면 기다려 주십시오. 나중에 생성됩니다.",
71
+ "error_message": "오류 메시지",
72
+ "processing_error": "처리 중 오류 발생",
73
+ "network_error": "네트워크 연결이 불안정하여 모델 다운로드가 시간 초과되었습니다. 나중에 다시 시도해 주십시오.",
74
+ "memory_error": "GPU 메모리가 부족합니다. GPU 메모리 보존 값을 늘리거나 동영상 길이를 줄여보세요.",
75
+ "model_error": "모델 로드에 실패했습니다. 네트워크 문제 또는 서버 부하가 높을 수 있습니다. 나중에 다시 시도해 주십시오.",
76
+ "partial_video": "처리 중 오류가 발생했지만 일부 동영상이 생성되었습니다.",
77
+ "processing_interrupt": "처리 중 중단되었지만 일부 동영상이 생성되었습니다."
78
  }
79
  }
80
 
81
+ # 다국어 텍스트를 반환하는 함수
82
  def get_translation(key, lang="en"):
83
  if lang in translations and key in translations[lang]:
84
  return translations[lang][key]
85
+ # 기본���(영어) 반환
86
  return translations["en"].get(key, key)
87
 
88
+ # 디폴트 언어를 영어로 설정
89
  current_language = "en"
90
 
91
+ # 언어 전환 함수
92
  def switch_language():
93
  global current_language
94
+ current_language = "ko" if current_language == "en" else "en"
95
  return current_language
96
 
97
  import gradio as gr
 
102
  import numpy as np
103
  import math
104
 
105
+ # Spaces 환경 체크
106
  IN_HF_SPACE = os.environ.get('SPACE_ID') is not None
107
 
108
+ # GPU 사용 여부 기록
109
  GPU_AVAILABLE = False
110
  GPU_INITIALIZED = False
111
  last_update_time = time.time()
112
 
113
+ # Spaces 환경이라면, spaces 모듈 불러오기 시도
114
  if IN_HF_SPACE:
115
  try:
116
  import spaces
117
+ print("Hugging Face Space 환경에서 실행 중, spaces 모듈을 불러왔습니다.")
118
 
119
+ # GPU 사용 가능 여부 확인
120
  try:
121
  GPU_AVAILABLE = torch.cuda.is_available()
122
  print(f"GPU available: {GPU_AVAILABLE}")
 
124
  print(f"GPU device name: {torch.cuda.get_device_name(0)}")
125
  print(f"GPU memory: {torch.cuda.get_device_properties(0).total_memory / 1e9} GB")
126
 
127
+ # 작은 테스트 연산으로 실제 GPU 동작 확인
128
  test_tensor = torch.zeros(1, device='cuda')
129
  test_tensor = test_tensor + 1
130
  del test_tensor
131
+ print("GPU 테스트 연산 성공")
132
  else:
133
+ print("경고: CUDA는 가능하다고 하나 실제 GPU 디바이스를 찾을 수 없습니다.")
134
  except Exception as e:
135
  GPU_AVAILABLE = False
136
+ print(f"GPU 확인 중 오류 발생: {e}")
137
+ print("CPU 모드로 진행합니다.")
138
  except ImportError:
139
+ print("spaces 모듈을 불러올 수 없습니다. Spaces 환경이 아닐 수 있습니다.")
140
  GPU_AVAILABLE = torch.cuda.is_available()
141
 
142
  from PIL import Image
 
156
  outputs_folder = './outputs/'
157
  os.makedirs(outputs_folder, exist_ok=True)
158
 
159
+ # Spaces 환경이 아닐 경우, VRAM 확인
160
  if not IN_HF_SPACE:
 
161
  try:
162
  if torch.cuda.is_available():
163
  free_mem_gb = get_cuda_free_memory_gb(gpu)
164
+ print(f'남은 VRAM: {free_mem_gb} GB')
165
  else:
166
+ free_mem_gb = 6.0 # 기본값
167
+ print("CUDA를 사용할 수 없으므로 기본 메모리 설정을 사용합니다.")
168
  except Exception as e:
169
+ free_mem_gb = 6.0
170
+ print(f"CUDA 메모리 확인 중 오류 발생: {e} / 기본값 사용")
171
 
172
  high_vram = free_mem_gb > 60
173
+ print(f'high_vram 모드: {high_vram}')
174
  else:
175
+ # Spaces 환경에서 기본값 설정
176
+ print("Spaces 환경에서 기본 메모리 설정 사용")
177
  try:
178
  if GPU_AVAILABLE:
179
+ free_mem_gb = torch.cuda.get_device_properties(0).total_memory / 1e9 * 0.9
180
+ high_vram = free_mem_gb > 10 # 조금 더 보수적으로 설정
181
  else:
182
+ free_mem_gb = 6.0
183
  high_vram = False
184
  except Exception as e:
185
+ print(f"GPU 메모리 확인 중 오류: {e}")
186
+ free_mem_gb = 6.0
187
  high_vram = False
188
 
189
+ print(f'GPU 메모리: {free_mem_gb:.2f} GB, High-VRAM 모드: {high_vram}')
190
 
191
+ # 전역 모델 ��조
192
  models = {}
193
+ cpu_fallback_mode = not GPU_AVAILABLE # GPU가 불가능하면 CPU 모드로
194
 
 
195
  def load_models():
196
  global models, cpu_fallback_mode, GPU_INITIALIZED
197
 
198
  if GPU_INITIALIZED:
199
+ print("모델이 이미 로드되었습니다. 다시 로드하지 않습니다.")
200
  return models
201
 
202
+ print("모델 로드를 시작합니다...")
203
+
204
  try:
 
205
  device = 'cuda' if GPU_AVAILABLE and not cpu_fallback_mode else 'cpu'
206
+ model_device = 'cpu' # 우선 CPU에 로드
207
+
208
+ # 기본적으로 GPU면 float16, CPU면 float32
209
  dtype = torch.float16 if GPU_AVAILABLE else torch.float32
210
  transformer_dtype = torch.bfloat16 if GPU_AVAILABLE else torch.float32
211
 
212
+ print(f"사용 디바이스: {device}, vae/text encoder dtype: {dtype}, transformer dtype: {transformer_dtype}")
213
 
 
214
  try:
215
  text_encoder = LlamaModel.from_pretrained("hunyuanvideo-community/HunyuanVideo", subfolder='text_encoder', torch_dtype=dtype).to(model_device)
216
  text_encoder_2 = CLIPTextModel.from_pretrained("hunyuanvideo-community/HunyuanVideo", subfolder='text_encoder_2', torch_dtype=dtype).to(model_device)
 
223
 
224
  transformer = HunyuanVideoTransformer3DModelPacked.from_pretrained('lllyasviel/FramePackI2V_HY', torch_dtype=transformer_dtype).to(model_device)
225
 
226
+ print("모든 모델을 성공적으로 로드했습니다.")
227
  except Exception as e:
228
+ print(f"모델 로드 중 오류 발생: {e}")
229
+ print("정밀도를 낮춰 다시 로드합니다...")
230
+
 
231
  dtype = torch.float32
232
  transformer_dtype = torch.float32
233
  cpu_fallback_mode = True
 
243
 
244
  transformer = HunyuanVideoTransformer3DModelPacked.from_pretrained('lllyasviel/FramePackI2V_HY', torch_dtype=transformer_dtype).to('cpu')
245
 
246
+ print("CPU 모드로 모델 로드 성공")
247
 
248
  vae.eval()
249
  text_encoder.eval()
 
258
  transformer.high_quality_fp32_output_for_inference = True
259
  print('transformer.high_quality_fp32_output_for_inference = True')
260
 
 
261
  if not cpu_fallback_mode:
262
  transformer.to(dtype=transformer_dtype)
263
  vae.to(dtype=dtype)
 
274
  if torch.cuda.is_available() and not cpu_fallback_mode:
275
  try:
276
  if not high_vram:
277
+ # 메모리 최적화
278
  DynamicSwapInstaller.install_model(transformer, device=device)
279
  DynamicSwapInstaller.install_model(text_encoder, device=device)
280
  else:
 
283
  image_encoder.to(device)
284
  vae.to(device)
285
  transformer.to(device)
286
+ print(f"모델을 {device}로 이동 완료")
287
  except Exception as e:
288
+ print(f"{device} 모델 이동 중 오류 발생: {e}")
289
+ print("CPU 모드로 전환")
290
  cpu_fallback_mode = True
291
+
292
+ models_local = {
 
293
  'text_encoder': text_encoder,
294
  'text_encoder_2': text_encoder_2,
295
  'tokenizer': tokenizer,
 
301
  }
302
 
303
  GPU_INITIALIZED = True
304
+ models.update(models_local)
305
+ print(f"모델 로드 완료. 현재 실행 모드: {'CPU' if cpu_fallback_mode else 'GPU'}")
306
  return models
307
  except Exception as e:
308
+ print(f"모델 로드 중 예상치 못한 오류가 발생: {e}")
309
  traceback.print_exc()
310
 
 
311
  error_info = {
312
  "error": str(e),
313
  "traceback": traceback.format_exc(),
 
315
  "device": "cpu" if cpu_fallback_mode else "cuda",
316
  }
317
 
 
318
  try:
319
  with open(os.path.join(outputs_folder, "error_log.txt"), "w") as f:
320
  f.write(str(error_info))
321
  except:
322
  pass
323
 
 
324
  cpu_fallback_mode = True
325
  return {}
326
 
 
 
327
  if IN_HF_SPACE and 'spaces' in globals() and GPU_AVAILABLE:
328
  try:
329
  @spaces.GPU
330
  def initialize_models():
331
+ """@spaces.GPU 환경에서 모델을 초기화"""
332
  global GPU_INITIALIZED
333
  try:
334
  result = load_models()
335
  GPU_INITIALIZED = True
336
  return result
337
  except Exception as e:
338
+ print(f"@spaces.GPU 모델 초기화 중 오류: {e}")
339
  traceback.print_exc()
340
  global cpu_fallback_mode
341
  cpu_fallback_mode = True
 
342
  return load_models()
343
  except Exception as e:
344
+ print(f"spaces.GPU 데코레이터 생성 중 오류: {e}")
 
345
  def initialize_models():
346
  return load_models()
347
 
 
 
348
  def get_models():
349
+ """모델을 불러오거나 이미 불러왔다면 반환"""
350
  global models, GPU_INITIALIZED
351
 
 
352
  model_loading_key = "__model_loading__"
353
 
354
  if not models:
 
355
  if model_loading_key in globals():
356
+ print("모델 로딩 중입니다. 대기 중...")
 
357
  import time
358
  start_wait = time.time()
359
  while not models and model_loading_key in globals():
360
  time.sleep(0.5)
 
361
  if time.time() - start_wait > 60:
362
+ print("모델 로딩 대기 시간 초과")
363
  break
364
 
365
  if models:
366
  return models
367
 
368
  try:
 
369
  globals()[model_loading_key] = True
370
 
371
  if IN_HF_SPACE and 'spaces' in globals() and GPU_AVAILABLE and not cpu_fallback_mode:
372
  try:
373
+ print("GPU 데코레이터(@spaces.GPU)로 모델 로딩 시도")
374
+ models_local = initialize_models()
375
+ models.update(models_local)
376
  except Exception as e:
377
+ print(f"GPU 데코레이터 로딩 실패: {e} / 직접 로딩 시도")
378
+ models_local = load_models()
379
+ models.update(models_local)
380
  else:
381
+ print("모델 직접 로딩 시도")
382
+ models_local = load_models()
383
+ models.update(models_local)
384
  except Exception as e:
385
+ print(f"모델 로드 중 오류: {e}")
386
  traceback.print_exc()
387
+ models.clear()
 
388
  finally:
 
389
  if model_loading_key in globals():
390
  del globals()[model_loading_key]
391
 
392
  return models
393
 
 
394
  stream = AsyncStream()
395
 
 
396
  @torch.no_grad()
397
  def worker(input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache):
398
  global last_update_time
399
  last_update_time = time.time()
400
 
 
401
  total_second_length = min(total_second_length, 5.0)
402
 
 
403
  try:
404
+ models_local = get_models()
405
+ if not models_local:
406
+ error_msg = "모델 로드에 실패했습니다. 로그를 확인하세요."
407
  print(error_msg)
408
  stream.output_queue.push(('error', error_msg))
409
  stream.output_queue.push(('end', None))
410
  return
411
 
412
+ text_encoder = models_local['text_encoder']
413
+ text_encoder_2 = models_local['text_encoder_2']
414
+ tokenizer = models_local['tokenizer']
415
+ tokenizer_2 = models_local['tokenizer_2']
416
+ vae = models_local['vae']
417
+ feature_extractor = models_local['feature_extractor']
418
+ image_encoder = models_local['image_encoder']
419
+ transformer = models_local['transformer']
420
  except Exception as e:
421
+ error_msg = f"모델 가져오기 실패: {e}"
422
  print(error_msg)
423
  traceback.print_exc()
424
  stream.output_queue.push(('error', error_msg))
425
  stream.output_queue.push(('end', None))
426
  return
427
 
 
428
  device = 'cuda' if GPU_AVAILABLE and not cpu_fallback_mode else 'cpu'
429
+ print(f"추론 디바이스: {device}")
430
+
 
431
  if cpu_fallback_mode:
432
+ print("CPU 모드에서 일부 파라미터를 축소합니다.")
 
433
  latent_window_size = min(latent_window_size, 5)
434
+ steps = min(steps, 15)
435
+ total_second_length = min(total_second_length, 2.0)
436
 
437
  total_latent_sections = (total_second_length * 30) / (latent_window_size * 4)
438
  total_latent_sections = int(max(round(total_latent_sections), 1))
 
446
  stream.output_queue.push(('progress', (None, '', make_progress_bar_html(0, 'Starting ...'))))
447
 
448
  try:
 
449
  if not high_vram and not cpu_fallback_mode:
450
  try:
451
  unload_complete_models(
452
  text_encoder, text_encoder_2, image_encoder, vae, transformer
453
  )
454
  except Exception as e:
455
+ print(f"모델 언로드 중 오류: {e}")
456
+
457
+ # 텍스트 인코딩
 
458
  last_update_time = time.time()
459
  stream.output_queue.push(('progress', (None, '', make_progress_bar_html(0, 'Text encoding ...'))))
460
 
 
473
  llama_vec, llama_attention_mask = crop_or_pad_yield_mask(llama_vec, length=512)
474
  llama_vec_n, llama_attention_mask_n = crop_or_pad_yield_mask(llama_vec_n, length=512)
475
  except Exception as e:
476
+ error_msg = f"텍스트 인코딩 오류: {e}"
477
  print(error_msg)
478
  traceback.print_exc()
479
  stream.output_queue.push(('error', error_msg))
480
  stream.output_queue.push(('end', None))
481
  return
482
 
483
+ # 입력 이미지 처리
484
  last_update_time = time.time()
485
  stream.output_queue.push(('progress', (None, '', make_progress_bar_html(0, 'Image processing ...'))))
486
 
 
488
  H, W, C = input_image.shape
489
  height, width = find_nearest_bucket(H, W, resolution=640)
490
 
 
491
  if cpu_fallback_mode:
492
  height = min(height, 320)
493
  width = min(width, 320)
494
 
495
  input_image_np = resize_and_center_crop(input_image, target_width=width, target_height=height)
 
496
  Image.fromarray(input_image_np).save(os.path.join(outputs_folder, f'{job_id}.png'))
497
 
498
  input_image_pt = torch.from_numpy(input_image_np).float() / 127.5 - 1
499
  input_image_pt = input_image_pt.permute(2, 0, 1)[None, :, None]
500
  except Exception as e:
501
+ error_msg = f"이미지 전처리 오류: {e}"
502
  print(error_msg)
503
  traceback.print_exc()
504
  stream.output_queue.push(('error', error_msg))
505
  stream.output_queue.push(('end', None))
506
  return
507
 
508
+ # VAE 인코딩
509
  last_update_time = time.time()
510
  stream.output_queue.push(('progress', (None, '', make_progress_bar_html(0, 'VAE encoding ...'))))
511
 
 
515
 
516
  start_latent = vae_encode(input_image_pt, vae)
517
  except Exception as e:
518
+ error_msg = f"VAE 인코딩 오류: {e}"
519
  print(error_msg)
520
  traceback.print_exc()
521
  stream.output_queue.push(('error', error_msg))
522
  stream.output_queue.push(('end', None))
523
  return
524
 
525
+ # CLIP Vision 인코딩
526
  last_update_time = time.time()
527
  stream.output_queue.push(('progress', (None, '', make_progress_bar_html(0, 'CLIP Vision encoding ...'))))
528
 
 
533
  image_encoder_output = hf_clip_vision_encode(input_image_np, feature_extractor, image_encoder)
534
  image_encoder_last_hidden_state = image_encoder_output.last_hidden_state
535
  except Exception as e:
536
+ error_msg = f"CLIP Vision 인코딩 오류: {e}"
537
  print(error_msg)
538
  traceback.print_exc()
539
  stream.output_queue.push(('error', error_msg))
540
  stream.output_queue.push(('end', None))
541
  return
542
 
543
+ # dtype 변환
544
  try:
545
  llama_vec = llama_vec.to(transformer.dtype)
546
  llama_vec_n = llama_vec_n.to(transformer.dtype)
 
548
  clip_l_pooler_n = clip_l_pooler_n.to(transformer.dtype)
549
  image_encoder_last_hidden_state = image_encoder_last_hidden_state.to(transformer.dtype)
550
  except Exception as e:
551
+ error_msg = f"dtype 변환 오류: {e}"
552
  print(error_msg)
553
  traceback.print_exc()
554
  stream.output_queue.push(('error', error_msg))
555
  stream.output_queue.push(('end', None))
556
  return
557
 
558
+ # 샘플링 진행
559
  last_update_time = time.time()
560
  stream.output_queue.push(('progress', (None, '', make_progress_bar_html(0, 'Start sampling ...'))))
561
 
 
567
  history_pixels = None
568
  total_generated_latent_frames = 0
569
  except Exception as e:
570
+ error_msg = f"히스토리 상태 초기화 오류: {e}"
571
  print(error_msg)
572
  traceback.print_exc()
573
  stream.output_queue.push(('error', error_msg))
 
575
  return
576
 
577
  latent_paddings = reversed(range(total_latent_sections))
 
578
  if total_latent_sections > 4:
579
+ latent_paddings = [3] + [2]*(total_latent_sections - 3) + [1, 0]
 
 
 
 
580
 
581
  for latent_padding in latent_paddings:
582
  last_update_time = time.time()
 
584
  latent_padding_size = latent_padding * latent_window_size
585
 
586
  if stream.input_queue.top() == 'end':
587
+ # 중단 신호 수신 시 부분 결과 반환
588
  if history_pixels is not None and total_generated_latent_frames > 0:
589
  try:
590
  output_filename = os.path.join(outputs_folder, f'{job_id}_final_{total_generated_latent_frames}.mp4')
591
  save_bcthw_as_mp4(history_pixels, output_filename, fps=30)
592
  stream.output_queue.push(('file', output_filename))
593
  except Exception as e:
594
+ print(f"마지막 비디오 저장 오류: {e}")
595
 
596
  stream.output_queue.push(('end', None))
597
  return
 
607
  clean_latents_post, clean_latents_2x, clean_latents_4x = history_latents[:, :, :1 + 2 + 16, :, :].split([1, 2, 16], dim=2)
608
  clean_latents = torch.cat([clean_latents_pre, clean_latents_post], dim=2)
609
  except Exception as e:
610
+ error_msg = f"샘플링 데이터 준비 오류: {e}"
611
  print(error_msg)
612
  traceback.print_exc()
 
613
  if last_output_filename:
614
  stream.output_queue.push(('file', last_output_filename))
615
  continue
 
619
  unload_complete_models()
620
  move_model_to_device_with_memory_preservation(transformer, target_device=device, preserved_memory_gb=gpu_memory_preservation)
621
  except Exception as e:
622
+ print(f"transformer GPU 이동 오류: {e}")
 
623
 
624
  if use_teacache and not cpu_fallback_mode:
625
  try:
626
  transformer.initialize_teacache(enable_teacache=True, num_steps=steps)
627
  except Exception as e:
628
+ print(f"teacache 초기화 오류: {e}")
 
629
  transformer.initialize_teacache(enable_teacache=False)
630
  else:
631
  transformer.initialize_teacache(enable_teacache=False)
 
635
  last_update_time = time.time()
636
 
637
  try:
638
+ if stream.input_queue.top() == 'end':
639
+ stream.output_queue.push(('end', None))
640
+ raise KeyboardInterrupt('사용자 중단 요청')
641
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
642
  preview = d['denoised']
643
  preview = vae_decode_fake(preview)
644
 
 
648
  current_step = d['i'] + 1
649
  percentage = int(100.0 * current_step / steps)
650
  hint = f'Sampling {current_step}/{steps}'
651
+ desc = f'Total generated frames: {int(max(0, total_generated_latent_frames * 4 - 3))}, Video length: {max(0, (total_generated_latent_frames * 4 - 3) / 30) :.2f} seconds (FPS-30).'
652
  stream.output_queue.push(('progress', (preview, desc, make_progress_bar_html(percentage, hint))))
653
+ except KeyboardInterrupt:
 
 
 
654
  raise
655
  except Exception as e:
656
+ print(f"콜백 중 오류: {e}")
 
 
657
  return
658
 
659
  try:
660
  sampling_start_time = time.time()
661
+ print(f"샘플링 시작, device: {device}, dtype: {transformer.dtype}, TeaCache: {use_teacache and not cpu_fallback_mode}")
662
 
663
  try:
 
664
  generated_latents = sample_hunyuan(
665
  transformer=transformer,
666
  sampler='unipc',
 
670
  real_guidance_scale=cfg,
671
  distilled_guidance_scale=gs,
672
  guidance_rescale=rs,
 
673
  num_inference_steps=steps,
674
  generator=rnd,
675
  prompt_embeds=llama_vec,
 
691
  callback=callback,
692
  )
693
 
694
+ print(f"샘플링 완료. 소요 시간: {time.time() - sampling_start_time:.2f}")
695
  except KeyboardInterrupt as e:
696
+ print(f"사용자 중단: {e}")
 
 
 
 
697
  if last_output_filename:
 
698
  stream.output_queue.push(('file', last_output_filename))
699
+ error_msg = "사용자에 의해 중단되었지만, 일부 비디오가 생성되었습니다."
700
  else:
701
+ error_msg = "사용자에 의해 중단되었습니다. 비디오가 생성되지 않았습니다."
 
702
 
 
703
  stream.output_queue.push(('error', error_msg))
 
704
  stream.output_queue.push(('end', None))
 
705
  return
706
  except Exception as e:
707
+ print(f"샘플링 중 오류: {e}")
708
  traceback.print_exc()
 
 
709
  if last_output_filename:
710
  stream.output_queue.push(('file', last_output_filename))
711
+ error_msg = f"샘플링 중 오류(일부 비디오 생성됨): {e}"
 
 
712
  stream.output_queue.push(('error', error_msg))
713
  else:
714
+ error_msg = f"샘플링 중 오류: {e}"
 
715
  stream.output_queue.push(('error', error_msg))
 
716
  stream.output_queue.push(('end', None))
717
  return
718
 
 
723
  total_generated_latent_frames += int(generated_latents.shape[2])
724
  history_latents = torch.cat([generated_latents.to(history_latents), history_latents], dim=2)
725
  except Exception as e:
726
+ error_msg = f"생성된 잠재 변수 처리 오류: {e}"
727
  print(error_msg)
728
  traceback.print_exc()
 
729
  if last_output_filename:
730
  stream.output_queue.push(('file', last_output_filename))
731
  stream.output_queue.push(('error', error_msg))
 
737
  offload_model_from_device_for_memory_preservation(transformer, target_device=device, preserved_memory_gb=8)
738
  load_model_as_complete(vae, target_device=device)
739
  except Exception as e:
740
+ print(f"모델 메모리 관리 오류: {e}")
 
741
 
742
  try:
743
  real_history_latents = history_latents[:, :, :total_generated_latent_frames, :, :]
744
  except Exception as e:
745
+ error_msg = f"히스토리 잠재 변수 처리 오류: {e}"
746
  print(error_msg)
 
747
  if last_output_filename:
748
  stream.output_queue.push(('file', last_output_filename))
749
  continue
750
 
751
  try:
752
  vae_start_time = time.time()
753
+ print(f"VAE 디코딩 시작, 잠재 변수 크기: {real_history_latents.shape}")
754
+
755
  if history_pixels is None:
756
  history_pixels = vae_decode(real_history_latents, vae).cpu()
757
  else:
 
761
  current_pixels = vae_decode(real_history_latents[:, :, :section_latent_frames], vae).cpu()
762
  history_pixels = soft_append_bcthw(current_pixels, history_pixels, overlapped_frames)
763
 
764
+ print(f"VAE 디코딩 완료, 소요 시간: {time.time() - vae_start_time:.2f}")
765
+
766
  if not high_vram and not cpu_fallback_mode:
767
  try:
768
  unload_complete_models()
769
  except Exception as e:
770
+ print(f"모델 언로드 중 오류: {e}")
771
 
772
  output_filename = os.path.join(outputs_folder, f'{job_id}_{total_generated_latent_frames}.mp4')
773
 
774
  save_start_time = time.time()
775
  save_bcthw_as_mp4(history_pixels, output_filename, fps=30)
776
+ print(f"비디오 저장 완료, 소요 시간: {time.time() - save_start_time:.2f}")
777
 
778
+ print(f'디코딩 완료. 현재 latent 크기: {real_history_latents.shape}, pixel 크기: {history_pixels.shape}')
779
 
780
  last_output_filename = output_filename
781
  stream.output_queue.push(('file', output_filename))
782
  except Exception as e:
783
+ print(f"비디오 디코딩/저장 중 오류: {e}")
784
  traceback.print_exc()
 
 
785
  if last_output_filename:
786
  stream.output_queue.push(('file', last_output_filename))
787
+ error_msg = f"비디오 디코딩/저장 오류: {e}"
 
 
788
  stream.output_queue.push(('error', error_msg))
 
 
789
  continue
790
 
791
  if is_last_section:
792
  break
793
  except Exception as e:
794
+ print(f"처리 중 오류 발생: {e} (type: {type(e)})")
 
795
  traceback.print_exc()
796
 
 
797
  if isinstance(e, KeyboardInterrupt):
798
+ print("KeyboardInterrupt 발생")
799
 
800
  if not high_vram and not cpu_fallback_mode:
801
  try:
 
802
  unload_complete_models(
803
  text_encoder, text_encoder_2, image_encoder, vae, transformer
804
  )
 
805
  except Exception as unload_error:
806
+ print(f"언로드 오류: {unload_error}")
 
807
 
 
808
  if last_output_filename:
 
809
  stream.output_queue.push(('file', last_output_filename))
 
 
810
 
811
+ error_msg = f"처리 중 오류: {e}"
 
 
812
  stream.output_queue.push(('error', error_msg))
813
 
814
+ print("worker 함수 종료, 'end' 신호 전송")
 
815
  stream.output_queue.push(('end', None))
816
  return
817
 
 
 
818
  if IN_HF_SPACE and 'spaces' in globals():
819
  @spaces.GPU
820
  def process_with_gpu(input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache):
821
  global stream
822
  assert input_image is not None, 'No input image!'
823
 
 
824
  yield None, None, '', '', gr.update(interactive=False), gr.update(interactive=True)
825
 
826
  try:
827
  stream = AsyncStream()
 
 
828
  async_run(worker, input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache)
829
 
830
  output_filename = None
831
  prev_output_filename = None
832
  error_message = None
833
 
 
834
  while True:
835
  try:
836
  flag, data = stream.output_queue.next()
 
838
  if flag == 'file':
839
  output_filename = data
840
  prev_output_filename = output_filename
 
841
  yield output_filename, gr.update(), gr.update(), '', gr.update(interactive=False), gr.update(interactive=True)
842
 
843
  if flag == 'progress':
844
  preview, desc, html = data
 
845
  yield gr.update(), gr.update(visible=True, value=preview), desc, html, gr.update(interactive=False), gr.update(interactive=True)
846
 
847
  if flag == 'error':
848
  error_message = data
849
+ print(f"오류 메시지 수신: {error_message}")
 
850
 
851
  if flag == 'end':
 
852
  if output_filename is None and prev_output_filename is not None:
853
  output_filename = prev_output_filename
854
 
 
855
  if error_message:
856
  error_html = create_error_html(error_message)
857
  yield output_filename, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
858
  else:
 
859
  yield output_filename, gr.update(visible=False), gr.update(), '', gr.update(interactive=True), gr.update(interactive=False)
860
  break
861
  except Exception as e:
862
+ print(f"출력 처리 중 오류: {e}")
 
863
  current_time = time.time()
864
+ if current_time - last_update_time > 60:
865
+ print(f"처리가 {current_time - last_update_time:.1f} 동안 정지됨. 타임아웃으로 간주.")
 
 
866
  if prev_output_filename:
867
+ error_html = create_error_html("처리 시간이 초과되었지만 일부 동영상이 생성되었습니다.", is_timeout=True)
868
  yield prev_output_filename, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
869
  else:
870
+ error_html = create_error_html(f"처리 시간 초과: {e}", is_timeout=True)
871
  yield None, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
872
  break
 
873
  except Exception as e:
874
+ print(f"프로세스 시작 오류: {e}")
875
  traceback.print_exc()
876
  error_msg = str(e)
877
 
 
884
  global stream
885
  assert input_image is not None, 'No input image!'
886
 
 
887
  yield None, None, '', '', gr.update(interactive=False), gr.update(interactive=True)
888
 
889
  try:
890
  stream = AsyncStream()
 
 
891
  async_run(worker, input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache)
892
 
893
  output_filename = None
894
  prev_output_filename = None
895
  error_message = None
896
 
 
897
  while True:
898
  try:
899
  flag, data = stream.output_queue.next()
 
901
  if flag == 'file':
902
  output_filename = data
903
  prev_output_filename = output_filename
 
904
  yield output_filename, gr.update(), gr.update(), '', gr.update(interactive=False), gr.update(interactive=True)
905
 
906
  if flag == 'progress':
907
  preview, desc, html = data
 
908
  yield gr.update(), gr.update(visible=True, value=preview), desc, html, gr.update(interactive=False), gr.update(interactive=True)
909
 
910
  if flag == 'error':
911
  error_message = data
912
+ print(f"오류 메시지 수신: {error_message}")
 
913
 
914
  if flag == 'end':
 
915
  if output_filename is None and prev_output_filename is not None:
916
  output_filename = prev_output_filename
917
 
 
918
  if error_message:
919
  error_html = create_error_html(error_message)
920
  yield output_filename, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
921
  else:
 
922
  yield output_filename, gr.update(visible=False), gr.update(), '', gr.update(interactive=True), gr.update(interactive=False)
923
  break
924
  except Exception as e:
925
+ print(f"출력 처리 중 오류: {e}")
 
926
  current_time = time.time()
927
+ if current_time - last_update_time > 60:
928
+ print(f"{current_time - last_update_time:.1f} 동안 진행이 없어 타임아웃으로 간주합니다.")
 
 
929
  if prev_output_filename:
930
+ error_html = create_error_html("처리 시간이 초과되었지만 일부 동영상이 생성되었습니다.", is_timeout=True)
931
  yield prev_output_filename, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
932
  else:
933
+ error_html = create_error_html(f"처리 시간 초과: {e}", is_timeout=True)
934
  yield None, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
935
  break
 
936
  except Exception as e:
937
+ print(f"프로세스 시작 오류: {e}")
938
  traceback.print_exc()
939
  error_msg = str(e)
940
 
941
  error_html = create_error_html(error_msg)
942
  yield None, gr.update(visible=False), gr.update(), error_html, gr.update(interactive=True), gr.update(interactive=False)
943
 
 
944
  def end_process():
945
+ print("사용자가 중지 버튼을 눌렀습니다. 종료 신호를 보냅니다...")
 
 
946
  if 'stream' in globals() and stream is not None:
 
947
  try:
948
  current_top = stream.input_queue.top()
949
+ print(f"현재 입력 큐 top: {current_top}")
950
  except Exception as e:
951
+ print(f"입력 큐 확인 오류: {e}")
 
 
952
  try:
953
  stream.input_queue.push('end')
954
+ print("end 신호 전송 완료")
 
 
955
  try:
956
  current_top_after = stream.input_queue.top()
957
+ print(f"신호 전송 후 입력 큐 top: {current_top_after}")
958
  except Exception as e:
959
+ print(f"신호 전송 후 큐 상태 확인 오류: {e}")
 
960
  except Exception as e:
961
+ print(f"end 신호 전송 오류: {e}")
962
  else:
963
+ print("stream이 초기화되지 않아 종료 신호를 보낼 수 없습니다.")
964
  return None
965
 
 
966
  quick_prompts = [
967
  'The girl dances gracefully, with clear movements, full of charm.',
968
  'A character doing some simple body movements.',
969
  ]
970
  quick_prompts = [[x] for x in quick_prompts]
971
 
 
 
972
  def make_custom_css():
973
  progress_bar_css = make_progress_bar_css()
974
 
975
  responsive_css = """
976
+ /* progress_bar_css로부터 불러온 기본 설정 + 추가 */
977
+
978
  #app-container {
979
  max-width: 100%;
980
  margin: 0 auto;
981
  }
982
 
 
983
  #language-toggle {
984
  position: fixed;
985
  top: 10px;
 
994
  font-size: 14px;
995
  }
996
 
 
997
  h1 {
998
  font-size: 2rem;
999
  text-align: center;
1000
  margin-bottom: 1rem;
1001
  }
1002
 
 
1003
  .start-btn, .stop-btn {
1004
  min-height: 45px;
1005
  font-size: 1rem;
1006
  }
1007
 
 
1008
  @media (max-width: 768px) {
1009
  h1 {
1010
  font-size: 1.5rem;
1011
  margin-bottom: 0.5rem;
1012
  }
1013
 
 
1014
  .mobile-full-width {
1015
  flex-direction: column !important;
1016
  }
 
1020
  flex-grow: 1;
1021
  }
1022
 
 
1023
  .video-container {
1024
  height: auto !important;
1025
  }
1026
 
 
1027
  .button-container button {
1028
  min-height: 50px;
1029
  font-size: 1rem;
1030
  touch-action: manipulation;
1031
  }
1032
 
 
1033
  .slider-container input[type="range"] {
1034
  height: 30px;
1035
  }
1036
  }
1037
 
 
1038
  @media (min-width: 769px) and (max-width: 1024px) {
1039
  .tablet-adjust {
1040
  width: 48% !important;
1041
  }
1042
  }
1043
 
 
1044
  @media (prefers-color-scheme: dark) {
1045
  .dark-mode-text {
1046
  color: #f0f0f0;
1047
  }
 
1048
  .dark-mode-bg {
1049
  background-color: #2a2a2a;
1050
  }
1051
  }
1052
 
 
1053
  button, input, select, textarea {
1054
+ font-size: 16px;
1055
  }
1056
 
 
1057
  button, .interactive-element {
1058
  min-height: 44px;
1059
  min-width: 44px;
1060
  }
1061
 
 
1062
  .high-contrast {
1063
  color: #fff;
1064
  background-color: #000;
1065
  }
1066
 
 
1067
  .progress-container {
1068
  margin-top: 10px;
1069
  margin-bottom: 10px;
1070
  }
1071
 
 
1072
  #error-message {
1073
  color: #ff4444;
1074
  font-weight: bold;
 
1077
  margin-top: 10px;
1078
  }
1079
 
 
1080
  .error-message {
1081
  background-color: rgba(255, 0, 0, 0.1);
1082
  padding: 10px;
 
1085
  border: 1px solid #ffcccc;
1086
  }
1087
 
1088
+ .error-msg-en, .error-msg-ko {
 
1089
  font-weight: bold;
1090
  }
1091
 
 
1092
  .error-icon {
1093
  color: #ff4444;
1094
  font-size: 18px;
1095
  margin-right: 8px;
1096
  }
1097
 
 
1098
  #error-message:empty {
1099
  background-color: transparent;
1100
  border: none;
 
1102
  margin: 0;
1103
  }
1104
 
 
1105
  .error {
1106
  display: none !important;
1107
  }
1108
  """
1109
 
1110
+ return progress_bar_css + responsive_css
 
 
 
1111
 
1112
  css = make_custom_css()
1113
  block = gr.Blocks(css=css).queue()
1114
  with block:
 
1115
  gr.HTML("""
1116
  <div id="app-container">
1117
+ <button id="language-toggle" onclick="toggleLanguage()">한국어 / English</button>
1118
  </div>
1119
  <script>
 
1120
  window.currentLang = "en";
 
 
1121
  function toggleLanguage() {
1122
+ window.currentLang = (window.currentLang === "en") ? "ko" : "en";
1123
 
 
1124
  const elements = document.querySelectorAll('[data-i18n]');
 
 
1125
  elements.forEach(el => {
1126
  const key = el.getAttribute('data-i18n');
1127
  const translations = {
 
1149
  "next_latents": "Next Latents",
1150
  "generated_video": "Generated Video",
1151
  "sampling_note": "Note: Due to reversed sampling, ending actions will be generated before starting actions. If the starting action is not in the video, please wait, it will be generated later.",
1152
+ "error_message": "Error"
 
 
 
 
 
 
1153
  },
1154
+ "ko": {
1155
+ "title": "FramePack - 이미지에서 동영상 생성",
1156
+ "upload_image": "이미지 업로드",
1157
+ "prompt": "프롬프트",
1158
+ "quick_prompts": "빠른 프롬프트 목록",
1159
+ "start_generation": "생성 시작",
1160
+ "stop_generation": "생성 중지",
1161
+ "use_teacache": "TeaCache 사용",
1162
+ "teacache_info": "더 빠른 속도를 제공하지만 손가락이나 손 생성 품질이 약간 떨어질 수 있습니다.",
1163
+ "negative_prompt": "부정 프롬프트",
1164
+ "seed": "랜덤 시드",
1165
+ "video_length": "동영상 길이 (최대 5)",
1166
+ "latent_window": "잠재 윈도우 크기",
1167
+ "steps": "추론 스텝 수",
1168
+ "steps_info": "이 값을 변경하는 것은 권장되지 않습니다.",
1169
+ "cfg_scale": "CFG 스케일",
1170
+ "distilled_cfg": "증류된 CFG 스케일",
1171
+ "distilled_cfg_info": "이 값을 변경하는 것은 권장되지 않습니다.",
1172
+ "cfg_rescale": "CFG 재스케일",
1173
+ "gpu_memory": "GPU 메모리 보존 (GB) (값이 클수록 속도가 느려짐)",
1174
+ "gpu_memory_info": "OOM 오류가 발생하면 이 값을 더 크게 설정하십시오. 값이 클수록 속도가 느려집니다.",
1175
+ "next_latents": "다음 잠재 변수",
1176
+ "generated_video": "생성된 동영상",
1177
+ "sampling_note": "주의: 역순 샘플링 때문에, 종료 동작이 시작 동작보다 먼저 생성됩니다. 시작 동작이 나타나지 않으면 기다려 주십시오.",
1178
+ "error_message": "오류 메시지"
 
 
 
 
 
 
1179
  }
1180
  };
1181
 
1182
  if (translations[window.currentLang] && translations[window.currentLang][key]) {
 
1183
  if (el.tagName === 'BUTTON') {
1184
  el.textContent = translations[window.currentLang][key];
1185
  } else if (el.tagName === 'LABEL') {
 
1190
  }
1191
  });
1192
 
1193
+ // bilingual-label 처리
1194
  document.querySelectorAll('.bilingual-label').forEach(el => {
1195
  const enText = el.getAttribute('data-en');
1196
+ const koText = el.getAttribute('data-ko');
1197
+ el.textContent = (window.currentLang === 'en') ? enText : koText;
1198
  });
1199
 
1200
+ // data-lang 처리
1201
  document.querySelectorAll('[data-lang]').forEach(el => {
1202
+ el.style.display = (el.getAttribute('data-lang') === window.currentLang) ? 'block' : 'none';
1203
  });
1204
  }
1205
 
 
1206
  document.addEventListener('DOMContentLoaded', function() {
 
1207
  setTimeout(() => {
1208
+ // 매핑
1209
  const labelMap = {
1210
  "Upload Image": "upload_image",
1211
+ "이미지 업로드": "upload_image",
1212
  "Prompt": "prompt",
1213
+ "프롬프트": "prompt",
1214
  "Quick Prompts": "quick_prompts",
1215
+ "빠른 ��롬프트 목록": "quick_prompts",
1216
+ "Generate": "start_generation",
1217
+ "생성 시작": "start_generation",
1218
  "Stop": "stop_generation",
1219
+ "생성 중지": "stop_generation"
 
1220
  };
1221
 
 
1222
  document.querySelectorAll('label, span, button').forEach(el => {
1223
  const text = el.textContent.trim();
1224
  if (labelMap[text]) {
 
1226
  }
1227
  });
1228
 
 
1229
  const titleEl = document.querySelector('h1');
1230
  if (titleEl) titleEl.setAttribute('data-i18n', 'title');
1231
 
 
1232
  toggleLanguage();
1233
  }, 1000);
1234
  });
1235
  </script>
1236
  """)
1237
+
1238
+ gr.HTML("<h1 data-i18n='title'>FramePack - Image to Video Generation</h1>")
1239
 
 
 
 
 
1240
  with gr.Row(elem_classes="mobile-full-width"):
1241
  with gr.Column(scale=1, elem_classes="mobile-full-width"):
 
1242
  input_image = gr.Image(
1243
  sources='upload',
1244
  type="numpy",
1245
+ label="Upload Image",
1246
  elem_id="input-image",
1247
  height=320
1248
  )
1249
 
 
1250
  prompt = gr.Textbox(
1251
+ label="Prompt",
1252
  value='',
1253
  elem_id="prompt-input"
1254
  )
1255
 
 
1256
  example_quick_prompts = gr.Dataset(
1257
  samples=quick_prompts,
1258
+ label='Quick Prompts',
1259
  samples_per_page=1000,
1260
  components=[prompt]
1261
  )
1262
+ example_quick_prompts.click(
1263
+ lambda x: x[0],
1264
+ inputs=[example_quick_prompts],
1265
+ outputs=prompt,
1266
+ show_progress=False,
1267
+ queue=False
1268
+ )
1269
 
 
1270
  with gr.Row(elem_classes="button-container"):
1271
  start_button = gr.Button(
1272
+ value="Generate",
1273
  elem_classes="start-btn",
1274
  elem_id="start-button",
1275
  variant="primary"
1276
  )
1277
 
1278
  end_button = gr.Button(
1279
+ value="Stop",
1280
  elem_classes="stop-btn",
1281
  elem_id="stop-button",
1282
  interactive=False
1283
  )
1284
 
 
1285
  with gr.Group():
1286
  use_teacache = gr.Checkbox(
1287
+ label='Use TeaCache',
1288
  value=True,
1289
+ info='Faster speed, but may result in slightly worse finger and hand generation.'
1290
  )
1291
 
1292
+ n_prompt = gr.Textbox(label="Negative Prompt", value="", visible=False)
1293
 
1294
  seed = gr.Number(
1295
+ label="Seed",
1296
  value=31337,
1297
  precision=0
1298
  )
1299
+
 
1300
  with gr.Group(elem_classes="slider-container"):
1301
  total_second_length = gr.Slider(
1302
+ label="Video Length (max 5 seconds)",
1303
  minimum=1,
1304
  maximum=5,
1305
  value=5,
 
1307
  )
1308
 
1309
  latent_window_size = gr.Slider(
1310
+ label="Latent Window Size",
1311
  minimum=1,
1312
  maximum=33,
1313
  value=9,
 
1316
  )
1317
 
1318
  steps = gr.Slider(
1319
+ label="Inference Steps",
1320
  minimum=1,
1321
  maximum=100,
1322
  value=25,
1323
  step=1,
1324
+ info='Changing this value is not recommended.'
1325
  )
1326
 
1327
  cfg = gr.Slider(
 
1334
  )
1335
 
1336
  gs = gr.Slider(
1337
+ label="Distilled CFG Scale",
1338
  minimum=1.0,
1339
  maximum=32.0,
1340
  value=10.0,
1341
  step=0.01,
1342
+ info='Changing this value is not recommended.'
1343
  )
1344
 
1345
  rs = gr.Slider(
1346
+ label="CFG Rescale",
1347
  minimum=0.0,
1348
  maximum=1.0,
1349
  value=0.0,
 
1352
  )
1353
 
1354
  gpu_memory_preservation = gr.Slider(
1355
+ label="GPU Memory (GB)",
1356
  minimum=6,
1357
  maximum=128,
1358
  value=6,
1359
  step=0.1,
1360
+ info="Set this to a larger value if you encounter OOM errors. Larger values cause slower speed."
1361
  )
1362
 
 
1363
  with gr.Column(scale=1, elem_classes="mobile-full-width"):
 
1364
  preview_image = gr.Image(
1365
+ label="Preview",
1366
  height=200,
1367
  visible=False,
1368
  elem_classes="preview-container"
1369
  )
1370
 
 
1371
  result_video = gr.Video(
1372
+ label="Generated Video",
1373
  autoplay=True,
1374
+ show_share_button=True,
1375
  height=512,
1376
  loop=True,
1377
  elem_classes="video-container",
1378
  elem_id="result-video"
1379
  )
1380
 
1381
+ gr.HTML("<div data-i18n='sampling_note'>Note: Due to reversed sampling, ending actions will be generated before starting actions.</div>")
 
1382
 
 
1383
  with gr.Group(elem_classes="progress-container"):
1384
  progress_desc = gr.Markdown('', elem_classes='no-generating-animation')
1385
  progress_bar = gr.HTML('', elem_classes='no-generating-animation')
1386
 
 
1387
  error_message = gr.HTML('', elem_id='error-message', visible=True)
1388
 
 
1389
  ips = [input_image, prompt, n_prompt, seed, total_second_length, latent_window_size, steps, cfg, gs, rs, gpu_memory_preservation, use_teacache]
 
 
 
 
1390
 
1391
+ start_button.click(fn=process, inputs=ips, outputs=[
1392
+ result_video, preview_image, progress_desc, progress_bar, start_button, end_button
1393
+ ])
1394
+ end_button.click(fn=end_process)
1395
 
1396
+ block.launch()
1397
 
 
1398
  def create_error_html(error_msg, is_timeout=False):
 
 
1399
  en_msg = ""
1400
+ ko_msg = ""
1401
 
1402
  if is_timeout:
1403
+ if "부분" in error_msg or "partial" in error_msg:
1404
+ en_msg = "Processing timed out, but partial video has been generated."
1405
+ ko_msg = "처리 시간이 초과되었지만 일부 동영상이 생성되었습니다."
1406
+ else:
1407
+ en_msg = f"Processing timed out: {error_msg}"
1408
+ ko_msg = f"처리 시간 초과: {error_msg}"
1409
+ elif "모델 로드" in error_msg:
1410
+ en_msg = "Failed to load models. Possibly heavy traffic or GPU problem."
1411
+ ko_msg = "모델 로드에 실패했습니다. 과도한 트래픽 또는 GPU 문제일 수 있습니다."
1412
+ elif "GPU" in error_msg or "CUDA" in error_msg or "memory" in error_msg or "메모리" in error_msg:
1413
+ en_msg = "GPU memory insufficient or error. Increase GPU memory preservation or reduce video length."
1414
+ ko_msg = "GPU 메모리가 부족하거나 오류가 발생했습니다. GPU 메모리 보존 값을 늘리거나 동영상 길이를 줄여보세요."
1415
+ elif "샘플링 중 오류" in error_msg or "sampling process" in error_msg:
1416
+ if "부분" in error_msg or "partial" in error_msg:
1417
+ en_msg = "Error during sampling, but partial video has been generated."
1418
+ ko_msg = "샘플링 중 오류가 발생했지만 일부 동영상이 생성되었습니다."
1419
  else:
1420
+ en_msg = "Error during sampling. Unable to generate video."
1421
+ ko_msg = "샘플링 중 오류가 발생했습니다. 비디오 생성에 실패했습니다."
1422
+ elif "네트워크" in error_msg or "Network" in error_msg or "ConnectionError" in error_msg or "ReadTimeoutError" in error_msg:
1423
+ en_msg = "Network is unstable, model download timed out. Please try again later."
1424
+ ko_msg = "네트워크가 불안정하여 모델 다운로드가 시간 초과되었습니다. 잠시 후 다시 시도해 주세요."
1425
+ elif "VAE" in error_msg or "디코딩" in error_msg or "decode" in error_msg:
1426
+ en_msg = "Error during video decoding or saving process. Try a different seed."
1427
+ ko_msg = "비디오 디코딩/저장 중 오류가 발생했습니다. 다른 시드를 시도해보세요."
1428
  else:
1429
  en_msg = f"Processing error: {error_msg}"
1430
+ ko_msg = f"처리 중 오류가 발생했습니다: {error_msg}"
1431
 
 
1432
  return f"""
1433
  <div class="error-message" id="custom-error-container">
1434
  <div class="error-msg-en" data-lang="en">
1435
  <span class="error-icon">⚠️</span> {en_msg}
1436
  </div>
1437
+ <div class="error-msg-ko" data-lang="ko">
1438
+ <span class="error-icon">⚠️</span> {ko_msg}
1439
  </div>
1440
  </div>
1441
  <script>
 
1442
  (function() {{
1443
  const errorContainer = document.getElementById('custom-error-container');
1444
  if (errorContainer) {{
1445
+ const currentLang = window.currentLang || 'en';
1446
  const errMsgs = errorContainer.querySelectorAll('[data-lang]');
1447
  errMsgs.forEach(msg => {{
1448
+ msg.style.display = (msg.getAttribute('data-lang') === currentLang) ? 'block' : 'none';
1449
  }});
 
 
1450
  const defaultErrorElements = document.querySelectorAll('.error');
1451
  defaultErrorElements.forEach(el => {{
1452
  el.style.display = 'none';
 
1454
  }}
1455
  }})();
1456
  </script>
1457
+ """