Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 

63 Zeilen
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()