+
    {|jF                       R t ^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
H
t
 ^ RIHt ^ RIHtHtHtHtHt Rt^Kt0 R3mtRtRtR	tR
 R ltR R ltR R ltR R ltR R ltR R ltR R ltR4R R llt R R lt!R R lt"R R lt#R  R! lt$R" R# lt%R$ R% lt&R& R' lt'R( R) lt(R* R+ lt)R, R- lt*R. R/ lt+R0 R1 lt,]-R28X  d
   ],! 4        R# R# )5a  
transcribe_audio.py (Beta 0.5)

Transcribe an audio/video file locally on macOS (Apple Silicon) using:
- ffmpeg for decode/resample -> 16kHz mono WAV
- whisper.cpp via Homebrew `whisper-cli` with Metal acceleration

Outputs:
- <output_dir>/<stem>.txt

Formatting goals:
- No timestamps (for now)
- Conservative paragraph breaks based on pause gaps >= 1.5s (when JSON segments are available)
- Append a footer block containing:
  - word count (also useful inside WP)
  - a short "transcribed with" tagline + link to repo
  - Footer is wrapped in markers so the WordPress publisher can strip it by default.

Idempotency:
- Writes a state file in _LOGS to avoid reprocessing identical inputs.
)annotationsN)datetime)Path)AnyDictListOptionalTupleg      ?z[[TRANSCRIBE_FOOTER_START]]z[[TRANSCRIBE_FOOTER_END]]z:https://github.com/berchman/audio2blog-macos-whisper-metalc               $    V ^8  d   QhRRRRRR/# )   log_filer   msgstrreturnNone )formats   "transcribe_audio.py__annotate__r   /   s!      4 c d     c                D   V P                   P                  R R R7       \        P                  ! 4       P	                  R4      pRV RV R2pV P                  RRR7      ;_uu_ 4       pVP                  V4       R	R	R	4       \        VR
R7       R	#   + '       g   i     L; i)Tparentsexist_okz%Y-%m-%d %H:%M:%S[z] 
autf-8encodingN )end)parentmkdirr   nowstrftimeopenwriteprint)r   r   tslinefs   &&   r   _logr,   /   s|    OO$6		 	 !4	5Brd"SED	sW	-	-	 
.	$B 
.	-s   &BB	c                    V ^8  d   QhRRRR/# )r   pathr   r   r   r   )r   s   "r   r   r   8   s      d s r   c                &  a \         P                  ! 4       pV P                  R 4      ;_uu_ 4       o\        V3R lR4       F  pVP	                  V4       K  	  RRR4       VP                  4       #   + '       g   i     TP                  4       # ; i)rbc                 &   < S P                  R4      # )i   i   )read)r+   s   r   <lambda>sha256_file.<locals>.<lambda>;   s    !&&"5r   r   N)hashlibsha256r&   iterupdate	hexdigest)r.   hchunkr+   s   &  @r   sha256_filer<   8   sd    A	4A5s;EHHUO < 
 ;;= 
 ;;=s   )A11B	c                    V ^8  d   QhRRRR/# )r   
state_filer   r   Dict[str, Any]r   )r   s   "r   r   r   @   s      4 N r   c                    V P                  4       '       d(    \        P                  ! V P                  R R7      4      # / #   \         d    / u # i ; i)r   r   )existsjsonloads	read_text	Exception)r>   s   &r   
load_staterF   @   sP    	::j22G2DEE I  	I	s   %A   AAc               $    V ^8  d   QhRRRRRR/# )r   r>   r   stater?   r   r   r   )r   s   "r   r   r   I   s&     Y Y4 Y Y4 Yr   c                    V P                   P                  R R R7       V P                  \        P                  ! V^R R7      RR7       R# )Tr   indent	sort_keysr   r   N)r"   r#   
write_textrB   dumps)r>   rH   s   &&r   
save_staterO   I   s<    D48$**U1EPWXr   c                   V ^8  d   QhRR/# )r   r   zTuple[str, str]r   )r   s   "r   r   r   N   s     	 	o 	r   c                 0   \         P                  ! R 4      ;'       g    Rp \         P                  ! R4      ;'       g    Rp\        V 4      P                  4       '       g   \	        R4      h\        V4      P                  4       '       g   \	        R4      hW3# )ffmpegz/opt/homebrew/bin/ffmpegzwhisper-cliz/opt/homebrew/bin/whisper-cliz3ffmpeg not found. Install with: brew install ffmpegz=whisper-cli not found. Install with: brew install whisper-cpp)shutilwhichr   rA   
SystemExit)rR   whispers     r   resolve_binsrW   N   sv    \\(#AA'AFll=)LL-LG<  NOO=!!XYY?r   c               (    V ^8  d   QhRRRRRRRR/# )r   
ffmpeg_binr   in_pathr   wav_pathr   r   r   )r   s   "r   r   r   Z   s.     T Ts TT TT Td Tr   c                    V RR\        V4      RRRRRRR	\        V4      .p\        P                  ! VR
\        P                  \        P                  R7       R# )zc
Convert anything ffmpeg can decode (m4a/aac/mp3/mov/...) to:
  - mono
  - 16kHz
  - pcm_s16le wav
-y-i-vn-ac1-ar16000-c:a	pcm_s16leTcheckstdoutstderrN)r   
subprocessrunPIPE)rY   rZ   r[   cmds   &&& r   convert_to_wavrn   Z   sN     	Dc'lswHC NN3d:??:??Sr   c          
     ,    V ^8  d   QhRRRRRRRRRR	/# )
r   rY   r   rZ   r   out_path	intervalsList[Dict[str, Any]]r   r   r   )r   s   "r   r   r   m   sA     T TTT T $	T
 
Tr   c           	        VP                   P                  R R R7       V RR\        V4      RRRRR.	pV'       dl   . pV FA  p\        VR	,          4      p\        VR
,          4      pVP	                  RVR RVR R24       KC  	  VP                  RRP                  V4      .4       VP                  RR\        V4      .4       \        P                  ! VR \        P                  \        P                  R7       R# )Tr   r]   r^   r_   r`   ra   rb   rc   startr!   zvolume=enable='between(t\,z.3fz\,z)':volume=0z-af,rd   re   rf   N)
r"   r#   r   floatappendextendjoinrj   rk   rl   )	rY   rZ   rp   rq   rm   filtersitemrt   r!   s	   &&&&     r   !write_wav_with_silenced_intervalsr|   m   s     OO$6Dc'lswC D$w-(EU$CNN8s3s3i{[\  	

E388G,-.JJS]34NN3d:??:??Sr   c               D    V ^8  d   QhRRRRRRRRRRR	RR
RRRRRRRRR/# )r   whisper_binr   
model_pathr   r[   
out_prefixthreadsint	beam_sizebest_oflanguagetry_jsonbool	full_jsonr   zTuple[Optional[Path], Path]r   )r   s   "r   r   r      s     /A /A/A/A /A 	/A
 /A /A /A /A /A /A !/Ar   c
                   V R\        V4      R\        V4      RVRRR\        V4      R\        V4      R\        V4      R	\        V4      .p
VP                  R
4      pVP                  R4      pV'       d   V
P                  4       pVP                  VP	                  R4      R4       V	'       d"   VP                  VP	                  R4      R4        \
        P                  ! VRR7       VP                  4       '       d   VP                  4       '       d   W3# \
        P                  ! V
RR7       VP                  4       '       d   W3# RV3#   \
        P                   d     LLi ; i)z}
Runs whisper-cli. Produces a .txt always, and tries to produce .json when supported.
Returns (json_path_or_None, txt_path).
z-mz-f
--languagez--output-txtz--no-timestamps	--threads--beam-size	--best-ofz--output-filez.json.txtz--output-jsonz--output-json-fullT)rg   N)	r   with_suffixcopyinsertindexrj   rk   rA   CalledProcessError)r~   r   r[   r   r   r   r   r   r   r   rm   	json_pathtxt_pathcmd_jsons   &&&&&&&&&&    r   run_whisperr      s3   " 	c*oc(mhS\s9~S\ZC &&w/I%%f-H88:~6HOOHNN>:<PQ	NN840!!hoo&7&7 **
 NN3d#"))++I@@@@ ,, 		s   -E =E E E#"E#c                    V ^8  d   QhRRRR/# )r   r   r   r   rr   r   )r   s   "r   r   r      s     . .$ .+? .r   c           	     ,   \         P                  ! V P                  RRR7      4      p. p\        V\        4      '       d\   RV9   d(   \        VR,          \
        4      '       d   VR,          pM-RV9   d'   \        VR,          \
        4      '       d
   VR,          p\        V\
        4      '       g   . # . pV EFX  p\        V\        4      '       g   K  \        VP                  RR4      4      P                  4       pV'       g   KO  RpRpR	V9   dM   R
V9   dF   \        VR	,          4      p\        VR
,          4      pVR8  g   VR8  d   VR,          pVR,          pMRV9   d-   RV9   d&   \        VR,          4      p\        VR,          4      pMhRV9   db   \        VR,          \        4      '       dE   \        VR,          P                  RR4      4      p\        VR,          P                  RR4      4      pVP                  RVRVRV/4       EK[  	  V# )zi
Expect whisper.cpp-style json containing segments with start/end and text.
We'll tolerate a few shapes.
r   ignorer   errorssegmentstranscriptiontextr    Nt0t1        Y@rt   r!   
timestampsfrom        to)rB   rC   rD   
isinstancedictlistr   getstriprv   rw   )r   datar   outsr   rt   r!   s   &       r   parse_whisper_jsonr      s   
 ::i))78)LMD H$*T*-=t"D"DJ'H$D4I4)P)PO,Hh%%	
C!T""155$%++- 19!D'NE$.Ct|sTzu\eqj!G*%E%/CQ:aot#D#D!L/--fc:;E,++D#67C

GUE3=>5 6 Jr   c                    V ^8  d   QhRRRR/# r   r   r   r   r   )r   s   "r   r   r      s     / / / /r   c                N    \         P                  ! R RV P                  4       4      # )z[^a-z]r    )resublower)r   s   &r   normalize_wordr      s    66)R..r   c                    V ^8  d   QhRRRR/# )r   r   r   r   r   r   )r   s   "r   r   r      s     @ @ @ @r   c                r    \        V 4      pV\        9   d   R # \        \        P                  ! RV4      4      # )Tz(u+h+|u+m+|a+h+|e+r+m*))r   FILLER_WORDSr   r   	fullmatch)r   words   & r   is_filler_wordr      s.    $D|7>??r   c                    V ^8  d   QhRRRR/# )r   valuer   r   zOptional[float]r   )r   s   "r   r   r      s        r   c                   V f   R # \        V \        \        34      '       d.   \        V 4      pVR8  d
   VR,          # VR8  d
   VR,          # V# \        V 4      P	                  4       pV'       g   R # RV9  d    \        \        V4      4      # VP                  RR4      P                  R4      p \        VR	,          4      p\        V4      ^8  d   \        VR
,          4      M^ p\        V4      ^8  d   \        VR,          4      M^ pVR,          V^<,          ,           V,           #   \         d     R # i ; i  \         d     R # i ; i)Ni'       @@r   r   :ru   .i  )
r   r   rv   r   r   timestamp_to_seconds
ValueErrorreplacesplitlen)r   valr   partssecondsminuteshourss   &      r   r   r      s   }%#u&&El;<:;
u:D
$	'd44 LLc"((-Eb	"$'J!O#eBi."%e*/E"Iqt|gl*W44  		  s%   ;D) 1A7D; )D87D8;E
	E
c                    V ^8  d   QhRRRR/# )r   r{   r?   r   z'Tuple[Optional[float], Optional[float]]r   )r   s   "r   r   r     s     
 
^ 
(O 
r   c                   R V 9   d,   RV 9   d%   \        V R ,          4      \        V R,          4      3# RV 9   d,   RV 9   d%   \        V R,          4      \        V R,          4      3# RV 9   d,   RV 9   d%   \        V R,          4      \        V R,          4      3# V P                  R4      p\        V\        4      '       d5   \        VP                  R4      4      \        VP                  R4      4      3# R# )rt   r!   r   r   r   r   r   )NN)r   r   r   r   )r{   r)   s   & r   
item_timesr     s    $5D=#DM24He4UUU~$$,#DL13GT
3SSSt|#DJ/1Ed4j1QQQ	,	B"d#BFF6N35I"&&QU,5WWWr   c               $    V ^8  d   QhRRRRRR/# )r   r   r   
padding_msr   r   rr   r   )r   s   "r   r   r     s"     & & &# &BV &r   c                l   \         P                  ! V P                  R RR7      4      p. p\        V\        4      '       d^   \        VP                  R4      \        4      '       d   VR,          pM.\        VP                  R4      \        4      '       d
   VR,          p. p\        RVR,          4      pV EF  p\        V\        4      '       g   K  VP                  R4      p\        V\        4      '       d   V F  p\        V\        4      '       g   K  \        VP                  RVP                  R	R
4      4      4      P                  4       p	\        V4      w  rV	'       g   Kk  \        V	4      '       g   K~  V
f   K  Vf   K  W8  g   K  VP                  RV	R\        RW,
          4      RW,           RR/4       K  	  EK  \        VP                  R	R
4      4      P                  4       p	\        V4      w  rV	'       g   EKN  \        V	4      '       g   EKb  V
f   EKi  Vf   EKp  W8  g   EKy  VP                  RV	R\        RW,
          4      RW,           RR/4       EK  	  VP                  R R7       . pV F  pV'       d/   \        VR,          4      \        VR,          R,          4      8  d   VP                  \	        V4      4       KU  \        \        VR,          R,          4      \        VR,          4      4      VR,          R&   VR,          R,           RVR,           2VR,          R&   K  	  V# )r   r   r   r   r   r   r   wordsr   r   r    rt   r!   sourcesegmentc                J    \        V R ,          4      \        V R,          4      3# )rt   r!   )rv   )r{   s   &r   r3   *extract_filler_intervals.<locals>.<lambda>;  s    uT']3U4;5GHr   )key r   )rB   rC   rD   r   r   r   r   maxr   r   r   r   rw   sortrv   )r   r   r   r   rawpadsegr   r   r   rt   r!   mergedr{   s   &&            r   extract_filler_intervalsr     sy   ::i))78)LMDH$dhhz*D11J'H1488O,H "C
c:&
'C#t$$ eT""!$--488FDHHVR,@ABHHJ'-
4N400U5F3?_b_jJJgs37LeUXU^`hjpqr  swwvr*+113D#CJEtt,,1Bs[^[f

FD'3sEK3H%QTQZ\dfopq! $ HHHHI#%FtG}-fRj6G0HHMM$t*%fRj&7 8%U:LMr
5 &r
6 231T&\NCr
6  Mr   c               0    V ^8  d   QhRRRRRRRRRRR	R
/# )r   
audit_pathr   
audio_pathcleaned_pathr   r   rq   rr   r   r   r   )r   s   "r   r   r   F  sL     Y YYY Y 	Y
 $Y 
Yr   c                    R \        V4      R\        V4      RVR\        \        4      R\        V4      RV/pV P	                  \
        P                  ! V^RR7      RR	7       R
# )source_audiocleaned_audior   filler_wordscountrq   TrJ   r   r   N)r   sortedr   r   rM   rB   rN   )r   r   r   r   rq   audits   &&&&& r   write_filler_auditr   F  s^     	J\*j|,YYE $**U1EPWXr   c                    V ^8  d   QhRRRR/# )r   r   rr   r   r   r   )r   s   "r   r   r   X  s     $ $#7 $C $r   c                H   V '       g   R# . p. pRpV  EF  pVR,          P                  4       pV'       g   K%  VP                  R4      pVP                  R4      pVe   \        V\        \        34      '       d{   \        V\        \        34      '       d_   \	        V4      \	        V4      ,
          pV\
        8  d9   V'       d1   VP                  RP                  V4      P                  4       4       . pVP                  V4       \        V\        \        34      '       g   EK  \	        V4      pEK  	  V'       d/   VP                  RP                  V4      P                  4       4       RP                  R V 4       4      p	V	P                  R	R4      P                  4       p	V	# )
zv
Paragraph break when gap between previous end and next start >= PAUSE_PARAGRAPH_SECONDS.
Otherwise join with spaces.
r    Nr   rt   r!   r   z

c              3  8   "   T F  q'       g   K  Vx  K  	  R # 5i)Nr   ).0ps   & r   	<genexpr>'build_paragraph_text.<locals>.<genexpr>z  s     ,A!aas   	
z  )	r   r   r   r   rv   PAUSE_PARAGRAPH_SECONDSrw   ry   r   )
r   parascurprev_endr   txtrt   r!   gapr   s
   &         r   build_paragraph_textr   X  s:   
 EC $H&k! ggenJusEl$C$C
S[^ach]iHjHj,x0C--#SXXc]0023

3cC<((SzH! $ SXXc]((*+ ++,,
,C
++dC
 
&
&
(CJr   c                    V ^8  d   QhRRRR/# )r   r   r   r   r   r   )r   s   "r   r   r     s      S S r   c                    V P                  R R4      P                  R4       Uu. uF  qP                  4       '       g   K  VNK  	  pp\        V4      # u upi )r   r   )r   r   r   r   )r   r   r   s   &  r   
word_countr    sF    "ll45;;C@Q@dJJLTT@EQu: Rs   A Ac                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r     s        r   c                    \         V 9   d9   \        V 9   d.   V P                  \         ^4      ^ ,          P                  4       pV# V P                  4       # )   )FOOTER_START
FOOTER_ENDr   rstrip)r   pres   & r   strip_existing_footerr	    s@    t
d 2jjq)!,335
;;=r   c               (    V ^8  d   QhRRRRRRRR/# )	r   output_pathr   bodyr   wcr   r   r   r   )r   s   "r   r   r     s(     4 4$ 4c 4s 4t 4r   c                    \        V4      P                  4       pR \        RV R2R\         2\        R .pVR,           RP                  V4      ,           pV P                  VRR7       R# )r    z<h6>Word count: z</h6>z9Transcribed locally with whisper.cpp (Metal). More info: r   r   r   N)r	  r   r  REPO_URLr  ry   rM   )r  r  r  footer_linesfinals   &&&  r   write_transcriptr    sl     &,,.D 	
2$e$
CH:N
L 4K$))L11E573r   c                   V ^8  d   QhRR/# )r   r   r   r   )r   s   "r   r   r     s     x5 x5d x5r   c                    \         P                  ! 4       p V P                  R RR7       V P                  RRR7       V P                  R\        \        P
                  ! 4       R,          R,          R,          4      R	7       V P                  R
RR	7       V P                  R\        ^R7       V P                  R\        ^R7       V P                  R\        ^R7       V P                  RRRR7       V P                  R\        \        R7       V P                  4       p\	        VP                  4      P                  4       P                  4       p\	        VP                  4      P                  4       P                  4       pVP                  RRR7       \	        \        4      P                  4       P                  pVR,          pVR,          pVR,          p\	        VP                   4      P                  4       P                  4       pVP#                  4       '       g   \%        RV 24      hVP#                  4       '       g   \%        RV 24      h\'        4       w  r\)        V4      pRVP*                   RVP,                   RVP.                   RVP0                   R VP2                   R!VP4                   R"VP6                   2p\8        P:                  ! V R#V 2P=                  R$4      4      P?                  4       p\A        V4      pVPC                  R%/ 4       VR%,          PE                  \        V4      4      pV'       d2   VPE                  R&4      V8X  d   \G        VR'VP*                   24       R(# W2PH                   R)2,          pVPK                  R*4      p\G        VR+VP*                   R,VP*                   24        \M        WV4       Y2PH                  ,          p\G        TR.4       R(p \S        T
TTTTP.                  TP0                  TP2                  TP,                  RTP4                  R/7
      w  pp TP#                  4       '       d   TPU                  4        R0pT'       d-   TP#                  4       '       d   \Y        T4      p\[        T4      pT'       gK   TPK                  R)4      pTP#                  4       '       d"   TP]                  R$R1R27      P_                  4       MR0pR(pR(pTP4                  '       d   T'       d   TP#                  4       '       d   Y2PH                   R32,          pY2PH                   R42,          p\a        TTP6                  4      p\c        YTT4       \e        TTTTP6                  T4       \G        TR5T R6\g        T4       R724       MTP4                  '       d   \G        TR84       \i        T4      p\k        TTT4       R&TR9TR:TR;\        T4      R<T'       d   \        T4      MR(R=T'       d   \        T4      MR(R>\l        Pn                  ! 4       Pq                  R?R@7      /TR%,          \        T4      &   \s        Y~4       \G        TRAT 24       R(#   \N        PP                   d   p\G        TR-T 24       h R(p?ii ; i  \V         d     ELAi ; i   TP#                  4       '       d   TPU                  4        i i   \V         d     i i ; i; i)B
audio_filez/Path to input audio/video (mp3/m4a/aac/mov/...))help
output_dirzDirectory to write transcriptsz--modelz$_00_GIT_MASTER/transcription-to-blog_MODELSzggml-medium.bin)defaultr   enr   )typer  r   r   z--clean-fillers
store_truez-Write a .cleaned.wav with filler words muted.)actionr  z--filler-padding-msTr   _LOGSztranscribe.logztranscribe_state.jsonzAudio file not found: zModel not found: zmodel=z|lang=z	|threads=z|beam=z|best=z|clean_fillers=z|filler_padding_ms=|r   runsrun_shazSKIP (checksum match): Nr   z.wavzConverting to wav: z -> zERROR converting with ffmpeg: z;Backend: whisper-cpp (Homebrew) | Metal GPU (Apple Silicon))
r~   r   r[   r   r   r   r   r   r   r   r    r   r   z.cleaned.wavz.fillers.jsonzCleaned audio written: z (z muted interval(s))z4Clean fillers skipped: no JSON timing file available	audio_shasettingsoutputr   filler_audit
updated_atr   )timespeczTranscript written: ):argparseArgumentParseradd_argumentr   r   homer   DEFAULT_FILLER_PADDING_MS
parse_argsr  
expanduserresolver  r#   __file__r"   modelrA   rU   rW   r<   namer   r   r   r   clean_fillersfiller_padding_msr5   r6   encoder9   rF   
setdefaultr   r,   stemr   rn   rj   r   r   unlinkrE   r   r   rD   r   r   r|   r   r   r  r  r   r$   	isoformatrO   )apargsr   out_dir
script_dirlog_dirr   r>   r   rY   r~   r"  settings_keyr!  rH   prevout_txtr[   er   r   r   r  segstxt_fallbackr   r%  rq   r  s                                r   mainrE    s   		 	 	"BOOL'XOYOOL'GOHOOIs499;9_+_bk+kn+  (AO  BOOL$O/OOKc1O5OOMQO7OOKc1O5OO%lApOqOO)=VOW==?Ddoo&113;;=J4??#..088:GMM$M.h'')00J7"G))H22Jdjj!,,.668J1*>??,ZL9::*nJ J'I
!i~ Nvdll^?4CUCUBV W!334	6 
 nn	{!L>:AA'JKUUWGz"E	VR =S_-D#w.X00ABC??+400G %%f-H((9hmm_MNzx8
 ??*JPQI)#!!LLnnLL]]((
	8	  !
 DY%%''!),#D)!--f5T`TgTgTiTi|%%wx%HNNPoqMLiI,<,<,>,>__$5\"BBOO#4M!BB,Y8N8NO	)*-QZ[<]DDZDZ\efX0r#i.AQQdef				XMN	D	BWdB' 	7YL#g,}]+$\L)thlln..	.B&E&M#j/" z!)'34E (( X7s;<4  			  ! ! 		s[   ;Y (A
Z 3&Z Z	4ZZ	ZZ[ &[[[[[[__main__>   aheruhumahhermuhhumm)TF).__doc__
__future__r   r(  r5   rB   r   rS   rj   sysr   pathlibr   typingr   r   r   r   r	   r   r,  r   r  r  r  r,   r<   rF   rO   rW   rn   r|   r   r   r   r   r   r   r   r   r   r  r	  r  rE  __name__r   r   r   <module>rU     s   , #    	   
   3 3  C,*
GY
	T&T2/Ad.b/@8
&RY$$N
4x5v zF r   