From 1241de6e1a02c5be9846708def36624d8166a610 Mon Sep 17 00:00:00 2001 From: Siddharth Date: Thu, 27 Nov 2025 22:24:17 -0700 Subject: [PATCH] trim integration export --- dist-electron/main.js | 1 - electron/windows.ts | 2 - src/components/launch/LaunchWindow.tsx | 2 +- src/components/video-editor/VideoEditor.tsx | 3 +- src/lib/exporter/videoExporter.ts | 53 +++++++++++++++++++-- 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/dist-electron/main.js b/dist-electron/main.js index 3fa8909..84b48e9 100644 --- a/dist-electron/main.js +++ b/dist-electron/main.js @@ -92,7 +92,6 @@ function createEditorWindow() { query: { windowType: "editor" } }); } - win.webContents.openDevTools(); return win; } function createSourceSelectorWindow() { diff --git a/electron/windows.ts b/electron/windows.ts index c5658f7..0f57dc8 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -114,8 +114,6 @@ export function createEditorWindow(): BrowserWindow { }) } - win.webContents.openDevTools(); - return win } diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index f6f6780..feb8183 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -156,7 +156,7 @@ export function LaunchWindow() { className={`gap-1 text-white bg-transparent hover:bg-transparent px-0 flex-1 text-right text-xs ${styles.electronNoDrag} ${styles.folderButton}`} > - Load + Open {/* Separator before hide/close buttons */} diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index b67f6c7..b496ede 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -281,6 +281,7 @@ export default function VideoEditor() { codec: 'avc1.640033', wallpaper, zoomRegions, + trimRegions, showShadow: shadowIntensity > 0, shadowIntensity, showBlur, @@ -325,7 +326,7 @@ export default function VideoEditor() { setIsExporting(false); exporterRef.current = null; } - }, [videoPath, wallpaper, zoomRegions, shadowIntensity, showBlur, cropRegion, isPlaying]); + }, [videoPath, wallpaper, zoomRegions, trimRegions, shadowIntensity, showBlur, cropRegion, isPlaying]); const handleCancelExport = useCallback(() => { if (exporterRef.current) { diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index c4d468d..9631598 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -2,12 +2,13 @@ import type { ExportConfig, ExportProgress, ExportResult } from './types'; import { VideoFileDecoder } from './videoDecoder'; import { FrameRenderer } from './frameRenderer'; import { VideoMuxer } from './muxer'; -import type { ZoomRegion, CropRegion } from '@/components/video-editor/types'; +import type { ZoomRegion, CropRegion, TrimRegion } from '@/components/video-editor/types'; interface VideoExporterConfig extends ExportConfig { videoUrl: string; wallpaper: string; zoomRegions: ZoomRegion[]; + trimRegions?: TrimRegion[]; showShadow: boolean; shadowIntensity: number; showBlur: boolean; @@ -35,6 +36,36 @@ export class VideoExporter { this.config = config; } + // Calculate the total duration excluding trim regions (in seconds) + private getEffectiveDuration(totalDuration: number): number { + const trimRegions = this.config.trimRegions || []; + const totalTrimDuration = trimRegions.reduce((sum, region) => { + return sum + (region.endMs - region.startMs) / 1000; + }, 0); + return totalDuration - totalTrimDuration; + } + + private mapEffectiveToSourceTime(effectiveTimeMs: number): number { + const trimRegions = this.config.trimRegions || []; + // Sort trim regions by start time + const sortedTrims = [...trimRegions].sort((a, b) => a.startMs - b.startMs); + + let sourceTimeMs = effectiveTimeMs; + + for (const trim of sortedTrims) { + // If the source time hasn't reached this trim region yet, we're done + if (sourceTimeMs < trim.startMs) { + break; + } + + // Add the duration of this trim region to the source time + const trimDuration = trim.endMs - trim.startMs; + sourceTimeMs += trimDuration; + } + + return sourceTimeMs; + } + async export(): Promise { try { this.cleanup(); @@ -60,7 +91,6 @@ export class VideoExporter { await this.renderer.initialize(); // Initialize video encoder - const totalFrames = Math.ceil(videoInfo.duration * this.config.frameRate); await this.initializeEncoder(); // Initialize muxer @@ -73,6 +103,14 @@ export class VideoExporter { throw new Error('Video element not available'); } + // Calculate effective duration and frame count (excluding trim regions) + const effectiveDuration = this.getEffectiveDuration(videoInfo.duration); + const totalFrames = Math.ceil(effectiveDuration * this.config.frameRate); + + console.log('[VideoExporter] Original duration:', videoInfo.duration, 's'); + console.log('[VideoExporter] Effective duration:', effectiveDuration, 's'); + console.log('[VideoExporter] Total frames to export:', totalFrames); + // Process frames continuously without batching delays const frameDuration = 1_000_000 / this.config.frameRate; // in microseconds let frameIndex = 0; @@ -81,7 +119,11 @@ export class VideoExporter { while (frameIndex < totalFrames && !this.cancelled) { const i = frameIndex; const timestamp = i * frameDuration; - const videoTime = i * timeStep; + + // Map effective time to source time (accounting for trim regions) + const effectiveTimeMs = (i * timeStep) * 1000; + const sourceTimeMs = this.mapEffectiveToSourceTime(effectiveTimeMs); + const videoTime = sourceTimeMs / 1000; // Seek if needed or wait for first frame to be ready const needsSeek = Math.abs(videoElement.currentTime - videoTime) > 0.001; @@ -106,8 +148,9 @@ export class VideoExporter { timestamp, }); - // Render the frame with all effects - await this.renderer!.renderFrame(videoFrame, timestamp); + // Render the frame with all effects using source timestamp + const sourceTimestamp = sourceTimeMs * 1000; // Convert to microseconds + await this.renderer!.renderFrame(videoFrame, sourceTimestamp); videoFrame.close();