aideepfake / scripts /voice_classifier.py
GitHub Action
Deploy backend to Hugging Face Space
887f5f0
Raw
History Blame Contribute Delete
5.41 kB
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)
}