0.6.0: background video, in/out range, top-level masks, track icons
- background video layer (document-level), syncs to playhead in editor and fullscreen - playback in/out brackets on the scrub ruler; i/o set them, r rewinds to in-point - togglePlay, tick, fullscreen loop, WebM + server FFmpeg export all honor [in, out] - top-level mask layers cascade over everything below until a group boundary - fullscreen preview slaves localTime to the bg video so it stops stuttering - welcome "new project" now actually loads a blank scene (was a no-op) - default canvas background hardened to white via normalizeScene - new keyframe button (+) in the name-spacer, backspace/delete removes selected kfs - drag-end auto-selects the created/shifted keyframe so the path is visible - animation path updates live during drag and spans every selected keyframe - track rows show an icon per asset kind (rect/circle/image/text/polygon/freeform) - default mask mode is negative, buttons read − then + - sticky track-name columns raised above playhead/brackets; orange bracket styling - tests for playback range, blank scene, mask defaults, cascading top-level masks Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
69de48ea8d
commit
d4c4830fe2
17 files changed
+1332
-153
No files matched your search
+14
-5
@@ -12,6 +12,14 @@ function effectiveEnd(scene: Scene): number {
|
||||
return Math.min(max, scene.duration)
|
||||
}
|
||||
|
||||
function playbackRange(scene: Scene): { start: number; end: number } {
|
||||
const autoEnd = effectiveEnd(scene)
|
||||
const start = Math.max(0, Math.min(scene.duration, scene.inTime ?? 0))
|
||||
const rawEnd = scene.outTime == null ? autoEnd : scene.outTime
|
||||
const end = Math.max(start, Math.min(scene.duration, rawEnd))
|
||||
return { start, end }
|
||||
}
|
||||
|
||||
export interface VideoExportResult {
|
||||
blob: Blob
|
||||
mimeType: string
|
||||
@@ -56,8 +64,10 @@ export async function renderSceneToVideo(
|
||||
if (!ctx) throw new Error('2D context unavailable')
|
||||
|
||||
const images = await loadSceneImages(scene)
|
||||
const { start: rangeIn, end: rangeOut } = playbackRange(scene)
|
||||
const rangeLen = Math.max(0, rangeOut - rangeIn)
|
||||
|
||||
drawSceneFrame(ctx, scene, 0, images)
|
||||
drawSceneFrame(ctx, scene, rangeIn, images)
|
||||
|
||||
const stream = canvas.captureStream(scene.fps)
|
||||
const recorder = new MediaRecorder(stream, {
|
||||
@@ -76,7 +86,6 @@ export async function renderSceneToVideo(
|
||||
|
||||
recorder.start()
|
||||
const startedAt = performance.now()
|
||||
const renderDuration = effectiveEnd(scene)
|
||||
|
||||
const finished = new Promise<void>((resolve, reject) => {
|
||||
let raf = 0
|
||||
@@ -93,10 +102,10 @@ export async function renderSceneToVideo(
|
||||
return
|
||||
}
|
||||
const elapsed = (performance.now() - startedAt) / 1000
|
||||
const t = Math.min(elapsed, renderDuration)
|
||||
const t = rangeIn + Math.min(elapsed, rangeLen)
|
||||
drawSceneFrame(ctx, scene, t, images)
|
||||
opts.onProgress?.(renderDuration > 0 ? Math.min(1, t / renderDuration) : 1)
|
||||
if (elapsed >= renderDuration) {
|
||||
opts.onProgress?.(rangeLen > 0 ? Math.min(1, elapsed / rangeLen) : 1)
|
||||
if (elapsed >= rangeLen) {
|
||||
try {
|
||||
recorder.stop()
|
||||
} catch {
|
||||
|
||||
Reference in new issue
Block a user