Skip to content

Video clip support#4

Merged
Mr-Quin merged 10 commits into
mainfrom
feat/video-clips
Jun 12, 2026
Merged

Video clip support#4
Mr-Quin merged 10 commits into
mainfrom
feat/video-clips

Conversation

@Mr-Quin

@Mr-Quin Mr-Quin commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Adds video clips (incl. animated GIF/WebP) as a second media type to Badgey, plus the editor/UX work and fixes that came out of iterating on it.

Core feature

  • Auto-detect image vs. clip from the dropped / picked / pasted file (animated GIF/WebP are clips; static ones stay images). Paste prefers an animated representation over a flattened PNG when both are on the clipboard.
  • Clip editor: filmstrip trim timeline (drag in/out, slide-the-window grip, scrub tab — handles sit outside the window so they can't cross on a tiny selection), frame-rate and speed sliders that snap to preset stops, loop, autoplay, and "grab a frame as a still".
  • Frame-budget meter blocks Send when the clip would exceed free space.
  • Speed affects the upload: same sampled frames, the AVI declares a scaled playback rate.
  • Live preview is a canvas painted through the same editor transform as the encoder (WYSIWYG), and simulates the chosen frame rate (low fps visibly stutters like the badge).
  • Touch: pinch-to-zoom and twist-to-rotate (plus two-finger pan) on the stage, alongside drag / wheel / keyboard.

Pipeline (browser-native, no ffmpeg.wasm)

New src/lib/video/ (zero UI imports): probe · frames (<video> seek + ImageDecoder for GIF/WebP) · clip (trim/budget/playback math) · avi (MJPEG-AVI muxer) · encode (orchestrator, the seam to add ffmpeg.wasm later). The badge receives the clip exactly like an image, with an .avi display name. Covers MP4/MOV/WebM/GIF/WebP; MKV/AVI/exotic codecs are out of native reach. The interactive stage was extracted into Preview.svelte, leaving Composer.svelte as layout.

Notable fixes

  • AVI container: each RIFF LIST size must include its 4-byte type — ours were 4 bytes short, which desktop ffmpeg tolerates but the badge's strict parser faults on (the "AVI crashes the badge" bug). Also set dwSuggestedBufferSize.
  • History storage: video originals are huge, so they live in a write-once blobs object store (loaded on demand); per-edit metadata autosaves stay small, and prune walks the index keys-only instead of reading every stored blob.
  • Recent + badge lists: render a saved snapshot for video items (not the unplayable video blob) with a Clip chip; Recent labels rows by relative time.
  • Snapshot on the upload-success screen; Done keeps the edit loaded; em-dash removed from the disconnect copy.

Verification

  • Unit (vitest): AVI muxer (incl. strict RIFF size-walk + buffer hint, real ffprobe/ffmpeg decode checked during dev), clip/budget/playbackFps math, animated detection, history round-trip + keys-only prune.
  • e2e (Playwright + simulator): clip upload → .avi on the badge, preview paints, autoplay advances, success/recent snapshots, animated GIF + pasted WebP detection, and a two-finger pinch resize.
  • bun run check 0/0, build clean. A review round (multi-angle) was run and its findings applied.

Design/spec/process docs are intentionally kept out of this repo (engineering notes live in ClickUp).

🤖 Generated with Claude Code

Mr-Quin and others added 4 commits June 11, 2026 00:16
Add browser-native video/gif/webp clip support: probe + frame extraction
(<video> seek / ImageDecoder), an MJPEG-AVI muxer, trim/budget math, and an
encode orchestrator in src/lib/video/ (zero UI imports). Extend EditorSession
with media/clip state, playback, size estimate and send; share the editor
transform for per-frame rendering; carry clip params in history; thread an
upload extension through the badge client. Unit tests for the muxer (incl.
strict RIFF size-walk + buffer-size hint), clip math, detection, and session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Filmstrip trim timeline with drag handles + scrub tab, playback bar, fps
chips, frame-budget meter, and a media-type pill. The round preview becomes a
canvas painted from the source video or decoded gif/webp frames through the
editor transform. Auto-detect on pick/drop/paste; paste prefers an animated
representation over a static one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drive the clip flow against FakeBadge: upload a clip and assert an .avi file
appears, the preview canvas paints real pixels, playback advances, an animated
gif auto-detects as a clip, and a pasted animated webp does too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Mr-Quin Mr-Quin force-pushed the feat/video-clips branch from 46e6d05 to 369498c Compare June 11, 2026 07:17
Mr-Quin and others added 6 commits June 11, 2026 00:44
- Extract the interactive editor stage into Preview.svelte (canvas/video/gif
  rendering + rAF loop + input handling), leaving Composer as layout.
- Add multi-touch pinch-to-zoom and twist-to-rotate (plus two-finger pan)
  alongside the existing drag / wheel / keyboard controls; e2e covers a pinch.
- Reduce editor card padding and let the controls span the full card width.
- Keep the timeline trim handles inside the strip so they no longer clip off
  the edge at the start and end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Clips autoplay when loaded.
- Frame rate and speed are now continuous sliders that snap to their preset
  stops (5/10/15/24 fps; 0.5/1/2x), with ticks positioned at each stop.
- Speed now changes the uploaded clip's playback rate on the badge (same
  sampled frames, AVI declares a scaled fps via playbackFps) - not just preview.
- Keep the first encoded frame as a snapshot: shown on the upload-success
  screen, and stored on history items so the Recent list renders a still (with
  a video badge) instead of the video blob, which broke for some formats.
  Video drafts get a first-frame snapshot too.
- Move Fit inline into the Size row; put a small Reset next to Send to badge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ine + flow fixes

- Show video snapshots (not the broken video blob) for clips on the badge file
  list, with a Clip chip; same snapshot reuse as the recent list.
- Recent list: label rows by relative time (most have no filename) and mark
  videos with a Clip chip instead of the clipped corner play badge.
- After Send, the Done button keeps the edit loaded instead of clearing it.
- Preview simulates the chosen frame rate: repaint only on frame steps so a low
  fps visibly stutters like the badge (always live while editing).
- Timeline: in/out handles now sit just outside the window edges with side
  gutters, so they can't cross each other on a tiny window and never clip at the
  ends.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Draft autosave was re-serializing the full original to IndexedDB on every edit,
and prune() read every stored blob into memory just to drop the oldest. Now:
video originals live in a write-once 'blobs' object store (loaded on demand via
getBlob), metadata autosaves stay small, and prune walks the updatedAt index
keys-only. Images keep their (small) blob inline. Restore loads the original
from the blob store when needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Correctness:
- frames: frameAt() resolved immediately when no seek is needed (assigning
  currentTime its current value fires no 'seeked' event) - fixes a silent hang +
  leaked <video> when grabbing a frame at time 0.
- editor-session: guard the debounced size estimate with a generation counter so
  a slow stale estimate can't overwrite a newer one; restore() only marks the
  blob dirty when the original came inline (not from the blob store).

Cleanup:
- reuse isAnimatedImageType in openFrameSource and Preview (was duplicated 3x).
- export OUT from editor.ts and delete the duplicate video/dims.ts.
- probeAnimatedImage counts frames without allocating every bitmap.
- gate the playback playhead sync to frame steps, not every rAF.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Mr-Quin Mr-Quin merged commit cc759ae into main Jun 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant