File size: 7,636 Bytes
3416e1b
 
 
9a40160
3416e1b
 
 
9a40160
cc0b037
3416e1b
9a40160
3416e1b
 
b2cb59a
3416e1b
 
 
 
 
 
 
cc0b037
 
 
 
 
 
 
 
 
 
3416e1b
 
 
43132d8
3416e1b
9a40160
3416e1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43132d8
 
3416e1b
 
 
9a40160
 
 
 
924bfa3
9a40160
 
 
 
 
 
 
 
 
924bfa3
9a40160
3416e1b
 
9a40160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
924bfa3
3416e1b
daf6e1e
 
 
3416e1b
 
 
 
 
9a40160
3416e1b
 
 
 
 
9a40160
3416e1b
9a40160
3416e1b
 
 
 
 
 
 
daf6e1e
 
9a40160
3416e1b
 
 
 
9a40160
daf6e1e
 
 
 
cc0b037
3416e1b
 
 
 
 
 
 
 
 
 
 
9a40160
3416e1b
 
 
 
 
 
9a40160
3416e1b
 
 
 
 
 
 
 
 
 
9a40160
3416e1b
 
 
 
 
 
 
 
 
9a40160
4769ba8
9a40160
 
3416e1b
 
 
 
 
 
 
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
# -*- coding: utf-8 -*-
"""
Cleaned fake vs forged vs real audio module (Gradio UI & Notebook Magics removed)
Fixed for PyTorch 2.6+ compatibility with LAION-CLAP checkpoints
"""

import os
import urllib.request
from huggingface_hub import login
import torch
import torch.serialization
import librosa
import numpy as np
import numpy
import laion_clap
import whisper
from sklearn.metrics.pairwise import cosine_similarity, euclidean_distances
from sklearn.preprocessing import normalize
from transformers import AutoFeatureExtractor, AutoModelForAudioClassification, AutoModelForAudioFrameClassification
from groq import Groq

# --- FETCH SECRETS & LOGIN ---
HF_TOKEN = os.environ.get("HF_TOKEN")
GROQ_API_KEY = os.environ.get("GROQ_API_KEY")

if HF_TOKEN:
    login(token=HF_TOKEN)
    print("βœ… Login Successful! Ab aap models load kar sakti hain.")
else:
    print("⚠️ WARNING: HF_TOKEN not found in Secrets. Private models load nahi honge.")

# --- SETUP & CONFIG ---
client = Groq(api_key=GROQ_API_KEY)
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
AST_MODEL_PATH = "aneela-pervez/FAKE-AUDIO"

# --- CATEGORIZED FORENSIC LABELS ---
FORENSIC_CATEGORIES = {
    "AUTHENTIC": ["Natural human speech", "Human voice with natural breathing", "Consistent room ambiance"],
    "AI_SYNTHETIC": ["AI voice clone robotic smoothness", "Synthetic neural vocoder", "Deepfake metallic resonance"],
    "MANIPULATED": ["Manually spliced audio", "Inconsistent stitching artifacts", "Micro-cuts and clicks"],
    "DISGUISED": ["Artificial pitch shifting", "Muffled voice mask identity", "Time-stretched artifacts"]
}
ALL_LABELS = [item for sublist in FORENSIC_CATEGORIES.values() for item in sublist]

# --- MODEL LOADING ---
print("πŸ”„ Initializing Fuzzy Forensic Engine v3.9...")
whisper_model = whisper.load_model("base", device=DEVICE)
id_extractor = AutoFeatureExtractor.from_pretrained("facebook/wav2vec2-base-960h")
id_model = AutoModelForAudioFrameClassification.from_pretrained("facebook/wav2vec2-base-960h").to(DEVICE).eval()

try:
    ast_extractor = AutoFeatureExtractor.from_pretrained(AST_MODEL_PATH)
    ast_model = AutoModelForAudioClassification.from_pretrained(AST_MODEL_PATH).to(DEVICE).eval()
except Exception as e:
    print(f"⚠️ AST Model issue (using default logic if fails): {e}")

# --- CLAP MODEL LOADING (PyTorch 2.6+ Full Fix) ---
# Step 1: Checkpoint pehle local mein download karein
CLAP_CKPT_URL = "https://huggingface.co/lukewys/laion_clap/resolve/main/music_audioset_epoch_15_esc_90.14.pt"
CLAP_CKPT_PATH = "/tmp/clap_music_audioset.pt"

if not os.path.exists(CLAP_CKPT_PATH):
    print("Downloading CLAP checkpoint to local disk...")
    urllib.request.urlretrieve(CLAP_CKPT_URL, CLAP_CKPT_PATH)
    print("βœ… CLAP checkpoint downloaded!")
else:
    print("βœ… CLAP checkpoint already cached.")

# Step 2: PyTorch 2.6+ safe globals fix
torch.serialization.add_safe_globals([numpy.core.multiarray.scalar])

# Step 3: CLAP model initialize karein
clap_model = laion_clap.CLAP_Module(enable_fusion=False, amodel='HTSAT-tiny').to(DEVICE)

# Step 4: Manually load checkpoint with strict=False
# strict=False: unexpected/missing keys jaise position_ids ko ignore karta hai
print("πŸ”„ Loading CLAP checkpoint (strict=False)...")
ckpt = torch.load(CLAP_CKPT_PATH, map_location=DEVICE, weights_only=False)

# Checkpoint structure check
if "state_dict" in ckpt:
    state_dict = ckpt["state_dict"]
elif "model" in ckpt:
    state_dict = ckpt["model"]
else:
    state_dict = ckpt

clap_model.model.load_state_dict(state_dict, strict=False)
clap_model.eval()
print("βœ… CLAP model loaded successfully!")


def extract_dna_embeddings(y):
    y = np.ascontiguousarray(y)
    inputs = id_extractor(y, sampling_rate=16000, return_tensors="pt")
    inputs = {k: (v.clone().detach() if isinstance(v, torch.Tensor) else torch.tensor(v)).to(DEVICE) for k, v in inputs.items()}
    with torch.no_grad():
        logits = id_model(**inputs).logits
        emb = torch.mean(logits, dim=1).cpu().numpy()
    return normalize(emb)


def analyze_voice_forensics(audio_path):
    try:
        y, sr = librosa.load(audio_path, sr=16000)
        y_48, _ = librosa.load(audio_path, sr=48000)
        y = y / (np.max(np.abs(y)) + 1e-9)
        y = np.ascontiguousarray(y)

        # 1. SEGMENT-WISE CROSS VALIDATION
        segments = np.array_split(y, 3)
        seg_embs = [extract_dna_embeddings(s) for s in segments]
        cos_sim = cosine_similarity(seg_embs[0], seg_embs[-1])[0][0]
        euc_dist = euclidean_distances(seg_embs[0], seg_embs[-1])[0][0]

        # 2. FEATURE EXTRACTION
        transcription = whisper_model.transcribe(audio_path)["text"]
        inputs = ast_extractor(y, sampling_rate=16000, return_tensors="pt")
        inputs = {k: (v.clone().detach() if isinstance(v, torch.Tensor) else torch.tensor(v)).to(DEVICE) for k, v in inputs.items()}

        with torch.no_grad():
            ast_prob = torch.nn.functional.softmax(ast_model(**inputs).logits, dim=-1).cpu().numpy()[0][1]
            ast_score = ast_prob * 100

        # CLAP EMBEDDINGS
        audio_emb_np = clap_model.get_audio_embedding_from_data(x=[y_48])
        text_emb_np = clap_model.get_text_embedding(ALL_LABELS)
        audio_emb = torch.from_numpy(audio_emb_np).to(DEVICE)
        text_emb = torch.from_numpy(text_emb_np).to(DEVICE)
        c_sims = torch.nn.functional.cosine_similarity(audio_emb, text_emb)
        top_idx = torch.argmax(c_sims).item()
        clap_label = ALL_LABELS[top_idx]

        category = "UNKNOWN"
        for cat, labels in FORENSIC_CATEGORIES.items():
            if clap_label in labels:
                category = cat
                break

        num_edits = len(np.where(np.diff(librosa.onset.onset_strength(y=y, sr=16000)) > 6.5)[0])

        # FUZZY LOGIC MEMBERSHIP SCORING
        mu_ai = min(1.0, max(0.0, (ast_score - 40) / 45))
        mu_spliced = min(1.0, max(0.0, (num_edits - 8) / 10))
        dna_conf = (cos_sim - 0.85) / 0.10
        cat_conf = 1.0 if category == "AUTHENTIC" else 0.0
        mu_auth = min(1.0, max(0.0, (dna_conf + cat_conf) / 2))

        # FUZZY DECISION ENGINE (V4.0)
        if mu_auth > 0.80 and mu_spliced < 0.75:
            verdict = "βœ… AUTHENTIC HUMAN VOICE"
            category_override = "AUTHENTIC"
        elif mu_ai > 0.85 and mu_auth < 0.80:
            verdict = "🚨 AI CLONE DETECTED"
            category_override = "AI_SYNTHETIC"
        else:
            verdict = "⚠️ MANIPULATED REAL VOICE (Spliced)"
            category_override = "MANIPULATED"

        # LLM REASONING
        audit_context = (f"Verdict: {verdict}. Category: {category_override}. Metrics: ID {cos_sim:.2f}, "
                         f"Texture {ast_score:.1f}%, Cuts {num_edits}. "
                         f"Fuzzy Scores: AI={mu_ai:.2f}, Spliced={mu_spliced:.2f}, Auth={mu_auth:.2f}")

        prompt = (f"Act as a Senior Forensic Auditor. {audit_context} "
                  "Instruction: Justify the verdict. If identity stability is high (Auth > 0.8), "
                  "emphasize that DNA integrity confirms human origin despite high spectral texture. "
                  "Give a 2-line direct forensic conclusion.")

        res = client.chat.completions.create(
            model="openai/gpt-oss-120b",
            messages=[{"role": "user", "content": prompt}]
        )
        reasoning = res.choices[0].message.content

        evidence = f"Category: {category} | ID: {cos_sim:.2f} | Texture: {ast_score:.1f}% | Cuts: {num_edits}"
        return verdict, evidence, reasoning

    except Exception as e:
        return "Error", str(e), "N/A"