Completes the pro-output story: an Export-format selector (WAV / MP3) on the
Stories toolbar. WAV stays fully client-side; MP3 routes the client-stitched
WAV through a new backend POST /stories/encode (ffmpeg via the Windows-reload-
safe spawn_subprocess, #175), with a strict format whitelist (mp3/m4b/ogg) and
bitrate validation so the uploaded format can't inject ffmpeg args. Both the
audiobook and per-character stems honor the selected format; MP3 falls back to
WAV with a toast if ffmpeg is unavailable.
- backend/api/routers/stories.py + main.py registration; temp-file cleanup.
- frontend/src/api/stories.ts encodeAudio() (apiFetch: same-origin + PIN).
- tests: format-whitelist 400, ffmpeg-missing 501, real mp3 encode (skips if
ffmpeg absent). Backend 26 incl. router smoke; frontend 152/152, typecheck/
build/CJK ✓.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>