Ë
    Eÿ´jB  ã            	       óx  — d Z ddlZddlZddlmZ ddlZddlmZmZm	Z	m
Z
mZmZmZmZmZmZ dZdedefd„Zd	edefd
„Zdededeeee   f   fd„Zdee   dedefd„Zdededeeeef   fd„Zedk(  rHddlZdZ  ee d«      \  Z!Z"Z# e$de!› de#d›d�«        e$de"› �«        e$e"jK                  «       dd «       yy)zDGenerate voiceover audio + word-timed ASS subtitles from story text.é    N)ÚPath)
Ú	AUDIO_DIRÚSUB_COLOR_HIGHLIGHTÚSUB_COLOR_NORMALÚSUB_FONTÚSUB_FONT_SIZEÚSUB_MARGIN_VÚSUB_OUTLINE_COLORÚSUB_OUTLINE_SIZEÚSUB_WORDS_PER_GROUPÚ	TTS_VOICEi€–˜ ÚticksÚreturnc                 ó   — | t         z  S )N)Ú_TICKS_PER_SEC)r   s    ú/root/workspace/ytshorts/tts.pyÚ_ticks_to_secondsr      s   € Ø”>Ñ!Ð!ó    Útc                 óz   — t        d| «      } t        | dz  «      }t        | dz  dz  «      }| dz  }|› d|d›d|d›�S )u3   Convert float seconds â†’ ASS timestamp  H:MM:SS.ccg        i  é<   ú:Ú02dz05.2f)ÚmaxÚint)r   ÚhÚmÚss       r   Ú_seconds_to_assr      sQ   € äˆC�‹€AÜˆA�‰I‹€AÜˆQ�‰X˜"ÑÓ€AØ	ˆB‰€AØˆS��!�C�˜˜!˜E˜Ð#Ð#r   ÚtextÚvoicec           
   ƒ   ó\  K  — t        j                  | |dd¬«      }g }g }|j                  «       2 3 d{  –—† }|d   dk(  r|j                  |d   «       Œ&|d   dk(  sŒ/|j                  |d   t	        |d	   «      t	        |d	   |d
   z   «      dœ«       Œf7 Œa6 dj                  |«      |fS ­w)zAStream edge-tts and collect audio bytes + per-word timing events.z+8%ÚWordBoundary)ÚrateÚboundaryNÚtypeÚaudioÚdatar    ÚoffsetÚduration)ÚwordÚstartÚendr   )Úedge_ttsÚCommunicateÚstreamÚappendr   Újoin)r    r!   ÚcommunicateÚaudio_chunksÚwordsÚchunks         r   Ú_synthesiser7   &   sÁ   è ø€ ä×&Ñ& t¨U¸ÈÔX€KØ "€LØ€Eà"×)Ñ)Ó+÷ 
ð 
ˆeØ�‰=˜GÒ#Ø×Ñ  f¡Õ.Ø�6‰]˜nÓ,Ø�L‰Là! &™MÜ.¨u°X©Ó?Ü,¨U°8©_¸uÀZÑ?PÑ-PÓQñõð	
øÐ+ð �8‰8�LÓ! 5Ð(Ð(ùs+   ‚.B,°B´BµB¸&B,Á7B,ÂBÂB,r5   Útotal_durationc                 ó~  — dt         › dt        › dt        › dt        › dt        › dt
        › d�}g }t        dt        | «      t        «      D ]  }|j                  | ||t        z    «       Œ g }t        |«      D �]-  \  }}|d   d   }|d	z   t        |«      k  r||d	z      d   d   }	n|d
z   }	t        |«      D ]ì  \  }
}|
r|d   n|}|
d	z   t        |«      k  r||
d	z      d   n|	}|
dk(  rdnd}g }t        |«      D ]g  \  }}t        j                  dd|d   «      j                  «       }||
k(  r$|j                  dt        › d|› dt        › d�«       ŒW|j                  |«       Œi |dj                  |«      z   }|j                  dt!        |«      › dt!        |«      › d|› �«       Œî �Œ0 |dj                  |«      z   dz   S )a  
    Group words into fixed-size groups and emit one ASS event per group.
    Within each event the current word is highlighted in yellow; the rest
    are white.  Each word gets its own event overlapping the group so the
    highlight moves word-by-word.
    ar  [Script Info]
ScriptType: v4.00+
PlayResX: 1080
PlayResY: 1920
WrapStyle: 1
ScaledBorderAndShadow: yes

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,ú,z,&H000000FF,z#,&H64000000,-1,0,0,0,100,100,0,0,1,z,0,2,40,40,z],1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
r   r,   é   g333333Ó?z){\fscx80\fscy80\t(0,90,\fscx100\fscy100)}Ú z[^\w\'\-\,\.\!\?]r+   z{\cz&\fscx112\fscy112}z&\fscx100\fscy100}ú zDialogue: 0,z,Default,,0,0,0,,ú
)r   r   r   r
   r   r	   ÚrangeÚlenr   r1   Ú	enumerateÚreÚsubÚupperr   r2   r   )r5   r8   ÚheaderÚgroupsÚiÚeventsÚg_idxÚgroupÚgroup_startÚ	group_endÚw_idxr+   Úw_startÚw_endÚpopÚpartsÚjÚwÚcleanÚ	line_texts                       r   Ú
_build_assrV   ;   s2  € ð
ô ˆz˜œ=˜/¨Ô+;Ð*<¸LÔIZÐH[Ð[~ô  @Pð  Qð  Q\ô  ]ið  \jð jð€Fð   "€FÜ�1”c˜%“jÔ"5Ó6ò :ˆØ�‰�e˜A Ô$7Ñ 7Ð8Õ9ð:ð €FÜ! &Ó)ó  ‰ˆˆuØ˜A‘h˜wÑ'ˆà�1‰9”s˜6“{Ò"Ø˜u q™yÑ)¨!Ñ,¨WÑ5‰Ià&¨Ñ,ˆIä$ UÓ+ò 	‰KˆE�4á',�d˜7’m°+ˆGØ16¸±¼SÀ»ZÒ1G�E˜% !™)Ñ$ WÒ-ÈYˆEàFKÈqÂjÑBÐVXˆCð  "ˆEÜ! %Ó(ò 	(‘��1ÜŸ™Ð3°R¸¸6¹ÓC×IÑIÓK�Ø˜’:à—L‘LØÔ 3Ð4Ð4IÈ%ÈØÔ 0Ð1Ð1FðHõð
 —L‘L Õ'ð	(ð ˜cŸh™h u›oÑ-ˆIØ�M‰MØœ¨wÓ7Ð8¸¼/È%Ó:PÐ9Qð R#Ø#, +ð/õò+	ð ðD �D—I‘I˜fÓ%Ñ%¨Ñ,Ð,r   Ústemc                 ó  — t         |› d�z  }t         |› d�z  }t        j                  t        | t        «      «      \  }}|j                  |«       |st        d«      ‚|d   d   dz   }|j                  t        ||«      d¬«       |||fS )	z—
    Synthesise `text` and write:
      AUDIO_DIR/<stem>.mp3
      AUDIO_DIR/<stem>.ass

    Returns (mp3_path, ass_path, audio_duration_seconds).
    z.mp3z.assz)edge-tts returned no word-boundary eventséÿÿÿÿr-   g      à?zutf-8)Úencoding)	r   ÚasyncioÚrunr7   r   Úwrite_bytesÚRuntimeErrorÚ
write_textrV   )r    rW   Úmp3_pathÚass_pathÚaudio_bytesr5   r8   s          r   Úgenerate_ttsrc   |   s•   € ô ˜d˜V 4˜=Ñ(€HÜ˜d˜V 4˜=Ñ(€Hä Ÿ™¤[°´yÓ%AÓBÑ€K�à×Ñ˜Ô%áÜÐFÓGÐGà˜2‘Y˜uÑ%¨Ñ+€NØ×Ñœ
 5¨.Ó9ÀGÐÔLà�X˜~Ð-Ð-r   Ú__main__aŒ  I never expected my coworker to steal my lunch for three months. So I put a tiny tracker inside a sealed container of leftovers. At noon the signal led straight to my manager's office. He had been taking food because he thought nobody would say a word. I showed HR the location history and suddenly he had a lot to explain. The next day he brought in sandwiches for everyone. Nobody touched them.Útest_ttszAudio: z  (z.1fzs)zASS:   iX  )&Ú__doc__r[   rB   Úpathlibr   r.   Úconfigr   r   r   r   r   r	   r
   r   r   r   r   r   Úfloatr   Ústrr   ÚtupleÚbytesÚlistÚdictr7   rV   rc   Ú__name__ÚsysÚsampleÚmp3ÚassÚdurÚprintÚ	read_text© r   r   ú<module>rx      s*  ðÙ JÛ Û 	Ý ã ÷÷ ÷ ð €ð"˜Sð " Uó "ð$�uð $ ó $ð)˜Cð )¨ð )°°e¸TÀ$¹ZÐ6GÑ0Hó )ð*>-�d˜4‘jð >-°%ð >-¸Có >-ðB.�sð . #ð .¨%°°d¸EÐ0AÑ*Bó .ð0 ˆzÒÛð	Sð ñ ! ¨Ó4�M€CˆˆcÙ	ˆG�C�5˜˜C ˜9 BÐ
'Ô(Ù	ˆG�C�5ˆ/ÔÙ	ˆ#�-‰-‹/˜$˜3Ð
Õ ð r   