Download code/identity.py from laion/tyger-speaker-predictor: direct link, hf CLI and curl.
- Browser
- Download file 4.83 kB
-
https://huggingface.co/laion/tyger-speaker-predictor/resolve/main/code/identity.py
- Command line
-
hf download hf://laion/tyger-speaker-predictor/code/identity.py
-
curl -L -o identity.py https://huggingface.co/laion/tyger-speaker-predictor/resolve/main/code/identity.py
4.83 kB
| """Session-only approximate voice cues, independent from TYGER turn decisions. | |
| No authentication, no voiceprints on disk. Never allow a short, uncertain frame | |
| or an overlap to enroll somebody. The full wake utterance supplies enrollment. | |
| """ | |
| from dataclasses import dataclass | |
| import re | |
| import time | |
| import numpy as np | |
| def unit(value): | |
| x=np.asarray(value,np.float32) | |
| if x.shape!=(192,) or not np.isfinite(x).all():raise ValueError('Expected finite 192-dimensional embedding') | |
| norm=float(np.linalg.norm(x)) | |
| if norm<1e-8:raise ValueError('Empty speaker embedding') | |
| return x/norm | |
| class User: | |
| name:str | |
| embedding:np.ndarray | |
| seen:float | |
| class IdentityManager: | |
| def __init__(self,threshold=.65,margin=.04,timeout_s=300,max_users=5,robo=None,clock=time.monotonic): | |
| self.threshold=threshold;self.margin=margin;self.timeout_s=timeout_s | |
| self.max_users=max_users;self.robo=unit(robo) if robo is not None else None | |
| self.clock=clock;self.users=[];self.utterance=[];self.voiced_ms=0 | |
| def clear(self): | |
| self.users.clear();self.reset_utterance() | |
| def reset_utterance(self): | |
| self.utterance=[];self.voiced_ms=0 | |
| def observe(self,prediction): | |
| kind=prediction.get('kind','uncertain');embedding=prediction.get('embedding') | |
| if kind!='speech' or embedding is None or prediction.get('confidence',0)<.6: | |
| return {'kind':kind,'name':None,'similarity':None} | |
| x=unit(embedding) | |
| match=self.match(x,refresh=True) | |
| if match['kind']=='robo':return match | |
| self.utterance.append(x) | |
| self.utterance=self.utterance[-150:] # <=48 s of accepted cues, RAM only | |
| self.voiced_ms+=320 | |
| return match | |
| def expire(self): | |
| now=self.clock() | |
| self.users=[u for u in self.users if self.timeout_s<=0 or now-u.seen<self.timeout_s] | |
| def match(self,embedding,refresh=False): | |
| self.expire();x=unit(embedding) | |
| entries=[(u.name,float(x@u.embedding),u) for u in self.users] | |
| if self.robo is not None:entries.append(('Robby',float(x@self.robo),None)) | |
| entries.sort(key=lambda item:item[1],reverse=True) | |
| if not entries:return {'kind':'unknown','name':None,'similarity':None} | |
| name,score,user=entries[0];runner=entries[1][1] if len(entries)>1 else -1 | |
| confident=score>=self.threshold and score-runner>=self.margin | |
| if not confident:return {'kind':'unknown','name':None,'similarity':score} | |
| if user is not None and refresh:user.seen=self.clock() | |
| return {'kind':'robo' if user is None else 'enrolled','name':name,'similarity':score} | |
| def finish(self,text,enabled=False): | |
| """Apply a gate to endpoint handling, not to the turn-taking network. | |
| Typed messages never pass through this function. Thresholds must come | |
| from validation; a weak pilot must leave this feature opt-in. | |
| """ | |
| wake=bool(re.search(r'\b(?:hey|hi|hallo|hello)[,\s]+(?:robo|robby|robbie|robot)\b',text,re.I)) | |
| try: | |
| if not self.utterance or self.voiced_ms<640: | |
| return {'accepted':not enabled,'kind':'uncertain','reason':'Not enough confident speech to identify or enroll'} | |
| vector=unit(np.mean(self.utterance,axis=0)) | |
| match=self.match(vector) | |
| if match['kind']=='robo':return {**match,'accepted':not enabled,'reason':'Likely playback voice'} | |
| if wake and match['kind']!='enrolled': | |
| # Do not silently overwrite a similar user's voiceprint. | |
| similarities=[float(vector@u.embedding) for u in self.users] | |
| if similarities and max(similarities)>self.threshold-self.margin: | |
| return {**match,'accepted':not enabled,'reason':'Voice too close to an existing user; uncertain enrollment'} | |
| if len(self.users)>=self.max_users: | |
| return {**match,'accepted':not enabled,'reason':'Active-user limit reached'} | |
| name='User '+str(max([int(u.name.split()[-1]) for u in self.users],default=0)+1) | |
| self.users.append(User(name,vector,self.clock())) | |
| return {'kind':'enrolled','name':name,'similarity':1.,'accepted':True,'new_user':True} | |
| if match['kind']=='enrolled':match=self.match(vector,refresh=True) | |
| return {**match,'accepted':not enabled or match['kind']=='enrolled', | |
| 'reason':'Say Hey Robo in a full sentence to join' if match['kind']=='unknown' else 'Approximate session speaker cue'} | |
| finally:self.reset_utterance() | |
| def status(self): | |
| self.expire() | |
| return {'users':[u.name for u in self.users],'timeout_s':self.timeout_s,'secure_authentication':False, | |
| 'storage':'RAM for this conversation only; cleared on disconnect'} | |