From 18ca69546967647d79bc25d3274ad08fe3e32dc5 Mon Sep 17 00:00:00 2001 From: Siddharth Date: Fri, 28 Nov 2025 17:14:55 -0700 Subject: [PATCH] aspect ratio bug --- src/components/video-editor/VideoEditor.tsx | 26 +++++-- src/lib/exporter/frameRenderer.ts | 77 ++++++++++----------- 2 files changed, 58 insertions(+), 45 deletions(-) diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 61b1e5cd..798f4d02 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -261,11 +261,27 @@ export default function VideoEditor() { return; } - const width = video.videoWidth || 1920; - const height = video.videoHeight || 1080; + const sourceWidth = video.videoWidth || 1920; + const sourceHeight = video.videoHeight || 1080; + const targetAspectRatio = 16 / 9; + const sourceAspectRatio = sourceWidth / sourceHeight; + + let exportWidth: number; + let exportHeight: number; + + if (sourceAspectRatio > targetAspectRatio) { + exportHeight = sourceHeight; + exportWidth = Math.round(exportHeight * targetAspectRatio); + } else { + exportWidth = sourceWidth; + exportHeight = Math.round(exportWidth / targetAspectRatio); + } + + exportWidth = Math.round(exportWidth / 2) * 2; + exportHeight = Math.round(exportHeight / 2) * 2; // Calculate visually lossless bitrate matching screen recording optimization - const totalPixels = width * height; + const totalPixels = exportWidth * exportHeight; let bitrate = 30_000_000; if (totalPixels > 1920 * 1080 && totalPixels <= 2560 * 1440) { bitrate = 50_000_000; @@ -275,8 +291,8 @@ export default function VideoEditor() { const exporter = new VideoExporter({ videoUrl: videoPath, - width, - height, + width: exportWidth, + height: exportHeight, frameRate: 60, bitrate, codec: 'avc1.640033', diff --git a/src/lib/exporter/frameRenderer.ts b/src/lib/exporter/frameRenderer.ts index f5624a6f..ffe1f842 100644 --- a/src/lib/exporter/frameRenderer.ts +++ b/src/lib/exporter/frameRenderer.ts @@ -3,8 +3,9 @@ import type { ZoomRegion, CropRegion } from '@/components/video-editor/types'; import { ZOOM_DEPTH_SCALES } from '@/components/video-editor/types'; import { findDominantRegion } from '@/components/video-editor/videoPlayback/zoomRegionUtils'; import { applyZoomTransform } from '@/components/video-editor/videoPlayback/zoomTransform'; -import { DEFAULT_FOCUS, SMOOTHING_FACTOR, MIN_DELTA, VIEWPORT_SCALE } from '@/components/video-editor/videoPlayback/constants'; +import { DEFAULT_FOCUS, SMOOTHING_FACTOR, MIN_DELTA } from '@/components/video-editor/videoPlayback/constants'; import { clampFocusToStage as clampFocusToStageUtil } from '@/components/video-editor/videoPlayback/focusUtils'; +import { layoutVideoContent as layoutVideoContentUtil } from '@/components/video-editor/videoPlayback/layoutUtils'; interface FrameRenderConfig { width: number; @@ -298,57 +299,53 @@ export class FrameRenderer { } private updateLayout(): void { - if (!this.app || !this.videoSprite || !this.maskGraphics || !this.videoContainer) return; + if (!this.app || !this.videoSprite || !this.maskGraphics || !this.videoContainer || !this.cameraContainer) return; const { width, height } = this.config; - const { cropRegion } = this.config; const videoWidth = this.config.videoWidth; const videoHeight = this.config.videoHeight; - // Calculate cropped video dimensions - const cropStartX = cropRegion.x; - const cropStartY = cropRegion.y; - const cropEndX = cropRegion.x + cropRegion.width; - const cropEndY = cropRegion.y + cropRegion.height; + // Create a mock container element for layoutVideoContentUtil + const mockContainer = { + clientWidth: width, + clientHeight: height, + } as HTMLDivElement; - const croppedVideoWidth = videoWidth * (cropEndX - cropStartX); - const croppedVideoHeight = videoHeight * (cropEndY - cropStartY); - - // Calculate scale to fit in viewport - const viewportWidth = width * VIEWPORT_SCALE; - const viewportHeight = height * VIEWPORT_SCALE; - const scale = Math.min(viewportWidth / croppedVideoWidth, viewportHeight / croppedVideoHeight); + // Create a mock video element with the actual dimensions + const mockVideoElement = { + videoWidth, + videoHeight, + } as HTMLVideoElement; - // Position video sprite - this.videoSprite.width = videoWidth * scale; - this.videoSprite.height = videoHeight * scale; + // Use the same layout logic as preview + const layoutResult = layoutVideoContentUtil({ + container: mockContainer, + app: this.app, + videoSprite: this.videoSprite, + maskGraphics: this.maskGraphics, + videoElement: mockVideoElement, + cropRegion: this.config.cropRegion, + lockedVideoDimensions: { width: videoWidth, height: videoHeight }, + }); - const cropPixelX = cropStartX * videoWidth * scale; - const cropPixelY = cropStartY * videoHeight * scale; - this.videoSprite.x = -cropPixelX; - this.videoSprite.y = -cropPixelY; + if (!layoutResult) { + console.warn('[FrameRenderer] layoutVideoContentUtil returned null'); + return; + } - // Position video container - const croppedDisplayWidth = croppedVideoWidth * scale; - const croppedDisplayHeight = croppedVideoHeight * scale; - const centerOffsetX = (width - croppedDisplayWidth) / 2; - const centerOffsetY = (height - croppedDisplayHeight) / 2; - this.videoContainer.x = centerOffsetX; - this.videoContainer.y = centerOffsetY; - - // Update mask - const radius = Math.min(croppedDisplayWidth, croppedDisplayHeight) * 0.02; - this.maskGraphics.clear(); - this.maskGraphics.roundRect(0, 0, croppedDisplayWidth, croppedDisplayHeight, radius); - this.maskGraphics.fill({ color: 0xffffff }); + // Position the videoContainer (which contains the sprite and mask) + // Note: layoutVideoContentUtil positions the sprite within the container at (0,0) + // but in export we need to position the container itself + this.videoContainer.x = 0; + this.videoContainer.y = 0; // Cache layout info this.layoutCache = { - stageSize: { width, height }, - videoSize: { width: croppedVideoWidth, height: croppedVideoHeight }, - baseScale: scale, - baseOffset: { x: centerOffsetX, y: centerOffsetY }, - maskRect: { x: 0, y: 0, width: croppedDisplayWidth, height: croppedDisplayHeight }, + stageSize: layoutResult.stageSize, + videoSize: layoutResult.videoSize, + baseScale: layoutResult.baseScale, + baseOffset: layoutResult.baseOffset, + maskRect: layoutResult.maskRect, }; }