| import numpy as np |
| import librosa |
| from pathlib import Path |
| import hashlib |
| from cryptography.fernet import Fernet |
| import os |
| import json |
|
|
| class VoicePrint: |
| def __init__(self, hive_home): |
| self.hive_home = Path(hive_home) |
| self.vp_dir = self.hive_home / "users" / "voiceprints" |
| self.vp_dir.mkdir(parents=True, exist_ok=True) |
| self.key = os.getenv("VP_KEY", Fernet.generate_key().decode()) |
| self.cipher = Fernet(self.key.encode()) |
| self.users_file = self.hive_home / "users" / "profiles.json" |
| self.users = self.load_users() |
| |
| def load_users(self): |
| if self.users_file.exists(): |
| return json.loads(self.users_file.read_text()) |
| return {} |
| |
| def save_users(self): |
| self.users_file.write_text(json.dumps(self.users, indent=2)) |
| |
| def extract_features(self, audio_path): |
| try: |
| y, sr = librosa.load(str(audio_path), sr=16000) |
| mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=20) |
| return np.hstack([np.mean(mfcc, axis=1), np.std(mfcc, axis=1)]) |
| except: |
| return np.zeros(40) |
| |
| def enroll(self, audio_path, user_id): |
| features = self.extract_features(audio_path) |
| encrypted = self.cipher.encrypt(features.tobytes()) |
| fp = self.vp_dir / f"{user_id}.vp" |
| fp.write_bytes(encrypted) |
| |
| |
| if user_id not in self.users: |
| self.users[user_id] = {"id": user_id, "voice_enrolled": True} |
| else: |
| self.users[user_id]["voice_enrolled"] = True |
| self.save_users() |
| return True |
| |
| def verify(self, audio_path, user_id): |
| stored_fp = self.vp_dir / f"{user_id}.vp" |
| if not stored_fp.exists(): |
| return 0.0 |
| |
| try: |
| encrypted = stored_fp.read_bytes() |
| decrypted = self.cipher.decrypt(encrypted) |
| stored = np.frombuffer(decrypted, dtype=np.float64) |
| current = self.extract_features(audio_path) |
| if np.linalg.norm(current) == 0 or np.linalg.norm(stored) == 0: |
| return 0.0 |
| return np.dot(stored, current) / (np.linalg.norm(stored) * np.linalg.norm(current)) |
| except: |
| return 0.0 |
| |
| def identify(self, audio_path): |
| current = self.extract_features(audio_path) |
| if np.linalg.norm(current) == 0: |
| return None |
| |
| best_score = 0.0 |
| best_user = None |
| |
| for fp in self.vp_dir.glob("*.vp"): |
| user_id = fp.stem |
| score = self.verify(audio_path, user_id) |
| if score > best_score and score >= 0.7: |
| best_score = score |
| best_user = user_id |
| |
| return best_user |