jamiepine/voicebox · error · HTTPException
str(e)
Error message
str(e)
What it means
Raised (HTTP 500) by GET /stories/{story_id}/export-audio as the catch-all for any non-HTTPException thrown during export (the handler re-raises genuine HTTPException, then wraps everything else). The detail is the raw str(e), so the client sees whatever exception message the audio-mixing stack produced. Common underlying causes live in stories.export_story_audio: librosa/soundfile decode failures, missing generation audio files on disk, numpy shape mismatches, or sample-rate assumptions (it defaults to 24000 Hz).
Source
Thrown at backend/routes/stories.py:237
audio_bytes = await stories.export_story_audio(story_id, db)
if not audio_bytes:
raise HTTPException(status_code=400, detail="Story has no audio items")
safe_name = "".join(c for c in story.name if c.isalnum() or c in (" ", "-", "_")).strip()
if not safe_name:
safe_name = "story"
filename = f"{safe_name}.wav"
return StreamingResponse(
io.BytesIO(audio_bytes),
media_type="audio/wav",
headers={"Content-Disposition": safe_content_disposition("attachment", filename)},
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
View on GitHub (pinned to 51f49dea19)
Solutions
- Check the server logs for the wrapped exception's traceback — the real cause is there, not in the 500 detail.
- Verify every item's generation has an on-disk audio file before exporting.
- Replace detail=str(e) with a generic message and log the exception server-side to avoid leaking internals.
- If sample-rate mismatches are the cause, ensure all generations are produced at the same rate (the service hardcodes 24000).
Example fix
# before
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# after
except Exception:
logger.exception("export_story_audio failed for story %s", story_id)
raise HTTPException(status_code=500, detail="Audio export failed; see server logs") Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: ensure every item has a resolvable audio asset before export.
async function preflightExport(api, storyId) {
const story = await api.getStory(storyId).then(r => r.json());
const missing = (story.items ?? []).filter(i => !i.audio_path);
if (missing.length) throw new Error(`${missing.length} item(s) missing audio`);
return story;
} Type guard
function itemsHaveAudio(story) {
return (story.items ?? []).every(i => typeof i.audio_path === 'string' && i.generation_id);
} Try / catch
try { await api.exportStoryAudio(storyId); }
catch (e) {
if (e.status === 500) {
notify('Export failed; the server log has details. Check for missing audio files.');
// do NOT blind-retry; surface to the user
} else throw e;
} Prevention
- Don't retry a 500 blindly — read the server traceback first.
- Keep generation audio files co-located with the data dir; never delete them while rows reference them.
- Avoid detail=str(e) server-side; log internally and return a generic message.
When it happens
Trigger: A story item's generation audio file is missing from disk (deleted/moved data dir), an audio decode error in the mixing loop, or a malformed/zero-length audio array causing the concatenation/mixing math to throw.
Common situations: Data directory moved or partially restored from backup; a generation row points at a path that no longer exists; mismatched sample rates between generations breaking the mix; disk full during temp WAV write.
Related errors
- str(e)
- Story item not found or invalid split point
- Story item or version not found
- Story has no audio items
- Failed to clear cache: {str(e)}
AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12).
Data as JSON: /api/errors/4445a7f39c57db01.
Report an issue: GitHub.