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 90 91 91 if skip_audio is false: 92 92 1. write a 2-3 minute podcast script to podcast_script.txt 93 - - two hosts having a casual technical conversation 93 + - two hosts having a casual conversation 94 94 - focus on shipped features from the top of STATUS.md 95 95 - format: "Host: ..." and "Cohost: ..." lines 96 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 + 97 104 2. run: uv run scripts/generate_tts.py podcast_script.txt update.wav 98 105 99 - 3. run: uv run --with plyrfm -- plyrfm upload update.wav "plyr.fm update - <today's date>" 106 + 3. run: uv run --with plyrfm -- plyrfm upload update.wav "plyr.fm update - <today's date>" --album "$(date +%Y)" 100 107 101 108 ## task 3: open PR with changes 102 109
+20 -3
scripts/generate_tts.py
··· 11 11 # dependencies = ["google-genai"] 12 12 # /// 13 13 14 + import io 14 15 import os 15 16 import sys 17 + import wave 16 18 from pathlib import Path 17 19 18 20 from google import genai 19 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() 20 35 21 36 22 37 def main() -> None: ··· 70 85 ), 71 86 ) 72 87 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)") 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)") 76 93 77 94 78 95 if __name__ == "__main__":