Spaces:
Running
Running
File size: 5,408 Bytes
887f5f0 | 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 | import os
import numpy as np
import librosa
import torch
import torch.nn.functional as F
# Keep HF model downloads inside the repo instead of polluting the user's C: drive cache
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
os.environ.setdefault("HF_HOME", os.path.join(base_dir, "..", "hf_cache"))
if os.name == "nt":
os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1"
os.environ["HF_HUB_DISABLE_SYMLINKS"] = "1"
from transformers import AutoFeatureExtractor, AutoModelForAudioClassification
# Wav2Vec2-based voice deepfake/spoof detector, trained on real vs AI-cloned
# speech (ElevenLabs, Amazon Polly, Kokoro, Hume AI, Speechify, Luvvoice, etc.)
AUDIO_MODEL_ID = None # "garystafford/wav2vec2-deepfake-voice-detector" (Hanging on local Windows)
class VoiceClassifier:
"""
Classifies audio segments as Real or AI-generated (fake) using:
1. A pretrained Wav2Vec2-based voice deepfake/spoof detector.
2. Acoustic forensics (pitch jitter, spectral flatness, and silence/energy anomalies)
used as a fallback if the model fails to load.
"""
SAMPLE_RATE = 16000
def __init__(self, use_gpu: bool = True):
self.device = torch.device("cuda" if (use_gpu and torch.cuda.is_available()) else "cpu")
self.model = None
self.feature_extractor = None
self.fake_idx = 1
self.model_loaded = False
try:
print(f"Loading voice deepfake model '{AUDIO_MODEL_ID}' on {self.device}...")
self.feature_extractor = AutoFeatureExtractor.from_pretrained(AUDIO_MODEL_ID)
self.model = AutoModelForAudioClassification.from_pretrained(AUDIO_MODEL_ID)
self.model.to(self.device).eval()
self.fake_idx = self._find_fake_index(self.model.config.id2label)
self.model_loaded = True
print("Voice deepfake model loaded successfully!")
except Exception as e:
print(f"Warning: could not load voice deepfake model: {e}")
print("Falling back to local heuristic acoustic forensic classification.")
@staticmethod
def _find_fake_index(id2label: dict) -> int:
for idx, label in id2label.items():
l = str(label).lower()
if any(k in l for k in ("fake", "synthetic", "spoof", "generated", "artificial")):
return int(idx)
return 1
def analyze_segment(self, audio_segment: np.ndarray) -> dict:
"""
Runs deepfake classification and acoustic artifact analysis on a mono audio
segment sampled at SAMPLE_RATE.
Returns a dict with scores and metadata.
"""
if audio_segment.size == 0:
return {"fake_score": 0.5, "is_fake": False, "confidence": 0.5, "heuristics": {}}
heuristics = self._compute_heuristics(audio_segment)
deep_learning_score = None
if self.model_loaded and self.model is not None:
try:
inputs = self.feature_extractor(
audio_segment, sampling_rate=self.SAMPLE_RATE, return_tensors="pt", padding=True
)
inputs = {k: v.to(self.device) for k, v in inputs.items()}
with torch.no_grad():
outputs = self.model(**inputs)
probs = F.softmax(outputs.logits, dim=-1).cpu().numpy()[0]
deep_learning_score = float(probs[self.fake_idx])
except Exception as e:
print(f"Error during voice deepfake model inference: {e}. Using heuristics instead.")
if deep_learning_score is None:
# No deep model available: fall back to pure acoustic heuristics (no randomness).
deep_learning_score = (
0.4 * heuristics["pitch_jitter_score"] +
0.35 * heuristics["spectral_flatness_score"] +
0.25 * heuristics["silence_anomaly_score"]
)
fake_score = round(float(deep_learning_score), 4)
is_fake = fake_score > 0.5
confidence = round(fake_score if is_fake else (1.0 - fake_score), 4)
return {
"fake_score": fake_score,
"is_fake": is_fake,
"confidence": confidence,
"heuristics": heuristics
}
def _compute_heuristics(self, audio: np.ndarray) -> dict:
try:
pitches, magnitudes = librosa.piptrack(y=audio, sr=self.SAMPLE_RATE)
pitch_vals = pitches[magnitudes > np.median(magnitudes)]
jitter = float(np.std(pitch_vals)) if len(pitch_vals) > 0 else 0.0
norm_jitter = min(jitter / 500.0, 1.0)
except Exception:
norm_jitter = 0.5
try:
flatness = librosa.feature.spectral_flatness(y=audio)
avg_flatness = float(np.mean(flatness))
norm_flatness = min(avg_flatness * 10, 1.0)
except Exception:
norm_flatness = 0.5
try:
rms = librosa.feature.rms(y=audio)[0]
silence_ratio = float(np.sum(rms < 0.01) / len(rms)) if len(rms) > 0 else 0.0
norm_silence = min(silence_ratio * 2, 1.0)
except Exception:
norm_silence = 0.5
return {
"pitch_jitter_score": round(norm_jitter, 4),
"spectral_flatness_score": round(norm_flatness, 4),
"silence_anomaly_score": round(norm_silence, 4)
}
|