Deterministic video export: frame-accurate Source Video decode (source-media issue 07) - #18
Merged
Merged
Conversation
…e-media issue 07) A bound decodable Source Video now survives into the exported MP4 frame-accurately: the export loop decodes it with mediabunny's read half (Input -> VideoSampleSink.samplesAtTimestamps over t = frame / fps) instead of sampling the wall-clock preview <video>. VideoFrameTexture is the zero-blit path for in-clamp unrotated sources; oversized/rotated sources go through CanvasSink at the clamped size. First/last-frame hold, wrap matching the timeline-locked preview, verifyKeyPackets, and eager sample/frame close() are all in place; the decode streams (never buffers the file). The Exporter enforces core's availability/duration policy, and mid-export corruption aborts with its own error toast and no partial file.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2ae05ed. Configure here.
An availability refusal (a bound preview-only video) now throws with its own shared Error.name (VIDEO_EXPORT_UNAVAILABLE_ERROR_NAME) so the UI explains the codec/availability cause instead of blaming the browser's encoder. The first progress write moves ahead of the decode prelude (blob fetch + stream open) so the overlay's Cancel control exists from the job's first visible moment; the fetch rides the capture's AbortSignal, and a cancel landing while the stream opens is honored before any capture work.
4 tasks
…#19) ## Summary - The export panel now obeys `core`'s availability verdict (`resolveVideoExportAvailability`) instead of its UI-local "requires an Animation" gate: video export is offered when the document has an Animation OR the bound Source Media is a decodable Source Video, and disabled otherwise. - With no Animation, the panel's Duration row (and the export itself, enforced by the Exporter since #18) uses `resolveVideoExportDuration`: the video's own duration capped at 30 s, playing from its start. - A bound playable-but-undecodable Source Video pins a persistent "codec is not supported in this browser" notice on the export panel (not a transient toast — the condition holds while the file is bound); import and preview stay unaffected. - The `sourceMediaFacts` store→core projection the Exporter kept private moves to `state/store.ts` so the panel and the Exporter share one seam (red→green in `state/source-media.test.ts`). - CONTEXT.md's Exporter entry already reflects the availability rule (landed with the issue 06 policy module); verified, no remaining "requires an Animation" text. Stacked on #18 (deterministic video export) — merge that first. ## Test plan - [x] Full Vitest suite (477), typecheck, lint, build green - [x] Browser evidence (`.scratch/source-media/evidence/08/`, system Chrome): bare 8 s frame-counter clip with no Animation exports an 8.000 s MP4, no audio, frames 0/1/60/150/238/239 exactly matching source frames (plays from start) - [x] 45 s record clamps the panel to "30.0 s · 900 frames" - [x] No media + no Animation renders disabled with the hint; `canDecode: false` renders the persistent codec notice with export blocked and the preview still advancing <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes user-visible export eligibility and messaging for video; logic is delegated to tested core policy but mis-synced facts would block or allow exports incorrectly. > > **Overview** > The export panel stops gating video on a local “has Animation” check and instead uses **`core`** via `resolveVideoExportAvailability` and `resolveVideoExportDuration`, with bound media projected through shared **`sourceMediaFacts`** in `state/store.ts` (moved out of `Exporter.tsx` so the panel and exporter stay aligned). > > Video export is enabled when the document has an Animation **or** a decodable bound Source Video; duration and frame counts follow `resolveVideoExportDuration` (including source-only clips capped at 30s). When export is blocked, **`VideoUnavailableNotice`** shows either a hint to import video or add motion (`nothing-to-export`) or a **persistent** amber codec warning for preview-only undecodable video (`undecodable-video`), while import/preview stay available. > > Unit tests cover the `sourceMediaFacts` projection (null, image, video, `canDecode: false`). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 113d25c. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Input→VideoSampleSink.samplesAtTimestampsovert = frame / fps) instead of sampling the wall-clock preview<video>. No wall-clock playback during export; audio is stripped.core/media-timeseamvideoSampleTimeAtPlayhead(preview-matching wrap + first-frame hold) drives the sample timestamps; the Exporter now enforces core's video-export availability/duration policy, so the Animation duration truncates longer clips.VideoFrameTexture(zero-blit) for in-clamp unrotated sources,CanvasSinkat the clamped size for oversized/rotated ones. The export texture is published through an ephemeral override seam (useSyncExternalStore) so the Screen swaps to it only for the capture, then returns to the preview.SourceVideoDecodeErrortoast ("the video could not be decoded") and no partial file. Image-only export paths are unchanged.Test plan
videoSampleTimeAtPlayheadcovered TDD-style insrc/core/media-time.test.ts(hold, wrap, negative first timestamp, non-finite inputs) — 473 tests greenNote
Medium Risk
Changes the video export capture path and GL texture sourcing; failures are gated and named, but regressions could affect export determinism, preview during export, or duration/availability behavior.
Overview
Video export no longer samples the preview
<video>during encode. When a decodable Source Video is bound, the capture loop opens an export-only mediabunny decode stream (samplesAtTimestampsatframe / fps), stages each frame on a dedicated texture before render, and tears the stream down after restore.videoSampleTimeAtPlayheadincoredrives those sample times (preview-matching wrap plus first-frame hold when the track’s first packet is after 0).videoTimeAtPlayheadnow passes in-range playheads through without extra modulo so export timestamps stay exact.The Screen reads the decode texture via a short-lived
setExportSourceMediaoverride (useSyncExternalStore); timeline-locked preview skips playhead seeks while export runs. The Exporter enforcesresolveVideoExportAvailability/resolveVideoExportDuration(withsourceMediaFactsfrom the store) and throws named errors the UI maps to specific notices. Export panel video availability and duration follow the same core rules (decodable video without Animation, codec notice for preview-only).Reviewed by Cursor Bugbot for commit becd893. Bugbot is set up for automated code reviews on this repo. Configure here.