PrimeTTS / scripts /xasr_offline.py
Luigi's picture
PrimeTTS: full training pipeline + weights (fine-tune of Inflect-Nano-v1)
a37967e verified
Raw
History Blame Contribute Delete
2.73 kB
"""Offline X-ASR (zh-en zipformer transducer, 2026-06-03) scorer — accurate code-mix
assessment. Reuses the model's own raw-ort decode (int64-patched) + OpenCC T2S norm.
Run in moss-nano-venv (onnxruntime 1.27 + kaldi_native_fbank + opencc + soundfile/librosa)."""
import os, re, sys
MDIR="/home/luigi/jetson-tts/models/xasr-offline/sherpa-onnx-x-asr-zipformer-transducer-zh-en-2026-06-03"
_cwd=os.getcwd(); os.chdir(MDIR); sys.path.insert(0, MDIR)
import importlib.util
_spec=importlib.util.spec_from_file_location("xasr_test", f"{MDIR}/test_onnx.py")
T=importlib.util.module_from_spec(_spec); _spec.loader.exec_module(T)
os.chdir(_cwd)
import opencc, cn2an
_ZD={"0":"零","1":"一","2":"二","3":"三","4":"四","5":"五","6":"六","7":"七","8":"八","9":"九"}
def _numnorm(t):
def r(m):
d=m.group(0)
if len(d)>=5: return "".join(_ZD[c] for c in d)
try: return cn2an.an2cn(d,"low")
except: return "".join(_ZD[c] for c in d)
return re.sub(r"\d+",r,t)
_t2s=opencc.OpenCC('t2s'); _model=None; _id2tok=None
def _ensure():
global _model,_id2tok
if _model is None:
cwd=os.getcwd(); os.chdir(MDIR)
_id2tok=T.load_tokens("./tokens.txt"); _model=T.load_model(use_int8=False)
os.chdir(cwd)
return _model,_id2tok
def asr(wav):
import soundfile as sf, librosa, numpy as np
m,id2tok=_ensure()
samples,sr=sf.read(wav,dtype="float32")
if samples.ndim>1: samples=samples.mean(1)
if sr!=16000: samples=librosa.resample(samples,orig_sr=sr,target_sr=16000,res_type="soxr_hq")
if len(samples)<64000: samples=np.concatenate([samples,np.zeros(64000-len(samples),np.float32)]) # >=4s pad: short clips break encoder convs
feats=T.compute_feat(samples=samples.astype(np.float32), sample_rate=16000)
blank=0; hyp=[blank]*m.context_size
dout=m.run_decoder(hyp); enc=m.run_encoder(feats[None])
for k in range(enc.shape[1]):
jo=m.run_joiner(enc[0,k:k+1], dout); tid=int(jo.argmax())
if tid!=blank:
hyp.append(tid); dout=m.run_decoder(hyp[-m.context_size:])
toks=[id2tok[i] for i in hyp[m.context_size:]]
return "".join(toks).replace("▁"," ").strip()
def han(s): return "".join(c for c in s if "一"<=c<="鿿")
def _lev(r,h):
if not r: return 0.0
d=list(range(len(h)+1))
for i in range(1,len(r)+1):
p=d[0]; d[0]=i
for j in range(1,len(h)+1):
c=d[j]; d[j]=min(d[j]+1,d[j-1]+1,p+(r[i-1]!=h[j-1])); p=c
return d[len(h)]/len(r)
def score(ref,hyp):
is_zh=bool(re.search(r"[一-鿿]",ref))
if is_zh: return _lev(han(_t2s.convert(_numnorm(ref))),han(_t2s.convert(hyp)))
return _lev(re.findall(r"[a-z']+",ref.lower()),re.findall(r"[a-z']+",hyp.lower()))