fix: wrap Gemini TTS output in WAV header + add tone guidelines (#403)

- Gemini TTS returns raw PCM (audio/L16;rate=24000), not WAV
- Add pcm_to_wav() to wrap audio in proper RIFF/WAVE header
- Add tone guidelines for podcast scripts: accessible, user-focused,
intuitive analogies, matter-of-fact delivery

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>

authored by zzstoatzz.io Claude and committed by GitHub 40d2fc71 5a5ae63e

Changed files
+29 -5
.github
scripts
+9 -2
.github/workflows/status-maintenance.yml
··· 90 91 if skip_audio is false: 92 1. write a 2-3 minute podcast script to podcast_script.txt 93 - - two hosts having a casual technical conversation 94 - focus on shipped features from the top of STATUS.md 95 - format: "Host: ..." and "Cohost: ..." lines 96 97 2. run: uv run scripts/generate_tts.py podcast_script.txt update.wav 98 99 - 3. run: uv run --with plyrfm -- plyrfm upload update.wav "plyr.fm update - <today's date>" 100 101 ## task 3: open PR with changes 102
··· 90 91 if skip_audio is false: 92 1. write a 2-3 minute podcast script to podcast_script.txt 93 + - two hosts having a casual conversation 94 - focus on shipped features from the top of STATUS.md 95 - format: "Host: ..." and "Cohost: ..." lines 96 97 + tone guidelines: 98 + - accessible to non-technical listeners, but don't dumb it down 99 + - focus on user impact: what can people do now that they couldn't before? 100 + - use intuitive analogies to explain technical concepts in terms of everyday experience 101 + - matter-of-fact delivery, not hype-y or marketing-speak 102 + - brief, conversational - like two friends catching up on what shipped 103 + 104 2. run: uv run scripts/generate_tts.py podcast_script.txt update.wav 105 106 + 3. run: uv run --with plyrfm -- plyrfm upload update.wav "plyr.fm update - <today's date>" --album "$(date +%Y)" 107 108 ## task 3: open PR with changes 109
+20 -3
scripts/generate_tts.py
··· 11 # dependencies = ["google-genai"] 12 # /// 13 14 import os 15 import sys 16 from pathlib import Path 17 18 from google import genai 19 from google.genai import types 20 21 22 def main() -> None: ··· 70 ), 71 ) 72 73 - audio_data = response.candidates[0].content.parts[0].inline_data.data 74 - output_path.write_bytes(audio_data) 75 - print(f"saved audio to {output_path} ({len(audio_data)} bytes)") 76 77 78 if __name__ == "__main__":
··· 11 # dependencies = ["google-genai"] 12 # /// 13 14 + import io 15 import os 16 import sys 17 + import wave 18 from pathlib import Path 19 20 from google import genai 21 from google.genai import types 22 + 23 + 24 + def pcm_to_wav( 25 + pcm_data: bytes, sample_rate: int = 24000, channels: int = 1, sample_width: int = 2 26 + ) -> bytes: 27 + """wrap raw PCM data in a WAV header.""" 28 + buffer = io.BytesIO() 29 + with wave.open(buffer, "wb") as wav: 30 + wav.setnchannels(channels) 31 + wav.setsampwidth(sample_width) 32 + wav.setframerate(sample_rate) 33 + wav.writeframes(pcm_data) 34 + return buffer.getvalue() 35 36 37 def main() -> None: ··· 85 ), 86 ) 87 88 + # gemini returns raw PCM (audio/L16;codec=pcm;rate=24000), wrap in WAV header 89 + pcm_data = response.candidates[0].content.parts[0].inline_data.data 90 + wav_data = pcm_to_wav(pcm_data) 91 + output_path.write_bytes(wav_data) 92 + print(f"saved audio to {output_path} ({len(wav_data)} bytes)") 93 94 95 if __name__ == "__main__":