R1002 commited on
Commit
ac6ae89
·
verified ·
1 Parent(s): eb5b513

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +21 -24
app.py CHANGED
@@ -20,7 +20,7 @@ COMFY = Path("/app/ComfyUI")
20
  MODELS = COMFY / "models"
21
  OUTPUT = COMFY / "output"
22
 
23
- # ✅ Manifest สำหรับตรวจสอบว่าโมเดลครบ (Dockerfile ดาวน์โหลดไว้แล้วตอน Build)
24
  DOWNLOAD_MANIFEST = [
25
  {
26
  "file": "10Eros_v1-Q3_K_M.gguf",
@@ -60,7 +60,6 @@ DOWNLOAD_MANIFEST = [
60
  ]
61
 
62
  def _check_models(progress_cb=None):
63
- """ตรวจสอบว่าโมเดลที่ Docker ดาวน์โหลดไว้ครบหรือไม่"""
64
  if progress_cb:
65
  progress_cb(0.0, desc="Verifying models...")
66
  missing = []
@@ -208,8 +207,6 @@ NODE_LABELS = {
208
  "21": "Preprocessing image",
209
  "22": "I2V conditioning",
210
  "30": "Applying user LoRA",
211
- "40": "Encoding prompt",
212
- "41": "Encoding negative",
213
  }
214
 
215
  _comfy_proc = None
@@ -273,7 +270,6 @@ def _is_ic_lora(lora_path: str) -> bool:
273
  return False
274
  if lora_path in _ic_lora_cache:
275
  return _ic_lora_cache[lora_path]
276
-
277
  result = _detect_ic_lora(lora_path)
278
  _ic_lora_cache[lora_path] = result
279
  return result
@@ -281,14 +277,12 @@ def _is_ic_lora(lora_path: str) -> bool:
281
  def _detect_ic_lora(lora_path: str) -> bool:
282
  if re.search(r"ic[-_]?lora", lora_path, re.IGNORECASE):
283
  return True
284
-
285
  local = MODELS / "loras" / lora_path
286
  if local.exists():
287
  try:
288
  return _check_safetensors_header(str(local))
289
  except Exception:
290
  return False
291
-
292
  if "/" in lora_path:
293
  parts = lora_path.split("/")
294
  if len(parts) >= 3:
@@ -333,23 +327,33 @@ def _download_user_lora(repo_id: str, filename: str) -> str | None:
333
  return None
334
 
335
  def _build_workflow(prompt: str, steps: int, duration_sec: float, seed: int,
336
- img_name: str | None = None, user_lora: str | None = None,
337
- lora_strength: float = 0.6, vid_w: int | None = None,
338
- vid_h: int | None = None, enable_audio: bool = True) -> dict:
339
  wf = json.loads(json.dumps(WORKFLOW_TEMPLATE))
340
  wf["40"]["inputs"]["text"] = prompt
341
- frames = max(9, int(duration_sec * 24) + 1)
 
 
 
 
 
 
 
342
  wf["5"]["inputs"]["length"] = frames
 
 
 
 
343
  if not enable_audio:
344
- for n in ["49", "50", "51", "52", "53", "54"]:
345
- wf.pop(n, None)
346
- wf["11"]["inputs"]["latent_image"] = ["5", 0]
347
- wf["13"]["inputs"]["samples"] = ["11", 0]
348
  else:
349
  wf["50"]["inputs"]["frames_number"] = frames
 
350
  if vid_w and vid_h:
351
  wf["5"]["inputs"]["width"] = vid_w
352
  wf["5"]["inputs"]["height"] = vid_h
 
353
  wf["8"]["inputs"]["steps"] = steps
354
  wf["10"]["inputs"]["noise_seed"] = seed
355
 
@@ -563,7 +567,7 @@ def generate(prompt, duration_sec, steps, seed, image_path=None,
563
 
564
  if progress:
565
  progress(0.0, desc="Checking models...")
566
- _check_models(progress) # ✅ เปลี่ยนจาก _download_models เป็น _check_models
567
 
568
  if progress:
569
  progress(0.15, desc="Starting ComfyUI...")
@@ -659,6 +663,7 @@ def generate(prompt, duration_sec, steps, seed, image_path=None,
659
  print(f"[output] mp4 conversion failed: {e}, returning webp", flush=True)
660
  out_path = out_dir / "output.webp"
661
  shutil.copy2(result, out_path)
 
662
  elapsed = status_lines[0].split(":")[0] if ":" in status_lines[0] else "?"
663
  lora_info = f" | LoRA: {user_lora_file}" if user_lora_file else ""
664
  return str(out_path), f"Done {elapsed} | {mode} | {steps} steps | {duration_sec}s | seed {int(seed)}{lora_info}"
@@ -676,17 +681,14 @@ import gradio as gr
676
  import random
677
 
678
  _all_lora_choices = []
679
-
680
  _lora_state = {"mode": "search"}
681
 
682
  def _on_lora_interact(value):
683
  if not value or len(value) < 2:
684
  repos = _search_hf_loras("ltx 2.3 lora")
685
  return gr.update(choices=repos, value=None)
686
-
687
  if value.endswith(".safetensors"):
688
  return gr.update(value=value)
689
-
690
  if "/" in value:
691
  parts = value.split("/")
692
  if len(parts) >= 2:
@@ -702,7 +704,6 @@ def _on_lora_interact(value):
702
  if len(choices) == 1:
703
  return gr.update(choices=choices, value=choices[0])
704
  return gr.update(choices=choices, value=None)
705
-
706
  repos = _search_hf_loras(value)
707
  return gr.update(choices=repos, value=None)
708
 
@@ -760,10 +761,8 @@ with gr.Blocks(title="LTX 2.3 CPU") as demo:
760
  global _all_lora_choices
761
  selected = list(selected_values) if selected_values else []
762
  print(f"[lora] pick: {selected}", flush=True)
763
-
764
  valid = [v for v in selected if "/" in v]
765
  search_terms = [v for v in selected if "/" not in v and v.strip()]
766
-
767
  if search_terms:
768
  query = " ".join(search_terms)
769
  repos = _search_hf_loras(query)
@@ -781,7 +780,6 @@ with gr.Blocks(title="LTX 2.3 CPU") as demo:
781
  _all_lora_choices.append(r)
782
  print(f"[lora] search '{query}': {len(resolved)} new, {len(_all_lora_choices)} total", flush=True)
783
  return gr.update(choices=_all_lora_choices, value=valid[:9])
784
-
785
  if len(valid) > 9:
786
  valid = valid[:9]
787
  return gr.update(choices=_all_lora_choices, value=valid)
@@ -850,7 +848,6 @@ with gr.Blocks(title="LTX 2.3 CPU") as demo:
850
  fn=_gen,
851
  inputs=[prompt_in, image_in, lora_picker, lora_strength, audio_in, duration_in, steps_in, seed_in],
852
  outputs=[video_out, status_out],
853
- #api_name="generate",
854
  api_name="predict",
855
  )
856
  gr.Button(visible=False).click(fn=health, outputs=[gr.Textbox(visible=False)], api_name="health")
 
20
  MODELS = COMFY / "models"
21
  OUTPUT = COMFY / "output"
22
 
23
+ # ✅ Manifest สำหรับตรวจสอบว่าโมเดลครบ
24
  DOWNLOAD_MANIFEST = [
25
  {
26
  "file": "10Eros_v1-Q3_K_M.gguf",
 
60
  ]
61
 
62
  def _check_models(progress_cb=None):
 
63
  if progress_cb:
64
  progress_cb(0.0, desc="Verifying models...")
65
  missing = []
 
207
  "21": "Preprocessing image",
208
  "22": "I2V conditioning",
209
  "30": "Applying user LoRA",
 
 
210
  }
211
 
212
  _comfy_proc = None
 
270
  return False
271
  if lora_path in _ic_lora_cache:
272
  return _ic_lora_cache[lora_path]
 
273
  result = _detect_ic_lora(lora_path)
274
  _ic_lora_cache[lora_path] = result
275
  return result
 
277
  def _detect_ic_lora(lora_path: str) -> bool:
278
  if re.search(r"ic[-_]?lora", lora_path, re.IGNORECASE):
279
  return True
 
280
  local = MODELS / "loras" / lora_path
281
  if local.exists():
282
  try:
283
  return _check_safetensors_header(str(local))
284
  except Exception:
285
  return False
 
286
  if "/" in lora_path:
287
  parts = lora_path.split("/")
288
  if len(parts) >= 3:
 
327
  return None
328
 
329
  def _build_workflow(prompt: str, steps: int, duration_sec: float, seed: int,
330
+ img_name: str | None = None, user_lora: str | None = None,
331
+ lora_strength: float = 0.6, vid_w: int | None = None,
332
+ vid_h: int | None = None, enable_audio: bool = True) -> dict:
333
  wf = json.loads(json.dumps(WORKFLOW_TEMPLATE))
334
  wf["40"]["inputs"]["text"] = prompt
335
+
336
+ # 🔧 FIX 1: LTX Video ต้องการจำนวนเฟรมเป็นสูตร 8 * N + 1 เสมอ (เช่น 9, 17, 25, 33, 41, 49)
337
+ # หากคำนวณได้ค่าอื่น ระบบจะบีบอัด Latent แล้วทำให้มิติเวลา (T) กลายเป็น 0 และเกิด Error
338
+ raw_frames = int(float(duration_sec) * 24) + 1
339
+ frames = ((raw_frames - 1) // 8) * 8 + 1
340
+ frames = max(9, frames) # กำหนดขั้นต่ำที่ 9 เฟรม
341
+ print(f"[workflow] Calculated valid frames: {frames} (for {duration_sec}s duration)", flush=True)
342
+
343
  wf["5"]["inputs"]["length"] = frames
344
+
345
+ # 🔧 FIX 2: ไม่ลบ Node สร้าง Audio Latent (50, 51, 52, 53) ออกทั้งหมด
346
+ # เพราะโมเดล AV ต้องการโครงสร้าง Latent ที่ครบถ้วน หากขาดไปจะทำให้มิติ T=0 และ Crash
347
+ # แต่เราจะลบเฉพาะ Node Decode เสียง (54) ออกเพื่อประหยัดเวลา CPU ~4 ชั่วโมง
348
  if not enable_audio:
349
+ wf.pop("54", None) # ลบเฉพาะส่วน Decode เสียง
 
 
 
350
  else:
351
  wf["50"]["inputs"]["frames_number"] = frames
352
+
353
  if vid_w and vid_h:
354
  wf["5"]["inputs"]["width"] = vid_w
355
  wf["5"]["inputs"]["height"] = vid_h
356
+
357
  wf["8"]["inputs"]["steps"] = steps
358
  wf["10"]["inputs"]["noise_seed"] = seed
359
 
 
567
 
568
  if progress:
569
  progress(0.0, desc="Checking models...")
570
+ _check_models(progress)
571
 
572
  if progress:
573
  progress(0.15, desc="Starting ComfyUI...")
 
663
  print(f"[output] mp4 conversion failed: {e}, returning webp", flush=True)
664
  out_path = out_dir / "output.webp"
665
  shutil.copy2(result, out_path)
666
+
667
  elapsed = status_lines[0].split(":")[0] if ":" in status_lines[0] else "?"
668
  lora_info = f" | LoRA: {user_lora_file}" if user_lora_file else ""
669
  return str(out_path), f"Done {elapsed} | {mode} | {steps} steps | {duration_sec}s | seed {int(seed)}{lora_info}"
 
681
  import random
682
 
683
  _all_lora_choices = []
 
684
  _lora_state = {"mode": "search"}
685
 
686
  def _on_lora_interact(value):
687
  if not value or len(value) < 2:
688
  repos = _search_hf_loras("ltx 2.3 lora")
689
  return gr.update(choices=repos, value=None)
 
690
  if value.endswith(".safetensors"):
691
  return gr.update(value=value)
 
692
  if "/" in value:
693
  parts = value.split("/")
694
  if len(parts) >= 2:
 
704
  if len(choices) == 1:
705
  return gr.update(choices=choices, value=choices[0])
706
  return gr.update(choices=choices, value=None)
 
707
  repos = _search_hf_loras(value)
708
  return gr.update(choices=repos, value=None)
709
 
 
761
  global _all_lora_choices
762
  selected = list(selected_values) if selected_values else []
763
  print(f"[lora] pick: {selected}", flush=True)
 
764
  valid = [v for v in selected if "/" in v]
765
  search_terms = [v for v in selected if "/" not in v and v.strip()]
 
766
  if search_terms:
767
  query = " ".join(search_terms)
768
  repos = _search_hf_loras(query)
 
780
  _all_lora_choices.append(r)
781
  print(f"[lora] search '{query}': {len(resolved)} new, {len(_all_lora_choices)} total", flush=True)
782
  return gr.update(choices=_all_lora_choices, value=valid[:9])
 
783
  if len(valid) > 9:
784
  valid = valid[:9]
785
  return gr.update(choices=_all_lora_choices, value=valid)
 
848
  fn=_gen,
849
  inputs=[prompt_in, image_in, lora_picker, lora_strength, audio_in, duration_in, steps_in, seed_in],
850
  outputs=[video_out, status_out],
 
851
  api_name="predict",
852
  )
853
  gr.Button(visible=False).click(fn=health, outputs=[gr.Textbox(visible=False)], api_name="health")