File size: 3,457 Bytes
cf54850
 
eff3d67
cf54850
 
 
604836a
eff3d67
 
604836a
969e16d
 
604836a
969e16d
604836a
969e16d
604836a
eff3d67
cf54850
 
eff3d67
 
 
 
 
 
 
 
cf54850
eff3d67
 
 
 
 
 
 
 
 
cf54850
eff3d67
 
cf54850
eff3d67
cf54850
eff3d67
 
 
cf54850
eff3d67
cf54850
eff3d67
 
 
 
 
 
 
 
cf54850
eff3d67
 
 
969e16d
604836a
 
 
 
eff3d67
604836a
 
 
 
 
 
eff3d67
604836a
eff3d67
 
cf54850
eff3d67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from __future__ import annotations

import os


class ExplainModule:
    """Google Gemini text explainer."""

    def __init__(self):
        self.model = os.environ.get("GEMINI_EXPLAIN_MODEL", "gemini-3.1-flash-lite-preview")
        self.client = None
        try:
            from google import genai

            self.client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY", ""))
        except Exception as exc:
            print(f"ExplainModule using Gemini fallback: {exc}")

    def explain(
        self,
        fakescore,
        s1,
        s2,
        s3,
        weights,
        attribution,
        segments,
        top_generator,
    ) -> str:
        verdict = "FAKE" if fakescore > 0.5 else "REAL"
        confidence = (
            "high"
            if abs(fakescore - 0.5) > 0.3
            else "moderate"
            if abs(fakescore - 0.5) > 0.15
            else "low"
        )
        seg_text = ""
        if segments:
            seg_text = "Flagged timestamps: " + ", ".join(
                [f"{segment['time']}s (score={segment['score']})" for segment in segments[:5]]
            )
        attr_text = ""
        if attribution:
            top3 = sorted(attribution.items(), key=lambda item: -item[1])[:3]
            attr_text = "Top generators: " + ", ".join(
                [f"{name}: {prob * 100:.1f}%" for name, prob in top3]
            )
        prompt = f"""You are a forensic AI analyst. Analyze these deepfake detection results. Be specific about evidence.

Results:
- Verdict: {verdict} (FakeScore: {fakescore:.3f}, confidence: {confidence})
- Lip-Sync (M1): {s1:.3f} (weight: {weights.get('lip_sync', 'N/A')})
- Fingerprint (M2): {s2:.3f} (weight: {weights.get('fingerprint', 'N/A')})
- Graph-GNN (M3): {s3:.3f} (weight: {weights.get('graph_gnn', 'N/A')})
{seg_text}
{attr_text}
- Most likely generator: {top_generator}

Write 3-5 sentences. Reference specific scores and timestamps."""

        try:
            if self.client is None:
                raise RuntimeError("Gemini client unavailable")
            from google.genai import types

            response = self.client.models.generate_content(
                model=self.model,
                contents=prompt,
                config=types.GenerateContentConfig(
                    system_instruction="You are a forensic deepfake analyst. Be precise.",
                    max_output_tokens=300,
                    temperature=0.3,
                ),
            )
            return (response.text or "").strip()
        except Exception:
            return self._fallback(verdict, fakescore, s1, s2, s3, top_generator, confidence)

    def _fallback(self, verdict, fakescore, s1, s2, s3, top_gen, conf):
        if verdict == "FAKE":
            return (
                f"Video classified as {verdict} with {conf} confidence "
                f"(FakeScore: {fakescore:.3f}). "
                f"Lip-sync scored {s1:.2f}, indicating "
                f"{'significant' if s1 > 0.7 else 'moderate' if s1 > 0.5 else 'minimal'} "
                f"audio-visual inconsistency. "
                f"Style fingerprinting scored {s2:.2f}, top attribution: {top_gen}. "
                f"Graph analysis scored {s3:.2f}."
            )
        return (
            f"Video classified as {verdict} with {conf} confidence "
            f"(FakeScore: {fakescore:.3f}). "
            f"All modules returned scores below detection threshold."
        )