import gradio as gr
import os, uuid, time
from gradio_client import Client, handle_file
from moviepy.editor import VideoFileClip
# ── config ───────────────────────────────────────────────────────────
hf_token = os.environ.get("TOKEN")
output_dir = "uploads/output"
os.makedirs(output_dir, exist_ok=True)
client = Client("tonyassi/vfs2-cpu", hf_token=hf_token, download_files=output_dir)
UTM = "utm_source=hugging_face_space&utm_medium=banner&utm_campaign=pro_cta"
PRO_URL = f"https://www.face-swap.co/?{UTM}"
# ── helpers ──────────────────────────────────────────────────────────
def preprocess_video(path: str, target_fps: int = 12,
target_size: int = 800, target_len: int = 4) -> str:
clip = VideoFileClip(path)
if clip.duration > target_len:
clip = clip.subclip(0, target_len)
w, h = clip.size
clip = clip.resize(width=target_size) if w >= h else clip.resize(height=target_size)
clip = clip.set_fps(target_fps)
out_path = os.path.join(output_dir, f"pre_{uuid.uuid4().hex}.mp4")
clip.write_videofile(out_path, codec="libx264", audio_codec="aac",
fps=target_fps, verbose=False, logger=None)
clip.close()
return out_path
# ── main generate ────────────────────────────────────────────────────
def generate(input_image, input_video, gender):
# Pre-run nudge (small)
gr.Warning(
f'Skip the line — HD, no watermark, priority queue at '
f'face-swap.co'
)
if gender == "all":
gender = None
try:
pre_video = preprocess_video(input_video)
job = client.submit(
input_image=handle_file(input_image),
input_video={"video": handle_file(pre_video)},
device='cpu',
selector='many',
gender=gender,
race=None,
order=None,
api_name="/predict"
)
while not job.done():
time.sleep(5)
if not job.status().success:
return None
# Post-success modal (big)
gr.Info(
f"✨ Your preview is ready.
"
f"Get HD (4× quality, no watermark, priority) — "
f'Upgrade on face-swap.co',
duration=8
)
video_path = job.outputs()[0]["video"]
return video_path
except Exception as e:
gr.Error(f"Generation failed: {e}")
return None
def open_side(): # tiny helper
return gr.Sidebar(open=True)
# ── UI (Blocks) ──────────────────────────────────────────────────────
CUSTOM_CSS = """
.sticky-cta {
position: sticky; top: 0; z-index: 1000;
background: #a5b4fc;
color: #0f172a;
padding: 10px 14px;
text-align: center;
border-bottom: 1px solid #333;
display: block; /* full-width clickable */
text-decoration: none; /* remove underline */
cursor: pointer;
}
.sticky-cta:hover { filter: brightness(0.97); }
.sticky-cta .pill { background:#4f46e5; color:#fff; padding:4px 10px; border-radius:999px; margin-left:10px; }
.sticky-cta .cta-link { font-weight:600; text-decoration: underline; }
/* floating bottom promo */
.bottom-promo {
position: fixed; left: 50%; transform: translateX(-50%);
bottom: 16px; z-index: 1001; background:#0b0b0b; color:#fff;
border: 1px solid #2a2a2a; border-radius: 12px; padding: 10px 14px;
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}
.bottom-promo a { color:#4ea1ff; text-decoration:none; font-weight:600; }
/* big CTA button */
.upgrade-btn { width: 100%; font-size: 16px; padding: 10px 14px; }
/* hero markdown centering + larger heading */
#hero-md {
text-align: center;
}
#hero-md h3, /* standard markdown h3 */
#hero-md .prose h3 { /* some gradio themes wrap markdown with .prose */
font-size: 2.1rem; /* ~34px */
line-height: 1.2;
font-weight: 800;
margin-bottom: 0.25rem;
}
#hero-md p,
#hero-md .prose p {
font-size: 1.05rem; /* slightly larger body text */
}
"""
with gr.Blocks(title="Video Face Swap", theme=gr.themes.Soft(), css=CUSTOM_CSS) as demo:
# Sticky banner
gr.HTML(
f"""
⚡ Upgrade to HD — priority queue & no duration limit!
GPU
"""
)
gr.Markdown(
f"""
### Deep Fake Video (Preview)
[face-swap.co](https://www.face-swap.co/?utm_source=hfspace_deepfakevideo&utm_medium=subtitle)
**Free preview** is downsampled to 800px • 4s • 12fps to reduce wait time.
Want full-length **HD deep fake video** with GPU speed? **[Go Pro ↗](https://www.face-swap.co/?utm_source=hfspace_deepfakevideo&utm_medium=go_pro)**
""",
elem_id="hero-md"
)
with gr.Row():
with gr.Column(scale=5):
in_img = gr.Image(type="filepath", label="Source Image")
in_vid = gr.Video(label="Target Video")
in_gen = gr.Radio(choices=["all","female","male"], value="all", label="Gender")
go = gr.Button("Generate Preview", variant="primary")
pro = gr.Button("⚡ Upgrade to HD on face-swap.co", elem_classes=["upgrade-btn"])
with gr.Column(scale=5):
out_vid = gr.Video(label="Result")
gr.Examples(
examples=[["elon.png", "ironman.mp4", "all"], ["bella.jpg", "wizard.mp4", "all"]],
inputs=[in_img, in_vid, in_gen],
outputs=[out_vid],
fn=generate, # precompute + cache example output
cache_examples=True, # store the result on build so it loads instantly
run_on_click=True, # clicking the example triggers generate()
label="Try an example"
)
# Sidebar CTA
with gr.Sidebar(open=False) as side:
gr.Markdown("### Upgrade to HD 1920x1080\n- 4× quality\n- Priority queue\n- 1-5 minute video duration\n- GPU speed")
pro_paypergen = gr.Button("Pay per Generation", variant="primary")
pro_subscription = gr.Button("Monthly Subscription", variant="primary")
pro_api = gr.Button("Face Swap API", variant="primary")
# Floating bottom promo
gr.HTML(
f'