U
    8j,                     @  s   d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
 ddlmZ ee jd Zed	 d
 Zed ed ed gZed	 d ZdddddZdddddddZddddZd$dddddddd Zd!d" Zed#kree  dS )%a,  Standalone audio generation for quality evaluation.

Generates ElevenLabs audio for a sample of seed scenario turns, so you can
listen and evaluate quality BEFORE running the full data pipeline.

Usage:
    # Set env vars first:
    export ELEVENLABS_API_KEY=...
    export ELEVENLABS_VOICE_ID=...    # optional
    export ELEVENLABS_MODEL=eleven_multilingual_v2   # or eleven_v3

    # Generate first 10 turns (in order):
    python -m scripts.compiler.generate_audio --limit 10

    # Generate 10 random turns:
    python -m scripts.compiler.generate_audio --limit 10 --random

    # One turn per emotion (16 total, good for quality matrix):
    python -m scripts.compiler.generate_audio --one-per-emotion

    # Specific scenarios:
    python -m scripts.compiler.generate_audio --scenarios long_125,daily_001
    )annotationsN)Path)List   )	synth_all   dataemotionzseed_train_final.jsonlzseed_val.jsonlzseed_test.jsonlZaudio_previewlist)source_pathsreturnc              
   C  sJ   g }| D ]<}|  sq|  }|D ]}|t| q$W 5 Q R X q|S )zKLoad all scenarios from multiple jsonl files, preserving order within each.)existsopenappendjsonloads)r   	scenariospfline r   c/home/ubuntu/service/kemix-engine/package/face/animasync-face-v3/scripts/compiler/generate_audio.pyload_scenarios-   s    
r   r   strintbool)out_dirscenario_id	num_turnsr   c                 C  sN   t |D ]@}dd | | d| dD }|rB|d  jdk r dS qdS )	u  Check if all turns of this scenario already have valid mp3 files.

    Filters out *.raw.mp3 orphans — these are written mid-synthesis and
    deleted on success; if a process is killed they remain on disk and
    would otherwise make a partially-failed scenario look complete.
    c                 S  s   g | ]}|j d s|qS z.raw.mp3nameendswith).0mr   r   r   
<listcomp>A   s   z)scenario_already_done.<locals>.<listcomp>_tz_*.mp3r     FT)rangeglobstatst_size)r   r   r   timatchesr   r   r   scenario_already_done9   s    r.   )r   c                 C  sL   g }| D ]>}|d }t |d D ]$\}}|d  r ||||f q q|S )z4Return list of (scenario_id, turn_index, turn_dict).r   turnstext)	enumeratestripr   )r   itemsscensidr,   turnr   r   r   flatten_turns_for_scenariosJ   s    r7   F)r   r   
batch_sizeone_per_emotionspecificc              	     s  |rt |  fdd| D S |rt| }t  }g }|D ],}|d d }	|	|kr8||	 || q8g }
|D ]0\}}}|
| d| d|d  |gd qn|
S g }| D ]P}tdd	 |d
 D }|dkrqt||d |rq|| t||kr qq|S )a
  Pick the next N scenarios to process, skipping already-done ones.

    - specific: list of scenario_ids to target (overrides batch logic)
    - one_per_emotion: diagnostic mode, one turn per emotion
    - default: next `batch_size` unfinished scenarios in order
    c                   s   g | ]}|d   kr|qS )r   r   r#   sZ	requestedr   r   r%   `   s      z(pick_scenarios_batch.<locals>.<listcomp>r   r	   Z_singleTurn_)r   r/   c                 s  s   | ]}|d    rdV  qdS r0   r   Nr2   r#   tr   r   r   	<genexpr>w   s      z'pick_scenarios_batch.<locals>.<genexpr>r/   r   r   )setr7   addr   sumr.   len)r   r   r8   r9   r:   r3   seenZpicked_itemsxemoZsynthr5   r,   r6   Z
unfinishedr4   r   r   r=   r   pick_scenarios_batchU   s<    	


rK   c            +        s  t  } | jddttdd | jdttd | jdtdd	d
 | jddddd | jddd d dd
 | jdtdd | jdd dd | jdddgdd | jddddd |    jj	d d d! t
 j}t|}td"d# |D }td$| d%| d& t fd'd#|D }td(| d)| d* t| j j j jd+}td,t| d- |sjtd. d S d/d0lm}m} g g g g g f\}}	}
}}g g g   }}}|D ]}|d1 }|d2p|d3}d }|r||d4 }|| }t|d4 D ]\}}|d5  sq| d6| d7|d8  d9} j| }||d5  |	|d8  |
|d: || ||rv|nd  |d  || |||||d8 |d:|d5 ||d; qqi } jr؈ j|d<< td=d# |D r&td>d# |D }td?| d@ttdAd# |D  dB tdCd# |D }tdDd# |D }t|| }tdEd# t||D }tdF| dGt| dH tdI| dJ tdI| dK| dL|dM dN dOdP  jrtdQ d S tdRt| dS j  dT j!  t"||f j  j!|	|
|||dU|I d H } jdV } dWdX  j#dYD }!i }"| $ r| j%dZd[Z}#|#D ]N}$zt&'|$}%W n t(k
r   Y q`Y nX |%d\}&|&|!kr`|%|"|&< q`W 5 Q R X d]}'t||D ]4\}(})|(d\ |"kr|'d/7 }'|)|(d^< |(|"|(d\ < q| j%d_dZd[2}#t)|"D ]"}&|#*t&j+|"|& d`dadb  qW 5 Q R X tdct|" dd|' det||'  df t|}*tdg|* d)t| dht| di tdj j  tdk|   tdl d S )mNz	--sources+z9Scenario jsonl files to combine (default: train+val+test))nargstypedefaulthelpz	--out_dir)rN   rO   z--batch_size
   z*Number of scenarios per batch. Default 10.)rN   rO   rP   z--one-per-emotion
store_truer9   z@Diagnostic mode: 16 turns (one per emotion) instead of scenarios)actiondestrP   z--scenariosc                 S  s   dd |  dD S )Nc                 S  s   g | ]}|  r|  qS r   r@   )r#   rI   r   r   r   r%      s      z*main.<locals>.<lambda>.<locals>.<listcomp>,)split)r<   r   r   r   <lambda>       zmain.<locals>.<lambda>z>Specific scenario IDs (comma-separated), overrides batch logicz--concurrency   z
--voice_idu%   Override auto emotion→voice mapping)rO   rP   z	--backend
elevenlabsedge)choicesrO   z	--dry-rundry_runz:Print char count + voice plan, then exit without API callsT)parentsexist_okc                 s  s$   | ]}t d d |d D V  qdS )c                 s  s   | ]}|d    rdV  qdS r?   r@   rA   r   r   r   rC      s      !main.<locals>.<genexpr>.<genexpr>r/   N)rF   r;   r   r   r   rC      s    zmain.<locals>.<genexpr>z[pool] z scenarios, z turns totalc                 3  s6   | ].}t  j|d  tdd |d D rdV  qdS )r   c                 s  s   | ]}|d    rdV  qdS r?   r@   rA   r   r   r   rC      s      r`   r/   r   N)r.   r   rF   r;   argsr   r   rC      s    z[progress] /z scenarios already have audio)r9   r:   z	[select] z scenarios this batchuC   [info] Nothing to do — all selected scenarios already have audio.r   )dominant_base_for_turnsFEMALE_BY_BASEr   long_solo_r/   r0   r&   r>   r	   z.mp3vad)filer   Z
turn_indexr	   rh   r0   Z	monologuevoice_idrj   c                 s  s   | ]}|d k	V  qd S Nr   r#   vr   r   r   rC      s     c                 s  s   | ]}|d k	rdV  qd S )Nr   r   rl   r   r   r   rC      s      z[voice] z) turns locked to dominant-emotion voice (c                 s  s   | ]}|r|V  qd S rk   r   rl   r   r   r   rC      s      z unique voices)c                 s  s   | ]}t |V  qd S rk   )rG   rA   r   r   r   rC      s     c                 s  s(   | ] }|  r| jd krdV  qdS )r'   r   N)r   r*   r+   r#   r   r   r   r   rC      s      c                 s  s0   | ](\}}|  r | jd kst|V  qdS )r'   N)r   r*   r+   rG   )r#   rB   r   r   r   r   rC      s    z[chars] z characters across z turnsz[plan]  z( turns will be skipped (already on disk)z turns will be synthesized (~z chars / ~$r'   g333333?z.2f)z#[dry-run] exiting without API callsz[tts] z turns, backend=z, concurrency=)backendconcurrencyemotionsvadsvoice_seedsvoice_pools	voice_idszmanifest.jsonlc                 S  s   h | ]}|j d s|j qS r   r    rn   r   r   r   	<setcomp>  s    zmain.<locals>.<setcomp>z*.mp3zutf-8)encodingri   r   okwF)ensure_ascii
z[manifest] z rows total (+z new this batch, z	 updated)z
[done] z turns generated (z scenarios)z  audio:    z  manifest: uU   
[next batch] just re-run the same command — already-done scenarios will be skipped),argparseArgumentParseradd_argumentr   DEFAULT_SOURCES	AUDIO_OUTr   
parse_argsr   mkdirr   sourcesrG   rF   printrK   r8   r9   r   ttsrd   re   
startswithr1   r2   r   getrj   anyrD   zipr]   rp   rq   r   r)   r   r   r   r   	Exceptionsortedwritedumps)+apr   Ztotal_scenariosZtotal_turns
done_countZpicked_scenariosrd   re   textsrr   rs   	out_pathsZmanifestrt   ru   rv   r4   r5   is_monologuescenario_voice_idZdominant_baser,   r6   fnamepathZ
tts_kwargsZn_domZtotal_charsZ	will_skipZ
will_synthZwill_synth_charsZok_flagsZmanifest_pathZon_diskZkeptr   r   rowfnZ	new_countr$   ry   Z	successesr   ra   r   main   s   



  




   
(&" 	



*($r   __main__)FN)__doc__
__future__r   r}   asyncior   randompathlibr   typingr   r   r   __file__resolver^   ZPROJECT_ROOTZEMO_DIRr   r   r   r.   r7   rK   r   __name__runr   r   r   r   <module>   s2     - %