dosesnrolls1 commited on
Commit
2bcf3ee
·
verified ·
1 Parent(s): 19f6b88

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +13 -159
app.py CHANGED
@@ -41,14 +41,22 @@ DEFAULT_FPS = 24.0
41
  DEFAULT_DURATION = 5.0
42
  DEFAULT_RESOLUTION = 768
43
 
 
 
 
 
 
 
 
 
44
  # ---------------------------------------------------------------------------
45
  # Core processing function
46
  # ---------------------------------------------------------------------------
47
 
48
  @GPU(duration=300)
49
  def generate(
50
- guide_video_path: str,
51
- face_image: Image.Image,
52
  prompt: str,
53
  duration: float,
54
  fps: float,
@@ -56,7 +64,7 @@ def generate(
56
  seed: int,
57
  hf_token: str = "",
58
  progress: gr.Progress = gr.Progress(track_tqdm=True),
59
- ) -> tuple[str, str]:
60
  """
61
  Full head-swap pipeline:
62
  1. Load + resize guide video frames
@@ -70,159 +78,5 @@ def generate(
70
  global _pipeline_state
71
 
72
  # ---- validate inputs early ----
73
- if guide_video_path is None:
74
- return "", "Please upload a guide video."
75
- if face_image is None:
76
- return "", "Please upload a reference face image."
77
- if not prompt.strip():
78
- return "", "Please enter a text prompt."
79
-
80
- # ---- lazy model load ----
81
- if _pipeline_state is None:
82
- from pipeline import load_pipeline
83
- progress(0, desc="Loading models (first run only — ~5 min)…")
84
- _pipeline_state = load_pipeline(
85
- token=hf_token.strip() or None,
86
- progress_cb=lambda msg: progress(0, desc=msg),
87
- )
88
-
89
- progress(0.05, desc="Loading guide video…")
90
- frames, source_fps = load_video_frames(guide_video_path)
91
- if len(frames) == 0:
92
- return "", "Could not read frames from the guide video."
93
-
94
- # ---- extract audio before we do anything else ----
95
- audio_tmp = tempfile.mktemp(suffix=".wav")
96
- has_audio = extract_audio(guide_video_path, audio_tmp)
97
-
98
- # ---- resize frames ----
99
- progress(0.10, desc="Resizing frames…")
100
- orig_h, orig_w = frames.shape[1], frames.shape[2]
101
- target_w, target_h = compute_target_size(orig_w, orig_h, DEFAULT_RESOLUTION)
102
- frames = resize_frames(frames, target_w, target_h)
103
-
104
- # ---- trim / pad to requested duration ----
105
- n_frames = frames_for_duration(fps, duration)
106
- if len(frames) >= n_frames:
107
- frames = frames[:n_frames]
108
- else:
109
- # loop last frame
110
- pad = np.stack([frames[-1]] * (n_frames - len(frames)))
111
- frames = np.concatenate([frames, pad], axis=0)
112
-
113
- # ---- compose chroma strip ----
114
- progress(0.15, desc="Compositing reference face strip…")
115
- composed = compose_frames(
116
- frames,
117
- face_image,
118
- region_position="left",
119
- region_size_px=REGION_SIZE,
120
- )
121
-
122
- # ---- run diffusion ----
123
- progress(0.20, desc="Running LTX-2.3 diffusion…")
124
- from pipeline import run_inference
125
- generated = run_inference(
126
- _pipeline_state,
127
- composed,
128
- prompt=prompt,
129
- fps=fps,
130
- lora_strength=lora_strength,
131
- seed=int(seed),
132
- progress_cb=lambda msg: progress(0.20, desc=msg),
133
- )
134
-
135
- # ---- crop face strip from output ----
136
- progress(0.90, desc="Cropping reserved region…")
137
- cropped = crop_reserved_region(
138
- generated,
139
- region_position="left",
140
- region_size_px=REGION_SIZE,
141
- output_size=(target_w, target_h),
142
- )
143
-
144
- # ---- save output video with audio ----
145
- progress(0.95, desc="Encoding output video…")
146
- out_path = tempfile.mktemp(suffix=".mp4")
147
- save_video(
148
- cropped,
149
- fps=fps,
150
- output_path=out_path,
151
- audio_path=audio_tmp if has_audio else None,
152
- audio_duration=duration,
153
- )
154
-
155
- progress(1.0, desc="Done.")
156
- return out_path, "Generation complete."
157
-
158
-
159
- # ---------------------------------------------------------------------------
160
- # Gradio UI
161
- # ---------------------------------------------------------------------------
162
-
163
- DESCRIPTION = """
164
- # BFS — Best Face Swap Video
165
-
166
- Swap the identity in any video using the **V3 persistent-template** technique.
167
- The reference face is placed in a green chroma side-strip that persists across
168
- all frames, giving the model continuous identity conditioning throughout generation.
169
-
170
- **Prompt format:**
171
- ```
172
- head_swap:
173
- FACE: Female, fair skin, ~25 years old, long wavy auburn hair, green eyes…
174
- ACTION: A person in a grey hoodie walks toward the camera indoors…
175
- ```
176
- """
177
-
178
- EXAMPLES: list[list] = [
179
- # [guide_video, face_image, prompt, duration, fps, lora_strength, seed]
180
- ]
181
-
182
- with gr.Blocks(title="BFS — Best Face Swap Video") as demo:
183
- gr.Markdown(DESCRIPTION)
184
-
185
- with gr.Row():
186
- with gr.Column(scale=1):
187
- guide_video = gr.Video(label="Guide Video", sources=["upload"])
188
- face_image = gr.Image(label="Reference Face", type="pil", sources=["upload"])
189
- prompt = gr.Textbox(
190
- label="Text Prompt",
191
- placeholder="head_swap:\nFACE: ...\nACTION: ...",
192
- lines=6,
193
- )
194
-
195
- with gr.Accordion("Parameters", open=False):
196
- duration = gr.Slider(1, 15, value=DEFAULT_DURATION, step=0.5, label="Duration (seconds)")
197
- fps = gr.Slider(8, 30, value=DEFAULT_FPS, step=1.0, label="FPS")
198
- lora_strength = gr.Slider(0.5, 1.5, value=1.2, step=0.05, label="Face Swap Strength")
199
- seed = gr.Number(value=42, label="Seed", precision=0)
200
- hf_token = gr.Textbox(
201
- label="HF Token (optional)",
202
- type="password",
203
- placeholder="hf_… — only needed if the Space owner's token has no access to a gated model",
204
- )
205
-
206
- run_btn = gr.Button("Generate", variant="primary")
207
-
208
- with gr.Column(scale=1):
209
- output_video = gr.Video(label="Result", interactive=False)
210
- status_text = gr.Textbox(label="Status", interactive=False)
211
-
212
- run_btn.click(
213
- fn=generate,
214
- inputs=[guide_video, face_image, prompt, duration, fps, lora_strength, seed, hf_token],
215
- outputs=[output_video, status_text],
216
- api_name=False,
217
- )
218
-
219
- gr.Markdown("""
220
- ---
221
- **Hardware:** A100 80 GB GPU required.
222
- **Model:** [Alissonerdx/BFS-Best-Face-Swap-Video](https://huggingface.co/Alissonerdx/BFS-Best-Face-Swap-Video) · Built on [LTX-2.3](https://huggingface.co/Lightricks/LTX-2.3)
223
- **License:** For research and professional VFX use only. You must have explicit consent for any likeness you process.
224
- """)
225
-
226
-
227
- if __name__ == "__main__":
228
- demo.launch()
 
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,
59
+ face_image: Image.Image | None,
60
  prompt: str,
61
  duration: float,
62
  fps: float,
 
64
  seed: int,
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
 
78
  global _pipeline_state
79
 
80
  # ---- validate inputs early ----
81
+ if not guide_video_path:
82
+ return None, "Please up