o
    ij'                     @   sd  d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZejejeZejejedZejedddZejeddZejZd	Zejd
d  ZedkrejeddddZejeddZ eZ!ejedZ"dZ#dZ$dZ%ejedZ&dZ'n?edkrejeddddZ ejedddZ!ejeddZ#eje#dZ"ejeddddZ$dZ%dZ&dZ'ne(ded ej)de dd!l*m+Z+m,Z,m-Z-m.Z.m/Z/ da0da1e2 Z3da4d"d# Z5d$d% Z6d&d' Z7d(d) Z8d?d+d,Z9d@d/d0Z:dAd1d2Z;dBd4d5Z<d6d7 Z=dCd:d;Z>d<d= Z?e@d>kr0e?  dS dS )Du  Turn-based E2E inference: one utterance (wav + emotion) -> face(52) + body(vrma).

This is Phase A — the offline core the OpenAI Realtime bridge (Phase C) will call
on every `response.done`. No mic, no corpusify: the audio is treated as the real
assistant voice it will be in production.

  face : V3 face model         (face_v3_infer.render_face, in-process, warm)
  body : kemix velocity codec+flow  (persistent warm worker, separate process)

Writes  outputs/turns/<id>/{face.json, body.vrma, audio.wav, meta.json}  and the
combined viewer (Phase B) reads that folder.

    python infer_turn.py --wav <16k mono wav> --emotion excitement --id demo1
    Nz..zmotion-blenderexperimentsz
wave-handsmotion
GestureVRM   AVATAR_BODY_PROFILEv4solooutputs	gen_trainruns#solo_body_v1__44b_vel__scratch__r01	generatorzbest.pthmean_stdzB/data/mocap-convertor/datasets/solo_20260722_body_v1/processed/npzbasezcodec_selection.jsonTweights_v4_velvqvae_kemix_v4_velkemix_npz_v4	seed_bankzseed_avg.npztag3Fzunknown AVATAR_BODY_PROFILE=z; expected 'v4' or 'solo')render_faceload_face_modelEMOTION_LABELS
FaceStreamARKIT_52_NAMESc                  C   s   t tjtddtdtdtdtdt	ddg} t
r| d	t
g tr'| d
tg tr.| d tj| tdtjtjd}|jD ]}| dkrI|  S q=td)zSpawn the persistent body worker; block until it reports @@READY@@ (model warm).
    Separate process because V3-face and GVRM-body both use top-level `models`/`scripts`
    packages that collide in one interpreter.zbody_worker.pyz--ckptz--vqvae_dirz--meanstd_dirz--npzz--model_kind--steps8z--proto_seedz--codec_selectionz--continuous_decodeT)cwdtextstdinstdoutz	@@READY@@zbody worker exited before READY)PYospathjoinHERE	BODY_CKPT	VQVAE_DIRMEANSTD_DIRBODY_NPZBODY_MODEL_KIND
PROTO_SEEDextendCODEC_SELECTIONCONTINUOUS_DECODEappend
subprocessPopenPIPEr   stripRuntimeError)cmdpline r7   infer_turn.py_start_body_workerL   s&   


r9   c                 C   s   t V tdu st durt atjtj| ddd  tj  tj	D ].}|
drSt|tdd  }|dsHtd|dd	 |  W  d   S q%td
1 s[w   Y  dS )zNSend one command to the persistent body worker and return its sentinel result.N),:)
separators
z
@@RESULT@@okzbody worker: errorunknownzbody worker closed unexpectedly)
_BODY_LOCK
_BODY_PROCpollr9   r   writejsondumpsflushr   
startswithloadslenr2   getr3   )jobr6   resr7   r7   r8   _body_requestb   s   



rN   c                   C   s   t du rt a t S )z<Return the warm V3 face model, loading it once on first use.N)_FACE_MODELr   r7   r7   r7   r8   get_face_models   s   rP   c                  C   s  t   } t }t   |  }t   }tddi t   | }t   }tsjdt  }d}|d t }tt	|d 
 }	td|d|d	 td
|d|	d td|d t||jtj|tjdd|dd dat   | }
t|dt|dt|
dtt   |  ddS )zGWarm both independent GPU model processes before realtime audio starts.opping	__warmup_   i>     stream_startneutral)rQ   	stream_idemotionemit_framesstream_chunk)rQ   rX   rY   pcm_b64stream_cancelrQ   rX   )dtyper   )frame_startT   )face_sbody_sstream_stotal_s)timerP   rN   _STREAMING_WARMr!   getpidFPSbase64	b64encodebytesdecoder   push_pcmnpzerosfloat32round)t0
face_modelt_facet1t_bodyt2sidrZ   	n_samplessilence_b64t_streamr7   r7   r8   warm_models{   s8   
r}         @c                 C      t d| |||dS )z=Send one batch job to the warm body worker; writes body.vrma.generate)rQ   wavrY   out_vrmaguidancerN   )wav_pathrY   r   r   r7   r7   r8   body_generate   s   r   rW   rT   c                 C   r   )NrV   )rQ   rX   rY   r   rZ   r   )rX   rY   r   rZ   r7   r7   r8   body_stream_start   s   r   c                 C   s&   d| |t |d}|r||d< t|S )Nr[   )rQ   rX   r\   rG   rY   )boolrN   )rX   r\   rY   rG   rL   r7   r7   r8   body_stream_chunk   s   r    c                 C   s    d| |d}|r||d< t |S )N
stream_end)rQ   rX   r\   rY   r   )rX   r\   rY   rL   r7   r7   r8   body_stream_end   s   r   c                 C   s   t d| dS )Nr]   r^   r   )rX   r7   r7   r8   body_stream_cancel   s   r         c                    sn  |t vrtd|dt  t  tjtdd|}tj|dd t }t	| ||t
|d\ }tj|d}	d	d
lm}
 t|tt||
 fddt|D dt|	d t | }t }tj|d}t| |||d t | }tj|d}t| | ||tt||||dddt|dt|dtt | ddd}tj|ttj|ddddd |S )Nzunknown emotion u    — must be one of r	   turnsT)exist_ok)level	model_devscenario_idz	face.jsonr   )r   c                    s   g | ]}d d  | D qS )c                 S   s   g | ]	}t t|d qS )r   )rr   float).0vr7   r7   r8   
<listcomp>   s    z*render_turn.<locals>.<listcomp>.<listcomp>r7   )r   tbsr7   r8   r      s    zrender_turn.<locals>.<listcomp>)r   fps
num_framesnamesblendshapeswz	body.vrma)r   z	audio.wavrU   )rb   rc   re   )idrY   r   face_framesr   stepsr   facebodyaudiotimingz	meta.jsonF   ensure_asciiindent)r   
ValueErrorrP   r!   r"   r#   r$   makedirsrf   r   rO   face_v3_inferr   rE   dumpri   intrangeopenr   shutilcopyfilerr   )r   rY   out_idr   r   r   turn_dirrs   Tf	face_jsonr   ru   rv   	body_vrmarw   	audio_outmetar7   r   r8   render_turn   s>   "r   c                  C   s   t  } | jddd | jddddt d | jd	d d
d | jdtdd | jdtdd | jdtdd |  }|jpHdtt		  }t
|j|j||j|j|jd}ttj|ddd td| d d S )Nz--wavT)requiredz	--emotionrW   zone of: z, )defaulthelpz--idzturn id (folder name)z--levelr   )typer   r   r   z
--guidancer~   turn_)r   r   r   Fr   r   z
-> outputs/turns/z&/  (face.json + body.vrma + audio.wav))argparseArgumentParseradd_argumentr#   r   r   r   
parse_argsr   rf   r   r   rY   r   r   r   printrE   rF   )apar   r   r7   r7   r8   main   s   r   __main__)r~   )rW   r~   rT   )NF)r   N)r   r   r~   )A__doc__r   rj   rE   r!   r   r/   sys	threadingrf   wavenumpyro   torchr"   dirnameabspath__file__r$   r#   KEMIXWHGVRM
executabler    ri   environrK   r2   lowerBODY_PROFILESOLO_RUNr%   r&   r'   r(   r*   r)   r,   r-   r   insertr   r   r   r   r   r   rO   rB   LockrA   rg   r9   rN   rP   r}   r   r   r   r   r   r   r   __name__r7   r7   r7   r8   <module>   s    


"



'

