@@ -1232,11 +1232,16 @@ export default function VideoEditor() {
|
||||
// Build export settings from current state
|
||||
const sourceWidth = video.videoWidth || 1920;
|
||||
const sourceHeight = video.videoHeight || 1080;
|
||||
const aspectRatioValue =
|
||||
aspectRatio === "native"
|
||||
? getNativeAspectRatioValue(sourceWidth, sourceHeight, cropRegion)
|
||||
: getAspectRatioValue(aspectRatio);
|
||||
const gifDimensions = calculateOutputDimensions(
|
||||
sourceWidth,
|
||||
sourceHeight,
|
||||
gifSizePreset,
|
||||
GIF_SIZE_PRESETS,
|
||||
aspectRatioValue,
|
||||
);
|
||||
|
||||
const settings: ExportSettings = {
|
||||
@@ -1260,7 +1265,17 @@ export default function VideoEditor() {
|
||||
|
||||
// Start export immediately
|
||||
handleExport(settings);
|
||||
}, [videoPath, exportFormat, exportQuality, gifFrameRate, gifLoop, gifSizePreset, handleExport]);
|
||||
}, [
|
||||
videoPath,
|
||||
exportFormat,
|
||||
exportQuality,
|
||||
gifFrameRate,
|
||||
gifLoop,
|
||||
gifSizePreset,
|
||||
aspectRatio,
|
||||
cropRegion,
|
||||
handleExport,
|
||||
]);
|
||||
|
||||
const handleCancelExport = useCallback(() => {
|
||||
if (exporterRef.current) {
|
||||
@@ -1475,6 +1490,13 @@ export default function VideoEditor() {
|
||||
videoPlaybackRef.current?.video?.videoHeight || 1080,
|
||||
gifSizePreset,
|
||||
GIF_SIZE_PRESETS,
|
||||
aspectRatio === "native"
|
||||
? getNativeAspectRatioValue(
|
||||
videoPlaybackRef.current?.video?.videoWidth || 1920,
|
||||
videoPlaybackRef.current?.video?.videoHeight || 1080,
|
||||
cropRegion,
|
||||
)
|
||||
: getAspectRatioValue(aspectRatio),
|
||||
)}
|
||||
onExport={handleOpenExportDialog}
|
||||
selectedAnnotationId={selectedAnnotationId}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { calculateOutputDimensions } from "./gifExporter";
|
||||
import { GIF_SIZE_PRESETS } from "./types";
|
||||
|
||||
describe("calculateOutputDimensions", () => {
|
||||
it("uses the selected aspect ratio for scaled GIF exports", () => {
|
||||
expect(calculateOutputDimensions(1080, 1920, "medium", GIF_SIZE_PRESETS, 16 / 9)).toEqual({
|
||||
width: 1280,
|
||||
height: 720,
|
||||
});
|
||||
});
|
||||
|
||||
it("fits original-size GIF exports within the source bounds at the selected aspect ratio", () => {
|
||||
expect(calculateOutputDimensions(1080, 1920, "original", GIF_SIZE_PRESETS, 16 / 9)).toEqual({
|
||||
width: 1080,
|
||||
height: 606,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -58,24 +58,39 @@ export function calculateOutputDimensions(
|
||||
sourceHeight: number,
|
||||
sizePreset: GifSizePreset,
|
||||
sizePresets: typeof GIF_SIZE_PRESETS,
|
||||
targetAspectRatio = sourceWidth / sourceHeight,
|
||||
): { width: number; height: number } {
|
||||
const preset = sizePresets[sizePreset];
|
||||
const maxHeight = preset.maxHeight;
|
||||
const aspectRatio =
|
||||
Number.isFinite(targetAspectRatio) && targetAspectRatio > 0
|
||||
? targetAspectRatio
|
||||
: sourceWidth / sourceHeight;
|
||||
|
||||
// If original is smaller than max height or preset is 'original', use source dimensions
|
||||
if (sourceHeight <= maxHeight || sizePreset === "original") {
|
||||
return { width: sourceWidth, height: sourceHeight };
|
||||
const toEven = (value: number) => {
|
||||
const evenValue = Math.max(2, Math.floor(value / 2) * 2);
|
||||
return evenValue;
|
||||
};
|
||||
|
||||
if (sizePreset === "original") {
|
||||
const sourceAspect = sourceWidth / sourceHeight;
|
||||
if (aspectRatio >= sourceAspect) {
|
||||
const width = toEven(sourceWidth);
|
||||
const height = toEven(width / aspectRatio);
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
const height = toEven(sourceHeight);
|
||||
const width = toEven(height * aspectRatio);
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
// Calculate scaled dimensions preserving aspect ratio
|
||||
const aspectRatio = sourceWidth / sourceHeight;
|
||||
const newHeight = maxHeight;
|
||||
const newWidth = Math.round(newHeight * aspectRatio);
|
||||
const targetHeight = maxHeight;
|
||||
const targetWidth = Math.round(targetHeight * aspectRatio);
|
||||
|
||||
// Ensure dimensions are even (required for some encoders)
|
||||
return {
|
||||
width: newWidth % 2 === 0 ? newWidth : newWidth + 1,
|
||||
height: newHeight % 2 === 0 ? newHeight : newHeight + 1,
|
||||
width: toEven(targetWidth),
|
||||
height: toEven(targetHeight),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user