feat(attachments): play every browser-playable video container inline + label sniffed types - #319
Conversation
… + label sniffed types Widen the magic-byte inline allowlist from MP4/WebM to every video container mainstream browsers can natively play: QuickTime (.mov — including screen recordings misnamed .mp4), M4V, Ogg, 3GPP, 3GPP2, and Matroska. The decision still comes only from server-side byte sniffing, never the filename extension, and SVG/HTML/active formats remain opaque octet-stream downloads. When a sniffed container stays a generic download (AVI, WMV, MPEG-TS…), preserve the sniffed MIME as a new optional crystal field detectedContentType so the download row can say 'AVI video' instead of application/octet-stream. The canonical crystal union accepts the field only alongside an octet-stream contentType, keeping it display metadata that can never widen inline serving; client normalization mirrors that. Since codec support inside an allowed container varies per browser (HEVC QuickTime on Firefox, MKV on Safari), a failed <video> element now degrades to its named download row instead of an inert black player. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🧹 Develop S3 preview removedThe PR-specific alias and every workflow-created develop deployment were removed when this PR closed. The ordinary generated Vercel Preview remains available on the shared development runtime. |
|
🤝 Merged Conflicted files:
Please review the merge commit before relying on it. |
…d conflicts) Conflicted paths: remix/app/docs/apiDocs.ts Resolved by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/32210305983 Co-Authored-By: Claude <noreply@anthropic.com>
Preview verification notesReporter's actual file, exact server decision path (real bytes, real Before this PR the same bytes collapsed to Preview deployment E2E (
Remaining manual check for an approved account (30 seconds): upload a macOS screen recording (or this PR's reporter file) to a post — it should finalize as 🤖 Generated with Claude Code |
Why
A QuickTime-container screen recording named
.mp4(magic bytesftypqt) uploaded to a post rendered as a generic41.7 MiB · application/octet-streamfile card instead of a video player, because the inline allowlist only admitted magic-byte-verifiedvideo/mp4+video/webmand everything else collapsed to an unlabeled octet-stream download.What
1. Every browser-playable video container now renders inline.
ATTACHMENT_INLINE_CONTENT_TYPES(server) and the mirroredINLINE_VIDEO_TYPES(client) grow from MP4/WebM to: QuickTime (video/quicktime), M4V (video/x-m4v), Ogg (video/ogg), 3GPP/3GPP2, and Matroska (video/x-matroska). The decision still comes exclusively from server-side magic-byte sniffing at upload finalization (file-typeover the first 8 KiB) — never the filename extension — and SVG/HTML/active formats remain opaque downloads.SAFE_VIDEO_CONTENT_TYPESinattachmentCorematches so canonical crystal re-derivation agrees.2. Opaque downloads now name their real container. New optional crystal field
detectedContentTypepreserves the sniffed MIME whenever the servedcontentTypestaysapplication/octet-stream(AVI, WMV, MPEG-TS, HEIC, PDF, ZIP…). The canonical crystal closed union accepts the field only alongside an octet-streamcontentType, so it is display metadata that can never widen inline serving or accompany an inline type. It flows throughattachmentPublicProjection→ post/comment projections → the client normalizer, and the download row shows a friendly label (AVI video,PDF document, raw MIME as fallback) instead ofapplication/octet-stream. Legacy 4-key crystals stay canonical.3. Graceful codec fallback. Container support ≠ codec support (HEVC QuickTime on Firefox, MKV on Safari).
PostAttachmentsnow degrades a failed<video>element to its named download row via the elementerrorevent instead of leaving an inert black player.Registration / docs
apiDocs.ts: upload-complete + content endpoint details and steps updated (docs registry is the route registration; no new endpoints).schemas/registry.ts: attachment schema documents the optionaldetectedContentTypefield; the pinned projection inbuiltinSchemaProjection.test.tsupdated per its review-prompt comment.TESTING.md: three new manual checklist rows (QuickTime/MKV inline, sniffed-container labels, codec-failure fallback).Tests & verification
npm run test:unit— green (exit 0), including 110 attachment tests with new cases: every new container finalizes inline; AVI finalizes octet +detectedContentType; canonical-crystal accepts the 5-key shape and rejects detected-on-inline, detected=octet, uppercase, and non-string forgeries; UI mediaKind/normalizer/label coverage.npm run buildinremix/: Vite client + Nitro Vercel output + verify) — passes.ftypqtQuickTime bytes, 41.7 MB) was verified locally as the exact trigger:file-typereportsvideo/quicktime, previously excluded by the allowlist.Out of scope (follow-up)
Existing ready attachments keep their stored octet-stream crystal (finalized before this change); a backfill that re-sniffs stored objects and upgrades crystals (e.g., the reporter's original post) needs S3 access inside the admin-migration frame and is left as a follow-up. Re-uploading the file after deploy gets the new behavior. Inline audio remains deliberately disabled pending a vetted player.
🤖 Generated with Claude Code