
    -Oj                        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mZmZ ej                  j!                  ej                  j#                  e            Zej                  j)                  ed      Zej                  j)                  edd      Zej                  j/                  d      ZdZd	Zd
Zd Zd Zd Zd Ze dk(  r e        yy)a_  Personal research podcast: turn a text source into a spoken episode + RSS.

Pipeline: source text -> gemma writes a spoken-friendly script -> Piper TTS (local)
-> MP3 -> episodes/ -> podcast.xml (RSS with enclosures; subscribe from any app).

RULE: machine voices never pronounce Lakota. The script prompt instructs gemma to
spell out any Lakota term letter by letter ("u-ogonek, k, i-acute...") or refer to
it descriptively. Human-recorded audio dropped into episodes/human/ is included in
the feed as-is, unmodified.

Usage: make_episode.py --source ../digest.md --title "Research radar" [--base-url URL]
    N)datetimetimezoneepisodesvoiceszen_US-lessac-medium.onnxz~/research-feed/.venv/bin/piperz)http://localhost:8000/v1/chat/completionszgemma4-26b-a4bup  Turn the following notes into a spoken podcast script for one host.
Rules:
- Conversational, clear, no headers or markdown — words to be read aloud only.
- Never read URLs, arXiv IDs, or file paths aloud.
- CRITICAL: never attempt to pronounce Lakota or other Indigenous-language words.
  When one appears, either describe it ("the Lakota word for 'plums'") or spell it
  slowly letter by letter, naming diacritics ("u with a nasal hook, k, i with an
  accent..."). Machine pronunciation of these words teaches learners errors.
- 3 to 6 minutes of speech. Start with a one-sentence summary of the episode.

NOTES:
{notes}
c           	         t         dddt        j                  | d d       dgd}t        j                  j                  t        t        j                  |      j                         dd	i
      }t        j                  j                  |d      5 }t        j                  |      d   d   d   d   j                         cd d d        S # 1 sw Y   y xY w)Ng333333?i  useri.  )notes)rolecontent)modeltemperature
max_tokensmessageszContent-Typezapplication/json)dataheaders,  )timeoutchoicesr   messager   )LLMSCRIPT_PROMPTformaturllibrequestRequestLLM_URLjsondumpsencodeurlopenloadstrip)r	   bodyreqrs       1/home/mgrim/research-feed/podcast/make_episode.py
llm_scriptr'   '   s    D"(]5I5IPUV\W\P]5I5^_`bD
..
 
 tzz$/?/F/F/H*8:L)M ! OC			S		1 HQyy|I&q))4Y?EEGH H Hs   /C

Cc           	         |j                  dd      }t        j                  t        dt        d|ddg| j                         dd       d	d l}t        j                  |      5 }|j                         |j                         |j                  |j                               }}}|j                         |z  }d d d        |j                         }	|	j                  d
       |	j                         |	j!                         |	j#                  d       |	j                        |	j%                         z   }
t        |d      j'                  t)        |
             t+        j,                  |       S # 1 sw Y   xY w)N.mp3z.wavz-mz-fz--sentence-silencez0.4T)inputcheckcapture_outputr   @      wb)replace
subprocessrunPIPERVOICEr   lameencwaveopengetframerategetnchannels
readframes
getnframesEncoderset_bit_rateset_in_sample_rateset_channelsset_qualityflushwritebytesosremove)textmp3_pathwav_pathr5   wratenchframesdurationencmp3s              r&   
tts_to_mp3rP   0   s%   /HNNE4h8LeTd4I	8	 *NN,ann.>Q\\^@\6c<<>T)* //
CR#0068H8H8Ms_`Oa
**V
syy{
*C4uSz*IIhO* *s   "AE..E7c                 &   g }t         t        j                  j                  t         d      fD ]4  }t        j                  j	                  |      s$t        j
                  |      D ]  }|j                  d      st        j                  j                  ||      }t        j                  j                  |t              }|j                  dd      }t        j                  j                  |      rt        j                  t        |            ni }|j                  t        j                  j                  |      ||t        j                  j!                  |      |f        7 |j#                  d       g }|dd D ]  \  }	}}}
}|j%                  d|j                  dd	      j                  d
d            }t&        j(                  j+                  t-        j.                  |	t0        j2                              }|j                  dt5        j6                  |       d|  d| d|
 dt5        j6                  |       d| dt5        j6                  |j%                  dd	             d        t&        j(                  j+                  t-        j8                  t0        j2                              }d|  d| dd	j                  |      z   dz   }t        t        j                  j                  t        d      d      j;                  |       t=        |      S )zFeed = every mp3 in episodes/ (machine) + episodes/human/ (community audio,
    included untouched). Newest first by file mtime.humanr)   .jsonT)reverseNd   title _ )tzz<item><title>z</title><enclosure url="/z
" length="z" type="audio/mpeg"/><guid>z</guid><pubDate>z</pubDate><description>descriptionz</description></item>u   <?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"><channel><title>Research Radar — personal feed</title><link>z</link><language>en-us</language><description>Auto-generated research briefings; human-recorded language audio included unmodified.</description><lastBuildDate>z</lastBuildDate>z</channel></rss>zpodcast.xmlrI   )EPISODESrD   pathjoinisdirlistdirendswithrelpathHEREr0   existsr   r!   r7   appendgetmtimegetsizesortgetemailutilsformat_datetimer   fromtimestampr   utchtmlescapenowrB   len)base_urlitemsrootfprelmeta_pmetaxmlmtimesizerV   pubrr   rsss                  r&   rebuild_rssr   @   s    E277<<':; 	Vww}}T"D! 	VAzz&!GGLLq)ggooa.67324''..2Htyyf.bbgg..q11c277??1;MtTU	V	V 
JJtJ
C%*4C[ c!q#tT!))FB"7"?"?S"IJkk))(*@*@8<<*XY

]4;;u#5"6 7''/j#l4& I KKN++;C5 A##';;txxb/I#J"KK`b 	cc ++
%
%hll8<<&@
AC :  !E!13 68WWS\B EWWC 	dM	*C066s;u:    c            	          t        j                         } | j                  dd       | j                  dd       | j                  dd       | j                         }t	        j
                  t        d       t        |j                        j                         }t        d	t        j                  
       t        |      }t        j                  dd|j                   j#                               j%                  d      }t'        j(                  t*        j,                        j/                  d      }t        j0                  j3                  t        | d| d      }t        dt        j                  
       t5        ||      }t7        j8                  |j                    d| |d d dt        |j;                  dd      d             t=        |j>                        }t        dt        j0                  jA                  |       d| d| d       y )Nz--sourceT)requiredz--titlez
--base-urlz(https://edgexpert-83b2.tail6663b0.ts.net)default)exist_okz+[podcast] writing script via local model...)filez
[^a-z0-9]+rX   z%Y%m%dr)   z[podcast] synthesizing...u    — r   )rV   r\   rS   rI   z[podcast] episode z (u   s) · feed has z items)!argparseArgumentParseradd_argument
parse_argsrD   makedirsr]   r7   sourcereadprintsysstderrr'   resubrV   lowerr"   r   rr   r   ro   strftimer^   r_   rP   r   dumpr0   r   rt   basename)	apargsr	   scriptslugstamprO   durns	            r&   mainr   c   s   		 	 	"BOOJO.OOIO-OOL*TOU==?DKK4(""$E	
7cjjIF66-djj&6&6&89??DDLL&//9E
'',,xE7!D6!6
7C	
%CJJ7
VS
!CII4::,eE73F4CLQ3;;vw/57DMM"A	rww//45RuOA3f
UVr   __main__)!__doc__rD   r   r   r   r6   r   r1   urllib.requestr   email.utilsrk   rp   r   r   r^   dirnameabspath__file__rd   r_   r]   r4   
expanduserr3   r   r   r   r'   rP   r   r   __name__ r   r&   <module>r      s    X W W W W '	wwrwwx0177<<j)
T8%?@
<=
5H  FW, zF r   