Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

63 wiersze
3.2 KiB

  1. """Check available clips against source audio and run an independent ASR screen.
  2. ASR similarity is a triage aid, not a pronunciation or visual lip-sync verdict.
  3. """
  4. from pathlib import Path
  5. import difflib
  6. import json
  7. import re
  8. import subprocess
  9. import sys
  10. import numpy as np
  11. from scipy.signal import correlate, correlation_lags
  12. ROOT=Path(__file__).resolve().parents[3]
  13. sys.path.insert(0,str(ROOT/'ai_person/ai_person_api'))
  14. from app.config import get_settings
  15. from app.services.asr_service import AsrService
  16. REPORT=ROOT/'unreal_tran/ute2e/reports/bridge-narration-junhao-20260921'
  17. WEB=ROOT/'unreal_tran/unreal_tran_web/public'
  18. def pcm(settings,path):
  19. return np.frombuffer(subprocess.run([settings.ffmpeg_binary,'-v','error','-i',str(path),
  20. '-vn','-ac','1','-ar','16000','-f','f32le','pipe:1'],capture_output=True,check=True).stdout,dtype=np.float32)
  21. def normalize(text):
  22. numbers=['零','一','二','三','四','五','六','七','八','九','十','十一','十二','十三','十四','十五','十六']
  23. text=re.sub(r'\d+',lambda m:numbers[int(m[0])] if int(m[0])<len(numbers) else m[0],text)
  24. return re.sub(r'[^\u4e00-\u9fffA-Za-z0-9]','',text)
  25. def main():
  26. settings=get_settings();asr=AsrService(settings)
  27. target=REPORT/'quality.json'
  28. results=json.loads(target.read_text('utf-8')) if target.exists() else []
  29. done={r['key']:r for r in results}
  30. progress=json.loads((REPORT/'generation.json').read_text('utf-8'))
  31. for clip in progress['clips']:
  32. if clip['key'] in done and done[clip['key']]['sha256']==clip['videoSha256'] and done[clip['key']].get('asrTailSeconds')==.8:continue
  33. audio=WEB/clip['audioUrl'].lstrip('/');video=WEB/clip['videoUrl'].lstrip('/')
  34. a,b=pcm(settings,audio),pcm(settings,video)
  35. lags=correlation_lags(len(b),len(a));corr=correlate(b,a,method='fft')
  36. mask=abs(lags)<=8000;lag=int(lags[mask][np.argmax(corr[mask])])
  37. score=float(np.max(corr[mask])/max(1e-9,np.linalg.norm(a)*np.linalg.norm(b)))
  38. recognizer=asr.load()
  39. stream=recognizer.create_stream()
  40. # Streaming Zipformer needs right context to emit the final syllables.
  41. stream.accept_waveform(16000,np.concatenate([b,np.zeros(12800,dtype=np.float32)]))
  42. stream.input_finished()
  43. while recognizer.is_ready(stream):recognizer.decode_stream(stream)
  44. transcript=recognizer.get_result(stream).strip()
  45. similarity=difflib.SequenceMatcher(None,normalize(clip['text']),normalize(transcript),autojunk=False).ratio()
  46. row={'key':clip['key'],'sha256':clip['videoSha256'],'text':clip['text'],'asrText':transcript,
  47. 'asrSimilarity':round(similarity,4),'asrTailSeconds':.8,'audioLagMs':round(lag/16,2),'audioCorrelation':round(score,4),
  48. 'sourceSeconds':len(a)/16000,'videoAudioSeconds':len(b)/16000,
  49. 'technicalPass':abs(lag)<1600 and score>.9 and abs(len(a)-len(b))<16000*.7,
  50. 'needsTranscriptReview':similarity<.85}
  51. results=[r for r in results if r['key']!=clip['key']]+[row]
  52. temp=target.with_suffix('.tmp');temp.write_text(json.dumps(results,ensure_ascii=False,indent=2),'utf-8');temp.replace(target)
  53. print(clip['key'],row['technicalPass'],'ASR',row['asrSimilarity'],'lag',row['audioLagMs'],flush=True)
  54. if __name__=='__main__':main()