|
- from pathlib import Path
- import sys,json,subprocess,re,threading,hashlib,shutil
- import numpy as np
- import soundfile as sf
- import httpx
-
- ROOT=Path(__file__).resolve().parents[3]
- sys.path.insert(0,str(ROOT/'ai_person/ai_person_api'))
- from app.config import get_settings
- from app.schemas.open_platform import GenerationCommand
- from app.services.open_generation_worker import OpenGenerationWorker
-
- out=ROOT/'unreal_tran/ute2e/reports/bridge-005-moss-refined-20260920'
- out.mkdir(parents=True,exist_ok=True)
- source=ROOT/'ai_person/outputs/moss-integration-check/step1-refined/natural-42.wav'
- audio,sr=sf.read(source)
- detect=subprocess.run(['ffmpeg','-hide_banner','-i',str(source),'-af','silencedetect=noise=-48dB:d=0.65','-f','null','-'],capture_output=True,text=True,check=True)
- starts=re.findall(r'silence_start: ([0-9.]+)',detect.stderr)
- ends=re.findall(r'silence_end: ([0-9.]+)',detect.stderr)
- pieces=[];last=0;cuts=[]
- for start,end in zip(starts,ends):
- start,end=float(start),float(end)
- a,b=round((start+.16)*sr),round((end-.16)*sr)
- if b<=a:continue
- pieces.append(audio[last:a]);last=b;cuts.append({'start':start,'end':end,'keptSeconds':.32})
- pieces.append(audio[last:])
- prepared=np.concatenate(pieces)
- wav=out/'TASK-VIRTUAL-005-step-01-Junhao-refined.wav'
- sf.write(wav,prepared,sr,subtype='PCM_16')
- shutil.copyfile(source,out/'natural-unedited.wav')
- settings=get_settings().model_copy(update={'service_base_url':'http://1.14.103.234:8123'})
- avatar='a1000000000000000000000000000005'
- with httpx.Client(trust_env=False,timeout=30) as client:
- response=client.get(str(settings.service_base_url)+f'/api/v1/avatars/{avatar}/assets/manifest.json');response.raise_for_status()
- expected=json.loads((ROOT/'ai_person/ai_person_service/builtin_avatars/instructors-v2.json').read_text('utf-8'))['instructors'][0]['bundle_sha256']['manifest.json']
- assert hashlib.sha256(response.content).hexdigest()==expected
- text='第一步,根据故障现象,确认是哪一个液压泵发生故障。'
- command=GenerationCommand(title='005 第一步 Junhao 连贯性修正版',text=text,avatarId='55',voiceId='49',speed=1,width=480,height=640,subtitles=False)
- worker=OpenGenerationWorker(None,settings,None,None)
- capture=out/'capture.webm';video=out/'TASK-VIRTUAL-005-step-01-Junhao-refined.mp4'
- print('pause cleanup:',cuts,'duration:',len(prepared)/sr,flush=True)
- worker.capture(command,avatar,wav,None,capture,threading.Event())
- subprocess.run(['ffmpeg','-v','error','-y','-i',str(capture),'-r','25','-c:v','libx264','-crf','20','-preset','veryfast','-pix_fmt','yuv420p','-c:a','aac','-b:a','128k','-movflags','+faststart',str(video)],check=True)
- subprocess.run(['ffmpeg','-v','error','-i',str(video),'-f','null','-'],check=True)
- probe=json.loads(subprocess.run(['ffprobe','-v','error','-show_streams','-show_format','-of','json',str(video)],capture_output=True,text=True,check=True).stdout)
- assert abs(float(probe['format']['duration'])-len(prepared)/sr)<.7
- shutil.copyfile(out.parent/'bridge-005-moss-junhao-v2-20260919/TASK-VIRTUAL-005-step-01-MOSS-Junhao.mp4',out/'previous.mp4')
- (out/'result.json').write_text(json.dumps({'text':text,'voice':'MOSS Junhao','seed':42,'cuts':cuts,'inputDuration':len(audio)/sr,'preparedDuration':len(prepared)/sr,'lipInputRate':16000,'metadata':probe,'publishedBindingsChanged':False},ensure_ascii=False,indent=2),'utf-8')
- (out/'index.html').write_text('''<!doctype html><html lang="zh-CN"><meta charset="utf-8"><title>005第一步讲解修正版</title><style>body{font:16px/1.8 system-ui;margin:32px auto;max-width:1000px;background:#eef5f3;color:#173e35}main{display:flex;gap:28px}section{background:white;padding:22px;flex:1;border-radius:14px}video{width:100%;aspect-ratio:3/4}audio{width:100%}a{color:#00856c}</style><h1>005 · 第一步讲解复核</h1><p>教员1 v2 · MOSS Junhao。修正版使用连贯句式、压缩长静音、统一16kHz口型输入。仍为独立试片,待人工确认口型和发音。</p><main><section><h2>本次修正版</h2><video controls src="TASK-VIRTUAL-005-step-01-Junhao-refined.mp4"></video><p>第一步,根据故障现象,确认是哪一个液压泵发生故障。</p><audio controls src="TASK-VIRTUAL-005-step-01-Junhao-refined.wav"></audio><p><a href="natural-unedited.wav">试听未处理的新语音</a></p></section><section><h2>上次版本</h2><video controls src="previous.mp4"></video></section></main><script>document.addEventListener('play',e=>{document.querySelectorAll('video,audio').forEach(a=>{if(a!==e.target)a.pause()})},true)</script></html>''','utf-8')
- print('completed',video,flush=True)
|