import gradio as gr
import insightface
from insightface.app import FaceAnalysis
from PIL import Image
import numpy as np
# ---------- HTML description ----------
wellcomingMessage = """
Face Swap
by Tony Assi
"""
# ---------- Check version ----------
assert insightface.__version__ >= '0.7'
# ---------- Initialize models ----------
app = FaceAnalysis(name='buffalo_l')
app.prepare(ctx_id=0, det_size=(640, 640))
# ensure model downloads fully
swapper = insightface.model_zoo.get_model('inswapper_128.onnx', download=True, download_zip=True)
# ---------- Swap logic ----------
def swap_faces(src_img, dest_img):
# Pre-run nudge
gr.Warning(
'Skip the limits — HD, priority queue & no watermark at '
'face-swap.co'
)
if src_img is None or dest_img is None:
raise gr.Error("Please upload both a source and a target image.")
src_faces = app.get(src_img)
dest_faces = app.get(dest_img)
if len(src_faces) == 0 or len(dest_faces) == 0:
raise gr.Error("No faces detected in one of the images. Try clearer, front-facing photos.")
# Just swap first detected face from each image
source_face = src_faces[0]
dest_face = dest_faces[0]
result = swapper.get(dest_img, dest_face, source_face, paste_back=True)
out_img = Image.fromarray(np.uint8(result)).convert("RGB")
# Post-success promo
gr.Info(
'✨ Like this preview?
'
'Get HD face swaps (higher resolution, priority queue & no watermark) — '
'Upgrade on face-swap.co',
duration=8
)
return out_img
def open_side():
# for 4.x Sidebar API
return gr.Sidebar(open=True)
# ---------- Custom CSS ----------
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;
text-decoration: none;
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; }
}
/* 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,
#hero-md .prose h3 {
font-size: 2.1rem;
line-height: 1.2;
font-weight: 800;
margin-bottom: 0.25rem;
}
#hero-md p,
#hero-md .prose p {
font-size: 1.05rem;
}
"""
# ---------- UI (Blocks) ----------
# NOTE: no css= here – we inject CSS via ")
# Sticky banner
gr.HTML(
''
'⚡ Upgrade to HD — priority queue & higher resolution swaps!'
'GPU'
''
)
# Hero / intro
gr.Markdown(
f"""
### Image Face Swap (Preview)
[face-swap.co](https://www.face-swap.co/?utm_source=hfspace_faceswap&utm_medium=subtitle)
**Free preview** runs on CPU and may be limited in resolution to keep it fast.
Want full-quality **HD AI face swap** with GPU speed? **[Go Pro ↗](https://www.face-swap.co/?utm_source=hfspace_faceswap&utm_medium=go_pro)**
""",
elem_id="hero-md"
)
# API CTA
gr.HTML(
"""
"""
)
with gr.Row():
with gr.Column(scale=5):
src_img = gr.Image(type="numpy", label="Source Image (face to copy)")
dest_img = gr.Image(type="numpy", label="Target Image (face to replace)")
go = gr.Button("Swap Face", variant="primary")
pro = gr.Button("⚡ Upgrade to HD on face-swap.co", elem_classes=["upgrade-btn"])
with gr.Column(scale=5):
out_img = gr.Image(label="Result")
# Examples
gr.Examples(
examples=[["./Images/kim.jpg", "./Images/marilyn.jpg"]],
inputs=[src_img, dest_img],
outputs=[out_img],
fn=swap_faces,
cache_examples=True,
run_on_click=True,
label="Try an example"
)
# Sidebar CTA
with gr.Sidebar(open=False) as side:
gr.Markdown(
"### Upgrade to HD 1920x1080\n"
"- Higher resolution face swaps\n"
"- Priority queue\n"
"- API access & automation\n"
"- No watermark"
)
pro_pro = gr.Button("Open Pro Checkout", variant="primary")
pro_api = gr.Button("API Access", variant="primary")
# Floating bottom promo
gr.HTML(
''
)
# Open sidebar when user starts a swap
go.click(fn=open_side, inputs=None, outputs=side, queue=False)
# Main action
go.click(fn=swap_faces, inputs=[src_img, dest_img], outputs=out_img)
# JS-only Pro buttons
pro.click(
fn=None, inputs=None, outputs=None,
js="()=>window.open('https://www.face-swap.co/?utm_source=hfspace_faceswap&utm_medium=upgrade_to_hd','_blank')"
)
pro_pro.click(
fn=None, inputs=None, outputs=None,
js="()=>window.open('https://www.face-swap.co/?utm_source=hfspace_faceswap&utm_medium=sidebar_pro','_blank')"
)
pro_api.click(
fn=None, inputs=None, outputs=None,
js="()=>window.open('https://www.face-swap.co/api?utm_source=hfspace_faceswap&utm_medium=sidebar_api','_blank')"
)
demo.queue()
if __name__ == "__main__":
# If this line ever complains about theme=, just drop the arg:
# demo.launch()
demo.launch(theme=gr.themes.Soft())