dosesnrolls1 commited on
Commit
352dc0d
·
verified ·
1 Parent(s): 2bcf3ee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +86 -67
app.py CHANGED
@@ -1,58 +1,3 @@
1
- """
2
- BFS — Best Face Swap Video · Hugging Face Space
3
- """
4
-
5
- from __future__ import annotations
6
-
7
- import os
8
- import tempfile
9
-
10
- import gradio as gr
11
- import numpy as np
12
- from PIL import Image
13
-
14
- from composer import compose_frames, crop_reserved_region
15
- from video_utils import (
16
- compute_target_size,
17
- extract_audio,
18
- frames_for_duration,
19
- load_video_frames,
20
- resize_frames,
21
- save_video,
22
- )
23
-
24
- # ---------------------------------------------------------------------------
25
- # GPU decorator — no-op locally, activates the ZeroGPU grant on HF Spaces
26
- # ---------------------------------------------------------------------------
27
- try:
28
- import spaces
29
- GPU = spaces.GPU
30
- except ImportError:
31
- def GPU(fn=None, **kwargs): # type: ignore
32
- return fn if fn is not None else lambda f: f
33
-
34
- # ---------------------------------------------------------------------------
35
- # Global model state (loaded once per worker)
36
- # ---------------------------------------------------------------------------
37
- _pipeline_state: dict | None = None
38
-
39
- REGION_SIZE = 256
40
- DEFAULT_FPS = 24.0
41
- DEFAULT_DURATION = 5.0
42
- DEFAULT_RESOLUTION = 768
43
-
44
-
45
- def make_temp_file(suffix: str) -> str:
46
- f = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
47
- path = f.name
48
- f.close()
49
- return path
50
-
51
-
52
- # ---------------------------------------------------------------------------
53
- # Core processing function
54
- # ---------------------------------------------------------------------------
55
-
56
  @GPU(duration=300)
57
  def generate(
58
  guide_video_path: str | None,
@@ -65,18 +10,92 @@ def generate(
65
  hf_token: str = "",
66
  progress: gr.Progress = gr.Progress(track_tqdm=True),
67
  ) -> tuple[str | None, str]:
68
- """
69
- Full head-swap pipeline:
70
- 1. Load + resize guide video frames
71
- 2. Compose chroma face strip (ReservedRegionFrameComposer)
72
- 3. Run LTX-2.3 diffusion
73
- 4. Crop face strip from output
74
- 5. Mux original audio back in
75
-
76
- Returns (output_video_path, status_message).
77
- """
78
  global _pipeline_state
79
 
80
- # ---- validate inputs early ----
81
  if not guide_video_path:
82
- return None, "Please up
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  @GPU(duration=300)
2
  def generate(
3
  guide_video_path: str | None,
 
10
  hf_token: str = "",
11
  progress: gr.Progress = gr.Progress(track_tqdm=True),
12
  ) -> tuple[str | None, str]:
 
 
 
 
 
 
 
 
 
 
13
  global _pipeline_state
14
 
 
15
  if not guide_video_path:
16
+ return None, "Please upload a guide video."
17
+ if face_image is None:
18
+ return None, "Please upload a reference face image."
19
+ if not prompt or not prompt.strip():
20
+ return None, "Please enter a text prompt."
21
+
22
+ if not os.path.isfile(guide_video_path):
23
+ return None, f"Guide video path is not a real file: {guide_video_path}"
24
+
25
+ if _pipeline_state is None:
26
+ from pipeline import load_pipeline
27
+ progress(0, desc="Loading models (first run only — ~5 min)…")
28
+ _pipeline_state = load_pipeline(
29
+ token=hf_token.strip() or None,
30
+ progress_cb=lambda msg: progress(0, desc=msg),
31
+ )
32
+
33
+ progress(0.05, desc="Loading guide video…")
34
+ frames, source_fps = load_video_frames(guide_video_path)
35
+ if len(frames) == 0:
36
+ return None, "Could not read frames from the guide video."
37
+
38
+ audio_tmp = make_temp_file(".wav")
39
+ has_audio = extract_audio(guide_video_path, audio_tmp)
40
+
41
+ progress(0.10, desc="Resizing frames…")
42
+ orig_h, orig_w = frames.shape[1], frames.shape[2]
43
+ target_w, target_h = compute_target_size(orig_w, orig_h, DEFAULT_RESOLUTION)
44
+ frames = resize_frames(frames, target_w, target_h)
45
+
46
+ n_frames = frames_for_duration(fps, duration)
47
+ if len(frames) >= n_frames:
48
+ frames = frames[:n_frames]
49
+ else:
50
+ pad = np.stack([frames[-1]] * (n_frames - len(frames)))
51
+ frames = np.concatenate([frames, pad], axis=0)
52
+
53
+ progress(0.15, desc="Compositing reference face strip…")
54
+ composed = compose_frames(
55
+ frames,
56
+ face_image,
57
+ region_position="left",
58
+ region_size_px=REGION_SIZE,
59
+ )
60
+
61
+ progress(0.20, desc="Running LTX-2.3 diffusion…")
62
+ from pipeline import run_inference
63
+ generated = run_inference(
64
+ _pipeline_state,
65
+ composed,
66
+ prompt=prompt,
67
+ fps=fps,
68
+ lora_strength=lora_strength,
69
+ seed=int(seed),
70
+ progress_cb=lambda msg: progress(0.20, desc=msg),
71
+ )
72
+
73
+ progress(0.90, desc="Cropping reserved region…")
74
+ cropped = crop_reserved_region(
75
+ generated,
76
+ region_position="left",
77
+ region_size_px=REGION_SIZE,
78
+ output_size=(target_w, target_h),
79
+ )
80
+
81
+ progress(0.95, desc="Encoding output video…")
82
+ out_path = make_temp_file(".mp4")
83
+ save_video(
84
+ cropped,
85
+ fps=fps,
86
+ output_path=out_path,
87
+ audio_path=audio_tmp if has_audio else None,
88
+ audio_duration=duration,
89
+ )
90
+
91
+ if not os.path.isfile(out_path):
92
+ return None, f"Output file was not created: {out_path}"
93
+
94
+ progress(1.0, desc="Done.")
95
+ return out_path, "Generation complete."
96
+
97
+ guide_video = gr.Video(
98
+ label="Guide Video",
99
+ sources=["upload"],
100
+ type="filepath",
101
+ )