|
|
import gradio as gr |
|
|
import insightface |
|
|
from insightface.app import FaceAnalysis |
|
|
from PIL import Image |
|
|
import numpy as np |
|
|
|
|
|
|
|
|
wellcomingMessage = """ |
|
|
<h1>Face Swap</h1> |
|
|
<p>by <a href="https://www.tonyassi.com/" target="_blank" style="color:#97d8be;">Tony Assi</a></p> |
|
|
<h2>Try out <a href="https://huggingface.co/spaces/tonyassi/video-face-swap" target="_blank">Video Face Swap</a> β€οΈ</h2> |
|
|
""" |
|
|
|
|
|
|
|
|
assert insightface.__version__ >= '0.7' |
|
|
|
|
|
|
|
|
app = FaceAnalysis(name='buffalo_l') |
|
|
app.prepare(ctx_id=0, det_size=(640, 640)) |
|
|
|
|
|
|
|
|
swapper = insightface.model_zoo.get_model('inswapper_128.onnx', download=True, download_zip=True) |
|
|
|
|
|
|
|
|
def swap_faces(src_img, dest_img): |
|
|
|
|
|
gr.Warning( |
|
|
'Skip the limits β HD, priority queue & no watermark at ' |
|
|
'<a href="https://www.face-swap.co/?utm_source=hfspace_faceswap&utm_medium=warning" ' |
|
|
'target="_blank" rel="noopener">face-swap.co</a>' |
|
|
) |
|
|
|
|
|
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.") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
gr.Info( |
|
|
'β¨ Like this preview?<br>' |
|
|
'<strong>Get HD face swaps</strong> (higher resolution, priority queue & no watermark) β ' |
|
|
'<a href="https://www.face-swap.co/?utm_source=hfspace_faceswap&utm_medium=info" ' |
|
|
'target="_blank" rel="noopener">Upgrade on face-swap.co</a>', |
|
|
duration=8 |
|
|
) |
|
|
|
|
|
return out_img |
|
|
|
|
|
def open_side(): |
|
|
|
|
|
return gr.Sidebar(open=True) |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks(title="Image Face Swap") as demo: |
|
|
|
|
|
gr.HTML(f"<style>{CUSTOM_CSS}</style>") |
|
|
|
|
|
|
|
|
gr.HTML( |
|
|
'<a class="sticky-cta" ' |
|
|
'href="https://www.face-swap.co/?utm_source=hfspace_faceswap&utm_medium=banner" ' |
|
|
'target="_blank" rel="noopener" aria-label="Upgrade to Pro on face-swap.co">' |
|
|
'β‘ <strong>Upgrade to HD</strong> β priority queue & higher resolution swaps!' |
|
|
'<span class="pill">GPU</span>' |
|
|
'</a>' |
|
|
) |
|
|
|
|
|
|
|
|
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" |
|
|
) |
|
|
|
|
|
|
|
|
gr.HTML( |
|
|
""" |
|
|
<div class="api-cta-wrap"> |
|
|
<a class="api-cta-hero" |
|
|
href="https://www.face-swap.co/api?utm_source=hfspace_faceswap&utm_medium=hero_api_new" |
|
|
target="_blank" rel="noopener" aria-label="Face Swap API"> |
|
|
<span class="new">NEW</span> |
|
|
<span class="txt">Face Swap API</span> |
|
|
<span class="chev">β</span> |
|
|
</a> |
|
|
</div> |
|
|
""" |
|
|
) |
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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" |
|
|
) |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
gr.HTML( |
|
|
'<div class="bottom-promo">' |
|
|
'Want HD & faster processing? ' |
|
|
'<a href="https://www.face-swap.co/?utm_source=hfspace_faceswap&utm_medium=upgrade" ' |
|
|
'target="_blank" rel="noopener">Upgrade</a>' |
|
|
'</div>' |
|
|
) |
|
|
|
|
|
|
|
|
go.click(fn=open_side, inputs=None, outputs=side, queue=False) |
|
|
|
|
|
|
|
|
go.click(fn=swap_faces, inputs=[src_img, dest_img], outputs=out_img) |
|
|
|
|
|
|
|
|
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__": |
|
|
|
|
|
|
|
|
demo.launch(theme=gr.themes.Soft()) |
|
|
|