aideepfake / scripts /voice_pipeline.py
GitHub Action
Deploy backend to Hugging Face Space
887f5f0
Raw
History Blame Contribute Delete
5.76 kB
import time
import hashlib
import numpy as np
import librosa
import speech_recognition as sr
import soundfile as sf
import io
from scripts.voice_classifier import VoiceClassifier
class VoiceDetectionPipeline:
"""
Loads an audio file, splits it into overlapping segments, classifies each
segment with VoiceClassifier, and aggregates a global report. Mirrors the
shape of DetectionPipeline (frames -> segments) so the frontend can reuse
similar timeline/chart components.
"""
SAMPLE_RATE = VoiceClassifier.SAMPLE_RATE
SEGMENT_SEC = 3.0
HOP_SEC = 2.0
MAX_SEGMENTS = 4
def __init__(self, classifier: VoiceClassifier):
self.classifier = classifier
def analyze_audio(self, file_path: str, update_progress_cb=None) -> dict:
start_time = time.time()
file_hash = self._get_file_hash(file_path)
if update_progress_cb:
update_progress_cb(10, "Loading Audio")
y, sample_rate = librosa.load(file_path, sr=self.SAMPLE_RATE, mono=True)
duration = float(len(y) / sample_rate) if sample_rate else 0.0
if update_progress_cb:
update_progress_cb(25, "Segmenting Audio")
segments = self._segment_audio(y, sample_rate)
total_segments = len(segments)
processed_segments = []
all_scores = []
all_jitter = []
all_flatness = []
all_silence = []
for idx, (seg_audio, seg_start) in enumerate(segments):
if update_progress_cb:
pct = int(25 + (idx / max(1, total_segments)) * 65)
update_progress_cb(pct, f"Analyzing Segment {idx + 1}/{total_segments}")
res = self.classifier.analyze_segment(seg_audio)
all_scores.append(res["fake_score"])
all_jitter.append(res["heuristics"].get("pitch_jitter_score", 0.0))
all_flatness.append(res["heuristics"].get("spectral_flatness_score", 0.0))
all_silence.append(res["heuristics"].get("silence_anomaly_score", 0.0))
processed_segments.append({
"segment_idx": idx,
"start_time": round(seg_start, 2),
"end_time": round(seg_start + len(seg_audio) / sample_rate, 2),
"fake_score": res["fake_score"],
"is_fake": res["is_fake"],
"confidence": res["confidence"],
"heuristics": res["heuristics"]
})
if update_progress_cb:
update_progress_cb(95, "Aggregating Report")
if all_scores:
avg_fake_score = float(np.mean(all_scores))
max_fake_score = float(np.max(all_scores))
global_score = round((0.7 * max_fake_score) + (0.3 * avg_fake_score), 4)
avg_jitter = float(np.mean(all_jitter))
avg_flatness = float(np.mean(all_flatness))
avg_silence = float(np.mean(all_silence))
else:
global_score = 0.0
avg_jitter = avg_flatness = avg_silence = 0.0
if update_progress_cb:
update_progress_cb(97, "Transcribing Audio")
transcript = ""
try:
r = sr.Recognizer()
wav_io = io.BytesIO()
sf.write(wav_io, y, sample_rate, format='WAV', subtype='PCM_16')
wav_io.seek(0)
with sr.AudioFile(wav_io) as source:
audio_data = r.record(source)
transcript = r.recognize_google(audio_data)
except Exception as e:
transcript = "[Transcription unavailable or no speech detected]"
print(f"Transcription error: {e}")
# Bypass hanging recognize_google call on Windows/local environment
transcript = "[Transcription disabled for local testing]"
is_fake = global_score > 0.5
confidence = global_score if is_fake else (1.0 - global_score)
processing_time = round(time.time() - start_time, 2)
report = {
"file_hash": file_hash,
"filename": file_path.split("/")[-1].split("\\")[-1],
"is_audio": True,
"duration_sec": round(duration, 2),
"global_fake_score": global_score,
"is_fake": is_fake,
"confidence": round(confidence, 4),
"total_segments_analyzed": total_segments,
"processing_time_sec": processing_time,
"timestamp": time.time(),
"average_heuristics": {
"pitch_jitter_score": round(avg_jitter, 4),
"spectral_flatness_score": round(avg_flatness, 4),
"silence_anomaly_score": round(avg_silence, 4)
},
"segments": processed_segments,
"used_voice_model": self.classifier.model_loaded,
"transcript": transcript
}
if update_progress_cb:
update_progress_cb(100, "Completed")
return report
def _segment_audio(self, y: np.ndarray, sr: int) -> list[tuple[np.ndarray, float]]:
seg_len = int(self.SEGMENT_SEC * sr)
hop_len = int(self.HOP_SEC * sr)
if len(y) <= seg_len:
return [(y, 0.0)]
segments = []
pos = 0
while pos < len(y) and len(segments) < self.MAX_SEGMENTS:
chunk = y[pos:pos + seg_len]
if len(chunk) < int(0.5 * sr):
break
segments.append((chunk, pos / sr))
pos += hop_len
return segments if segments else [(y, 0.0)]
def _get_file_hash(self, file_path: str) -> str:
sha256 = hashlib.sha256()
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256.update(byte_block)
return sha256.hexdigest()