DEEP-FAKE-DETECTOR / fake_audio.py
aneela-pervez's picture
Update fake_audio.py
4769ba8 verified
Raw
History Blame Contribute Delete
7.64 kB
# -*- 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"