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();