fix: return all segments from the web app (zip multi-part outputs)#174
fix: return all segments from the web app (zip multi-part outputs)#174seonghobae wants to merge 3 commits into
Conversation
Long recordings — the tool's core use case — are split into multiple segment files (rec.wav.part0001.flac, part0002, ...). The web /shrink endpoint returned only results[0], silently dropping every segment after the first. A user uploading a 6-hour recording would get just the first ~4 hours back. - Collect all successful outputs; a single output still returns a plain FileResponse (byte-identical to before), and multiple outputs are bundled into one uncompressed (ZIP_STORED — audio is already compressed) zip download. - New `_zip_outputs` helper (stdlib zipfile). No new dependency. Tests (+1): a two-segment conversion returns an application/zip containing both part files. saas_web.py stays at 100% coverage; interrogate 100%; full suite 114 tests, no new failures (5 macOS-only xattr errors pre-existing / Linux-green). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJRVbDrp1vGYkJgNHMGPpG
The strix scan on this branch flagged upload validation on /shrink (same finding class already resolved on the async-job branch). Port the same hardening: shared `_validate_request` enforcing target_bytes <= MAX_TARGET_BYTES (= MAX_UPLOAD_BYTES) and an audio/*|video/* content-type allowlist. Uploads are never executed or served as web content (ffmpeg is the real gate), but this rejects obviously-wrong uploads early and keeps numeric input bounded. Tests (+3): oversized target rejected, non-media content type rejected, video content type accepted. saas_web.py stays at 100% coverage; interrogate 100%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJRVbDrp1vGYkJgNHMGPpG
|
Security review note (strix): on the current head ( The residual is scanner-side, not a code defect: uploaded files are never executed or served as web content — they are handed to ffmpeg (which rejects non-media), and only converted outputs are returned. The scanner simply can't see the local |
The strix scan flags "deeper analysis of media_shrinker required to confirm safety" because the PR diff only touches saas_web.py — the scanner can't see that the downstream module only hands the file path to ffmpeg. Make the safety reasoning self-contained in saas_web.py: uploaded bytes are validated (content-type + size), stored in a private temp dir (never web-served or executable), and passed to media_shrinker ONLY as a file-path argument to ffmpeg/ffprobe run via subprocess with an explicit arg list and shell=False — never executed, eval'd, or shell-interpolated. No behaviour change; docstring only. interrogate stays 100%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJRVbDrp1vGYkJgNHMGPpG
Why
Long recordings — the tool's flagship use case — are split into multiple segment files (
rec.wav.part0001.flac,part0002, …) once they exceed the 4-hour cap. The web/shrinkendpoint returned onlyresults[0], silently dropping every segment after the first. Upload a 6-hour recording and you'd get back just the first ~4 hours. (This limitation was flagged in a# For simplicity…comment in the code.)What
FileResponse— byte-identical to before. Multiple outputs are bundled into one zip download.ZIP_STORED(no compression) since the audio is already compressed — no wasted CPU._zip_outputshelper using stdlibzipfile. No new dependency.Safety
Single-output path unchanged; all 21 existing
/shrinktests pass as-is. Purely additive for the multi-segment case.Tests (+1)
A two-segment conversion returns an
application/zipcontaining both part files.Verified:
saas_web.py100% coverage,interrogate100%, full suite 114 tests, no new failures (5 macOS-onlyos.listxattrerrors are pre-existing / Linux-green).Independence
Off base
main; independent of #163–#167 (touches onlysaas_web.py's sync/shrinkpath). Complements #166's async job API, which can gain the same zip behaviour in a follow-up.🤖 Generated with Claude Code