KoreaPeter's picture
Update app.py
ff06766 verified
Raw History Blame Contribute Delete
13.8 kB
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"""
<tr style="background:{bg}; border-bottom:1px solid #1e3a5f;">
<td style="padding:8px 14px; color:#90caf9; font-weight:600;">Frame {i+1}</td>
<td style="padding:8px 14px;">
<div style="background:#0d2137; border-radius:4px; height:14px; width:180px; overflow:hidden;">
<div style="background:{text}; width:{bar_w}%; height:100%; border-radius:4px;"></div>
</div>
</td>
<td style="padding:8px 14px; color:{text}; font-family:monospace;">{pct}</td>
<td style="padding:8px 14px; color:{text}; font-weight:600;">{badge}</td>
</tr>"""
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"""
<tr style="background:#0a3060; border-top:2px solid #1565c0;">
<td style="padding:10px 14px; color:#90caf9; font-weight:700;">β–Ά Final Score</td>
<td style="padding:10px 14px;">
<div style="background:#0d2137; border-radius:4px; height:14px; width:180px; overflow:hidden;">
<div style="background:{fc}; width:{int(fp*100)}%; height:100%; border-radius:4px;"></div>
</div>
</td>
<td style="padding:10px 14px; color:{fc}; font-family:monospace; font-weight:700;">{pct}</td>
<td style="padding:10px 14px; color:{fc}; font-weight:700;">Aggregated</td>
</tr>"""
return f"""
<div style="overflow-x:auto; margin-top:8px;">
<table style="width:100%; border-collapse:collapse; font-size:14px;
background:#061626; border-radius:8px; overflow:hidden;">
<thead>
<tr style="background:#0d47a1; color:#e3f2fd;">
<th style="padding:10px 14px; text-align:left;">Frame</th>
<th style="padding:10px 14px; text-align:left;">FAKE Probability</th>
<th style="padding:10px 14px; text-align:left;">Score</th>
<th style="padding:10px 14px; text-align:left;">Verdict</th>
</tr>
</thead>
<tbody>{rows}{final_row}</tbody>
</table>
</div>"""
# ── 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 = """
<div style="
background: linear-gradient(135deg, #0d47a1 0%, #1565c0 40%, #0a3060 100%);
border-radius: 12px; padding: 36px 40px; margin-bottom: 8px;
border: 1px solid #1e3a5f; box-shadow: 0 4px 24px rgba(13,71,161,0.4);
">
<div style="display:flex; align-items:center; gap:16px; margin-bottom:12px;">
<span style="font-size:2.4rem;">🎬</span>
<div>
<h1 style="margin:0; color:#e3f2fd; font-size:1.9rem; font-weight:700; letter-spacing:-0.5px;">DeepGuard</h1>
<p style="margin:4px 0 0; color:#90caf9; font-size:1rem;">Video Deepfake Detection</p>
</div>
</div>
<p style="margin:0 0 16px; color:#bbdefb; font-size:0.95rem; line-height:1.6;">
Frame-by-frame deepfake analysis powered by <strong style="color:#e3f2fd;">MS-EffGCViT</strong>
&nbsp;Β·&nbsp; 6 domain-specific models &nbsp;Β·&nbsp; Asian Β· Celebrity Β· Western faces
</p>
<div style="display:flex; gap:12px; flex-wrap:wrap;">
<a href="https://github.com/HanMoonSub/DeepGuard" target="_blank"
style="background:#1e3a5f; color:#64b5f6; padding:7px 16px; border-radius:6px;
text-decoration:none; font-size:0.85rem; border:1px solid #2979ff;">πŸ”— GitHub</a>
<a href="https://huggingface.co/KoreaPeter" target="_blank"
style="background:#1e3a5f; color:#64b5f6; padding:7px 16px; border-radius:6px;
text-decoration:none; font-size:0.85rem; border:1px solid #2979ff;">πŸ€— HuggingFace</a>
<a href="https://huggingface.co/spaces/KoreaPeter/DeepFake-Image-Detection" target="_blank"
style="background:#1e3a5f; color:#64b5f6; padding:7px 16px; border-radius:6px;
text-decoration:none; font-size:0.85rem; border:1px solid #2979ff;">πŸ•΅οΈ Image Detection</a>
<a href="https://huggingface.co/spaces/KoreaPeter/DeepFake-Detection-XAI" target="_blank"
style="background:#1e3a5f; color:#64b5f6; padding:7px 16px; border-radius:6px;
text-decoration:none; font-size:0.85rem; border:1px solid #2979ff;">πŸ”¬ DeepFake XAI</a>
</div>
</div>
"""
# ── 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)