From e837e7504379e100ff3b56282577ec67bb88bec9 Mon Sep 17 00:00:00 2001 From: cashmere Date: Tue, 28 Jul 2026 19:41:14 +0200 Subject: feat: ripple-extend resize, timeline filmstrip, export hardening Yesterday's verified web session (gates green: 316 tests, tsc, build): - ripple-extend pushes right-hand chain on static-element resize - per-position video filmstrip thumbnails with zoom-adaptive density - fps-aware export bitrate, 2s keyframes, even-dim export canvas - freeze frame uses fixed 3s duration, prompt removed --- apps/web/src/core/managers/media-manager.ts | 3 +++ apps/web/src/core/managers/renderer-manager.ts | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'apps/web/src/core') diff --git a/apps/web/src/core/managers/media-manager.ts b/apps/web/src/core/managers/media-manager.ts index 69da6e80..739fd3fa 100644 --- a/apps/web/src/core/managers/media-manager.ts +++ b/apps/web/src/core/managers/media-manager.ts @@ -4,6 +4,7 @@ import type { MediaAsset } from "@/media/types"; import { storageService } from "@/services/storage/service"; import { generateUUID } from "@/utils/id"; import { videoCache } from "@/services/video-cache/service"; +import { thumbnailCache } from "@/services/thumbnail-cache/service"; import { waveformCache } from "@/services/waveform-cache/service"; import { BatchCommand, RemoveMediaAssetCommand } from "@/commands"; @@ -103,6 +104,7 @@ export class MediaManager { } async clearProjectMedia({ projectId }: { projectId: string }): Promise { + thumbnailCache.clearAll(); waveformCache.clearAll(); this.assets.forEach((asset) => { @@ -131,6 +133,7 @@ export class MediaManager { clearAllAssets(): void { videoCache.clearAll(); + thumbnailCache.clearAll(); waveformCache.clearAll(); this.assets.forEach((asset) => { diff --git a/apps/web/src/core/managers/renderer-manager.ts b/apps/web/src/core/managers/renderer-manager.ts index 38d1e57f..d73e3e58 100644 --- a/apps/web/src/core/managers/renderer-manager.ts +++ b/apps/web/src/core/managers/renderer-manager.ts @@ -6,7 +6,6 @@ import { SceneExporter } from "@/services/renderer/scene-exporter"; import { buildScene } from "@/services/renderer/scene-builder"; import { createTimelineAudioBuffer } from "@/media/audio"; import { formatTimecode } from "opencut-wasm"; -import { frameRateToFloat } from "@/fps/utils"; import { downloadBlob } from "@/utils/browser"; type SnapshotResult = @@ -192,6 +191,10 @@ export class RendererManager { const exportFps = fps ?? activeProject.settings.fps; const canvasSize = activeProject.settings.canvasSize; + const exportSize = { + width: 2 * Math.round(canvasSize.width / 2), + height: 2 * Math.round(canvasSize.height / 2), + }; let audioBuffer: AudioBuffer | null = null; if (includeAudio) { @@ -207,13 +210,13 @@ export class RendererManager { tracks, mediaAssets, duration, - canvasSize, + canvasSize: exportSize, background: activeProject.settings.background, }); const exporter = new SceneExporter({ - width: canvasSize.width, - height: canvasSize.height, + width: exportSize.width, + height: exportSize.height, fps: exportFps, format, quality, -- cgit v1.3.1