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; }
/* centered, single-label API CTA */
.api-cta-wrap { text-align:center; margin-top:10px; }
.api-cta-hero {
display:inline-flex; align-items:center; gap:10px;
padding:10px 14px; border-radius:14px;
background: linear-gradient(90deg,#0ea5e9 0%, #a8a9de 100%);
color:#fff; font-weight:800; letter-spacing:0.1px;
box-shadow: 0 6px 22px rgba(99,102,241,0.35);
border: 1px solid rgba(255,255,255,0.22);
text-decoration:none;
}
.api-cta-hero:hover { filter:brightness(1.05); transform: translateY(-1px); transition: all .15s ease; }
.api-cta-hero .new {
background:#fff; color:#0ea5e9; font-weight:900;
padding:2px 8px; border-radius:999px; font-size:12px; line-height:1;
}
.api-cta-hero .txt { font-weight:800; }
.api-cta-hero .chev { opacity:.95; }
@media (max-width: 520px){
.api-cta-hero { padding:9px 12px; gap:8px; font-size:14px; }
.api-cta-hero .new { display:none; } /* keep it tidy on phones */
}
/* 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"
)
gr.HTML(
"""