Creator workflow suite: greenscreen facecam pipeline, scene styles, and recording reliability fixes#676
Conversation
On macOS, when recording mic-only (no system audio), the native helper writes mic audio both into the video's inline track and a .mic sidecar. The preview was playing both sources simultaneously, causing an audible echo. Mute the embedded preview whenever sidecar tracks exist but the video path was not listed as a distinct audio source (hasEmbeddedSourceAudio === false). Export behavior (includeEmbeddedInExport) is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… from persisted snapshots Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… reason Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ording, Alt+/ hotkey, teleprompter camera-full highlight - persistWebcamLayoutEvents no longer clears the session on first write: finalize runs for both the screen and webcam output files, and the events were landing only next to the webcam video where the editor never looks - the floating webcam self-view now hides while recording/finalizing: the HUD window shrinks to a 160px strip then, which clipped the preview to a sliver - camera layout hotkey moved from Alt+F10 to Alt+/ and shown in the tooltip - the teleprompter shows a blue border while camera-full mode is active Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…state Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- DEFAULT_SHORTCUTS.splitClip changed from { key: "c" } to { key: "b" }
- Updated TimelineCanvas hint text from "Press C to split clip" to "Press B to split clip" (literal; keyShortcuts config does not flow to TimelineCanvas as a prop, so dynamic rendering was disproportionate)
- Updated toolbar.splitClip tooltip label from "(C)" to "(B)" across all 10 i18n locales
- Added src/lib/shortcuts.test.ts with TDD-first test; saved user configs override the default
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Added magnetEnabled: boolean to ProjectEditorState with normalizeProjectEditor default true - Added TDD tests in projectPersistence.webcamLayout.test.ts (fail-first: 4 tests failing before implementation) - VideoEditor: useState + setMagnetEnabled in project open path (next to webcamLayout setters) + buildPersistedEditorState type + currentPersistedEditorState memo + deps - Toolbar: Magnet icon (from @phosphor-icons/react, same library as Scissors) placed next to split button; text-blue-400 when ON, text-muted-foreground when OFF - Added editor.toolbar.magnetOn / magnetOff i18n keys across all 10 locales Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the magnet is off, playback entering a trim region no longer seeks past it: a wall-clock driver pauses the media element and advances the playhead in real time while a black overlay covers the preview frame. requestVideoFrameCallback does not fire on a paused element (and the normal scheduler early-returns when paused), so the driver runs its own requestAnimationFrame loop. User pause freezes the wall clock retaining progress; user seeks cancel the driver (restarting it when scrubbing into a gap while playing). togglePlayPause/handleSeek now treat the editor's isPlaying state as authoritative because the gap driver keeps the element paused while logically playing. Magnet-on behavior is unchanged (guarded trim skip). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When magnetEnabled, VideoEditor maps all data passed to TimelineEditor into display space (inter-clip gaps collapsed, clips packed from 0) and reverse-maps every callback before the existing handlers run. The timeline package itself is untouched; magnet-off passes the original references through. Playhead findings: the call site already passed playheadTime = mapSourceTimeToTimelineTime(currentTime) (timeline space), and clip / zoom / audio spans are timeline space too, so the existing contract was internally consistent. Annotation and camera regions, however, are SOURCE space (preview filters them against the video element's currentTime) while being drawn on the timeline ruler at raw startMs — an existing inconsistency that only diverges when speed clips exist. It is preserved: their display mapping goes through source-space conversion (regionToDisplay/spanToSource) so preview semantics are unchanged, while zoom/audio use the timeline-space variants. Clip edge/body edits cannot be expressed in the collapsed display, so clip span changes are intercepted with a toast (i18n key editor.timeline.magnetClipEdit in all 10 locales); selection and delete still work, and deletes ripple naturally because the display recomputes from the remaining clips. Known limitation: timelineModel derives the waveform sourceSpan from clip.startMs internally and ignores extra fields, so source-audio waveform segments for clips after a collapsed gap sample a shifted source offset while the magnet is on (display-only artifact). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… magnet toggle Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…and click-emitted clip span changes - Timeline: render the camera row above the zoom row (clip > source-audio > camera > zoom > annotations > audio); row ids are stable so nothing index-based changes. - Camera-full fill: the crop panel always stores pixel-square bubble crops, so cover-fitting them into a 16:9 frame discarded ~44% of the crop height and over-zoomed the face. Add expandRectToAspect and expand the crop to the output frame aspect (centered, clamped) in both the preview fill branch and the exporter frame cache so fill barely crops and preview matches export. - Settings panel: hide bubble-only webcam controls (size, position grid, custom position sliders, margin) while the playhead is inside a camera-full segment, and also hide roundness/shadow when the fullscreen style is fill; show a localized hint in their place (key added to all 10 locales). - Timeline dnd: selection clicks ran full drag/resize cycles (no sensor activation constraint) and committed unchanged spans, which fired the magnet-on "Turn the magnet off to adjust clip edges" toast when merely selecting a clip before deleting it. Skip zero-delta gestures and gestures that resolve back to the item's current placement (isNoOpSpanCommit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Camera-full "Fill screen" segments exported as a shrunken, pillarboxed
camera square over the wallpaper instead of covering the frame, even
though the preview rendered fill correctly.
Root cause: the fill style expands the pixel-square bubble crop cache
to the output frame aspect mid-export, recreating the webcam frame
cache canvas at new dimensions. ensureWebcamSprite swapped the new
canvas into the existing texture via webcamTextureSource.resource +
update(). Pixi resizes the Texture, but sprites only forward texture
"update" events for dynamic textures (pixi.js Sprite "set texture":
value.on("update", ...) is gated on value.dynamic, and Texture.from()
textures are not dynamic), so the sprite's batched quad kept the stale
square dimensions while applyCoverLayoutToSprite computed the cover
scale for the expanded ones — e.g. a 720x720 quad scaled by 1.125
renders an 810x810 centered square in a 1440x810 export.
Track the source dimensions behind the webcam texture and replace the
sprite texture (replaceSpriteTexture) whenever they change, so the
sprite re-reads its dimensions through the texture setter. Same-size
refreshes keep the cheap in-place resource swap.
Verified end-to-end with a smoke export of a square-crop fill project:
the camera now covers the frame edge to edge during camera-full fill
segments, and bubble segments before/after the segment still render
correctly. Adds a renderer test covering the decoded-source bubble->
fill transition and an exporter test asserting webcamLayoutStyle /
webcamLayoutRegions forward into the frame renderer construction.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Facecam chroma key with eyedropper + auto key-color detection, edge softness, spill suppression, background image compositing (preview + both exporters, pixel-identical) - Garbage matte: box + bezier pen mask with feather and fullscreen editor - Facecam color controls (brightness/contrast/highlights/shadows) - Scene styles: fill-frame (fullscreen) segments with animated nested-rect transitions, Alt+./Alt+, hotkeys in editor and during recording, header toggle buttons, recording sidecar import, zoom suppression inside fullscreen segments, 'Remove background' now rides the same system - Shared webcam session manager: fixes multi-second Continuity Camera frame gaps from device renegotiation; self-heals dead streams; watchdog - Facecam FPS preference (24/30/60, default 30); HUD visible over fullscreen apps; auto-crop of notch black bars on fresh recordings - Instant preview updates (no more nudging the crop to refresh) - Timeline: condensed rows, marquee multi-select with batch delete, camera double-click affordance removed - Teleprompter auto-closes when recording stops Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedToo many files! This PR contains 160 files, which is 10 over the limit of 150. To get a review, narrow the scope: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (160)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
Extends the webcam Color group with white-balance temperature (warm/cool) and saturation sliders. Same pattern as the existing controls: pure math in chromaKeyMath.ts mirrored exactly by the WebGL shader, exact identity at neutral, persisted with the project, applied to the keyed foreground in preview and both exporters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The processing pipeline shipped with temperature/saturation support but the two sliders were missing from the panel's control list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
A pixel matching either key color is removed (minimum alpha wins), so one sample can cover the bright center and another the shadowed edges. New '+' swatch next to the key color arms the eyedropper for the second slot; ×2 clears it. Same reference-math + shader-mirror pattern as the single key, with unit tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Picks a color the key must leave alone (skin tones or clothing catching green spill). Protection wins over both key colors (max alpha), blends smoothly at boundaries, and protected pixels skip spill suppression so their color stays true. New '−' swatch in the key colors row; ×− clears. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Summary
A creator-workflow feature suite built and battle-tested while producing real facecam-over-screen content (courses, presentations, demos) with a green screen and an iPhone Continuity Camera. It contains two halves:
Everything renders identically in the editor preview and both exporters, persists with projects (older project files open unchanged), and degrades gracefully (no WebGL → raw webcam; missing assets → sensible fallbacks).
I'm happy to split this into smaller PRs per feature if that's easier to review — say the word and I'll carve it up.
Bug fixes (likely the easiest wins)
Webcam frame gaps from camera device renegotiation
Recordings could contain multi-second frozen-facecam gaps (we measured 24s missing from a 72s clip via ffprobe). Root cause: the floating preview and the recorder each held their own
getUserMediawith different constraints; any consumer churn (HUD compact transition, popover open/close) made Chromium restart the physical camera, and slow-renegotiation devices (iPhone Continuity Camera) stall 3–12s while the recorder's track receives no frames.Fix: a refcounted shared webcam session (
src/lib/webcamSession.ts) — onegetUserMediaper device; the preview attaches to it and the recorder records a cloned track, so the device never restarts mid-session. Also: dead-stream self-healing (ended tracks are never reused — Continuity Camera disconnects recover on reselect) and a[webcam-watchdog]that logs track mute/unmute with timestamps so future stalls leave evidence. Verified: post-fix recordings show 0 frame gaps.HUD invisible over fullscreen apps (macOS)
The HUD overlay never opted into fullscreen Spaces, so it vanished when presenting. Fixed with
setVisibleOnAllWorkspaces(visibleOnFullScreen)+ screen-saver window level, matching the update-toast window's existing pattern.Notch black-bar auto-crop
Fullscreen apps on notched MacBooks render the menu-bar band pure black, which screen captures faithfully record. Fresh recordings are now sampled (3 frames, conservative thresholds — dark themes/black slides are never falsely detected; unit-tested) and the bars cropped automatically.
Creator features
Facecam greenscreen pipeline (WebGL2)
Chroma key with eyedropper + auto key-color detection (samples the frame border for the dominant screen color — real screens shift far from pure green under lighting), edge softness, fixed spill suppression, and background-image compositing. Garbage matte with box or bezier pen mask (insert-on-path, smooth/corner toggling, mirrored handles, fullscreen editor dialog) and feather. Facecam color controls (brightness/contrast/highlights/shadows) applied to the keyed foreground. The per-pixel math lives in a pure, unit-tested module (
chromaKeyMath.ts) that the shader mirrors exactly.Scene styles: per-segment fill-frame ("Fullscreen") regions
Time ranges where the recording covers the full canvas edge-to-edge (presentation mode) vs the framed look (demo mode), with an animated transition between the two layouts using the existing easing. Controlled by a timeline track, ⌥+. / ⌥+, editor shortcuts, header toggle buttons — and the same hotkeys during recording, persisted as a sidecar event file and imported as pre-split segments on project open (mirrors the webcam-layout-events pattern). Zooms are automatically suppressed inside fullscreen segments. "Remove background" now rides this system (cover-crop at the project aspect) instead of switching to native aspect.
Editor ergonomics
Facecam FPS preference (24/30/60), instant preview updates for webcam setting changes, marquee multi-select with batch delete on the timeline, condensed track rows, teleprompter auto-close when recording stops, background image no longer mirrored by "Mirror webcam".
Testing
tsc --noEmitand biome clean on all touched files🤖 Generated with Claude Code