File size: 9,786 Bytes
069a3eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cf35b2e
069a3eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cf35b2e
069a3eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363e716
069a3eb
363e716
 
 
069a3eb
363e716
 
 
0f2cd7f
 
3381593
 
363e716
3381593
 
ad858eb
3381593
 
 
 
0f2cd7f
3381593
069a3eb
3381593
 
 
 
 
 
 
 
 
 
069a3eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363e716
 
 
 
 
069a3eb
 
 
 
 
 
cf35b2e
069a3eb
 
cf35b2e
069a3eb
 
 
 
363e716
 
 
 
3381593
363e716
3381593
ad858eb
3381593
363e716
 
 
 
 
069a3eb
3381593
069a3eb
 
 
 
 
 
 
 
 
 
 
 
 
 
dd69671
069a3eb
 
 
 
 
 
 
 
 
 
 
2400722
 
 
f9b1408
069a3eb
 
 
 
cf35b2e
069a3eb
 
 
 
 
 
 
 
 
 
 
cf35b2e
2400722
 
 
 
f9b1408
e6ac4f5
069a3eb
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
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'<a href="https://www.face-swap.co/?utm_source=hfspace_videofaceswap&utm_medium=warning" target="_blank" rel="noopener">face-swap.co</a>'
    )

    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.<br>"
            f"<strong>Get HD</strong> (4Γ— quality, no watermark, priority) β€” "
            f'<a href="https://www.face-swap.co/?utm_source=hfspace_videofaceswap&utm_medium=info" target="_blank" rel="noopener">Upgrade on face-swap.co</a>',
            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"""<a class="sticky-cta" href="https://www.face-swap.co/?utm_source=hfspace_videofaceswap&utm_medium=banner" target="_blank" rel="noopener"
              aria-label="Upgrade to Pro on face-swap.co">
              ⚑ <strong>Upgrade to HD</strong> β€” priority queue & no duration limit!
              <span class="pill">GPU</span>
            </a>"""
    )


    gr.Markdown(
        f"""
        ### Video Face Swap (Preview)
        [face-swap.co](https://www.face-swap.co/?utm_source=hfspace_videofaceswap&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_videofaceswap&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_videofaceswap&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):
            
            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_pro = gr.Button("Open Pro Checkout", variant="primary")
        #pro_subscription = gr.Button("Monthly Subscription", variant="primary")
        pro_api = gr.Button("API Access", variant="primary")


    # Floating bottom promo
    gr.HTML(
        f'<div class="bottom-promo">'
        f'Want HD & no duration limits? <a href="https://www.face-swap.co/?utm_source=hfspace_videofaceswap&utm_medium=upgrade" target="_blank" rel="noopener">Upgrade</a>'
        f'</div>'
    )

    
    go.click(fn=open_side, inputs=None, outputs=side, queue=False)  # fire instantly

    # Wire events
    go.click(fn=generate, inputs=[in_img, in_vid, in_gen], outputs=out_vid)

    # Open Pro in new tab via JS (no Python call)
    pro.click(fn=None, inputs=None, outputs=None,
              js=f"()=>window.open('https://www.face-swap.co/?utm_source=hfspace_videofaceswap&utm_medium=upgrade_to_hd','_blank')")
    pro_pro.click(fn=None, inputs=None, outputs=None,
               js=f"()=>window.open('https://www.face-swap.co/?utm_source=hfspace_videofaceswap&utm_medium=sidebar_pro','_blank')")
    #pro_subscription.click(fn=None, inputs=None, outputs=None,
    #           js=f"()=>window.open('https://www.face-swap.co/?utm_source=hfspace_videofaceswap&utm_medium=sidebar_subscription','_blank')")
    pro_api.click(fn=None, inputs=None, outputs=None,
               js=f"()=>window.open('https://www.face-swap.co/api?utm_source=hfspace_videofaceswap&utm_medium=sidebar_api','_blank')")

    # Queue for long jobs + to ensure alerts appear as modals
    demo.queue()

if __name__ == "__main__":
    demo.launch()