import spaces # ← must be first, before any CUDA-related import import gradio as gr import torch from transformers import pipeline # ── Model registry ─────────────────────────────────────────────────────────── MODELS = { ("fast", "kodf"): "KoreaPeter/ms-eff-gcvit-deepfake-b0-kodf", ("fast", "celeb-df-v2"): "KoreaPeter/ms-eff-gcvit-deepfake-b0-celeb-df-v2", ("fast", "ff++"): "KoreaPeter/ms-eff-gcvit-deepfake-b0-ff-plus-plus", ("pro", "kodf"): "KoreaPeter/ms-eff-gcvit-deepfake-b5-kodf", ("pro", "celeb-df-v2"): "KoreaPeter/ms-eff-gcvit-deepfake-b5-celeb-df-v2", ("pro", "ff++"): "KoreaPeter/ms-eff-gcvit-deepfake-b5-ff-plus-plus", } _cache: dict = {} def _get_pipeline(variant: str, dataset: str): key = (variant, dataset) if key not in _cache: # DEVICE를 함수 호출 시점에 평가 — ZeroGPU가 GPU 할당한 이후여야 함 device = "cuda" if torch.cuda.is_available() else "cpu" _cache[key] = pipeline( "video-classification", model=MODELS[key], trust_remote_code=True, device=device, ) return _cache[key] # ── Frame table (HTML) ──────────────────────────────────────────────────────── def _build_frame_table(frame_scores: list, final_fake_prob: float) -> str: rows = "" for i, s in enumerate(frame_scores): fp = round(float(s), 4) pct = f"{fp * 100:.1f}%" if fp >= 0.7: bg, badge, text = "#3b0f0f", "🔴 FAKE", "#ff6b6b" elif fp >= 0.4: bg, badge, text = "#2e2a00", "🟡 UNCERTAIN", "#ffd700" else: bg, badge, text = "#0b1f2e", "🟢 REAL", "#4fc3f7" bar_w = int(fp * 100) rows += f""" Frame {i+1}
{pct} {badge} """ fp = round(final_fake_prob, 4) pct = f"{fp * 100:.1f}%" fc = "#ff6b6b" if fp >= 0.7 else ("#ffd700" if fp >= 0.4 else "#4fc3f7") final_row = f""" ▶ Final Score
{pct} Aggregated """ return f"""
{rows}{final_row}
Frame FAKE Probability Score Verdict
""" # ── Inference ───────────────────────────────────────────────────────────────── @spaces.GPU def predict( video_path: str, variant: str, dataset: str, num_frames: int, margin_ratio: float, conf_thres: float, min_face_ratio: float, tta_hflip: float, agg_mode: str, ): if video_path is None: return None, "⚠️ Please upload a video.", "" try: pipe = _get_pipeline(variant, dataset) results = pipe( video_path, num_frames=num_frames, margin_ratio=margin_ratio, conf_thres=conf_thres, min_face_ratio=min_face_ratio, tta_hflip=tta_hflip, agg_mode=agg_mode, return_frame_scores=True, ) except Exception as e: return None, f"❌ Pipeline error: {e}", "" if results[0]["label"] == "no_face_detected": return ( {"REAL": 0.0, "FAKE": 0.0}, "😶 No face detected.\nPlease use a video with a clearly visible face.", "", ) scores = {r["label"].upper(): round(r["score"], 4) for r in results if r.get("label") in ("fake", "real")} fake_prob = scores.get("FAKE", 0.0) if fake_prob >= 0.7: verdict = f"🔴 FAKE ({fake_prob * 100:.1f}% confidence)" elif fake_prob >= 0.4: verdict = f"🟡 UNCERTAIN (FAKE {fake_prob * 100:.1f}%)" else: verdict = f"🟢 REAL ({(1 - fake_prob) * 100:.1f}% confidence)" frame_scores = next( (r["frame_scores"] for r in results if "frame_scores" in r), None ) table_html = _build_frame_table(frame_scores, fake_prob) if frame_scores else "" return scores, verdict, table_html # ── Hero banner HTML ────────────────────────────────────────────────────────── HERO = """
🎬

DeepGuard

Video Deepfake Detection

Frame-by-frame deepfake analysis powered by MS-EffGCViT  ·  6 domain-specific models  ·  Asian · Celebrity · Western faces

🔗 GitHub 🤗 HuggingFace 🕵️ Image Detection 🔬 DeepFake XAI
""" # ── Theme & CSS ─────────────────────────────────────────────────────────────── THEME = gr.themes.Base( primary_hue="blue", neutral_hue="slate", font=gr.themes.GoogleFont("Inter"), ).set( body_background_fill="#061626", body_text_color="#e3f2fd", block_background_fill="#0d2137", block_border_color="#1e3a5f", input_background_fill="#0a1929", button_primary_background_fill="#0d47a1", button_primary_text_color="#e3f2fd", button_primary_border_color="#2979ff", button_primary_background_fill_hover="#1565c0", button_primary_text_color_hover="#ffffff", ) CSS = """ .result-box textarea { font-size:1.1rem !important; font-weight:bold !important; text-align:center; } footer { display:none !important; } """ # ── Build UI ────────────────────────────────────────────────────────────────── with gr.Blocks(title="DeepGuard Video") as demo: gr.HTML(HERO) with gr.Row(): with gr.Column(scale=1, min_width=300): video_input = gr.Video(label="Upload Video") with gr.Accordion("🎞️ Example Videos — (click to expand)", open=False): gr.Examples( examples=["western_fake.mp4", "western_real.mp4"], inputs=video_input, label="order = FAKE, REAL", ) gr.Markdown("### ⚡ Model Variant") variant_radio = gr.Radio( choices=["fast", "pro"], value="pro", label=None, info="Fast (b0): 8.7M params | Pro (b5): 50.3M params", ) gr.Markdown("### 🌍 Training Domain") dataset_radio = gr.Radio( choices=[ ("🇰🇷 KoDF — East Asian faces", "kodf"), ("🌟 Celeb-DF-v2 — Celebrity faces", "celeb-df-v2"), ("🌍 FF++ — Western faces", "ff++"), ], value="ff++", label=None, ) run_btn = gr.Button("🔍 Analyze", variant="primary", size="lg") with gr.Column(scale=1, min_width=300): verdict_output = gr.Textbox( label="Result", interactive=False, lines=2, elem_classes=["result-box"], ) score_output = gr.Label(label="Class Probabilities", num_top_classes=2) gr.Markdown(""" --- **Model Guide** | | ⚡ Fast (b0) | 🔥 Pro (b5) | |---|---|---| | **Params** | 8.7M | 50.3M | | **Resolution** | 224×224 | 384×384 | | **Speed** | Faster | Slower | | **Accuracy** | High | Higher | **Domain Guide** | Domain | Training Data | Best For | |---|---|---| | KoDF | 62K real / 175K fake (Korean) | East Asian faces | | Celeb-DF-v2 | 890 real / 5.6K fake (Celebrities) | Celebrity faces | | FF++ | 1K real / 6K fake (4 manipulation types) | Western faces | """) with gr.Accordion("⚙️ Advanced Settings", open=True): gr.Markdown("Adjust inference parameters. Defaults are recommended for most cases.") with gr.Row(): with gr.Column(): num_frames_slider = gr.Slider( minimum=5, maximum=20, value=5, step=1, label="Num Frames", info="Number of frames sampled from the video", ) margin_ratio_slider = gr.Slider( minimum=0.0, maximum=0.5, value=0.2, step=0.05, label="Margin Ratio", info="Margin around the detected face bounding box", ) tta_hflip_slider = gr.Slider( minimum=0.0, maximum=1.0, value=0.0, step=0.1, label="TTA Horizontal Flip", info="⚠️ > 0.0 doubles inference time per frame", ) with gr.Column(): agg_radio = gr.Radio( choices=["conf", "mean", "vote"], value="conf", label="Aggregation Mode", info="conf: heuristic (recommended) · mean: average · vote: majority", ) conf_thres_slider = gr.Slider( minimum=0.1, maximum=0.9, value=0.5, step=0.05, label="Face Detection Confidence", info="YOLO face detection confidence threshold", ) min_face_ratio_slider = gr.Slider( minimum=0.001, maximum=0.1, value=0.01, step=0.001, label="Min Face Ratio", info="Minimum face-to-frame area ratio to process", ) with gr.Accordion("📊 Frame-level Results", open=True): frame_table = gr.HTML("") run_btn.click( fn=predict, inputs=[ video_input, variant_radio, dataset_radio, num_frames_slider, margin_ratio_slider, conf_thres_slider, min_face_ratio_slider, tta_hflip_slider, agg_radio, ], outputs=[score_output, verdict_output, frame_table], ) if __name__ == "__main__": demo.launch(theme=THEME, css=CSS)